From ec8ae759c270add73538a5d79f233b8613a5efe2 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 3 May 2022 21:10:55 +1000 Subject: [PATCH 001/298] notebook dev, adds nest coefficient in logit.py --- notebooks/example_dev-Copy1.ipynb | 4295 +++++++++++++++++ notebooks/frozen_rand_mode_choice_dev.ipynb | 4199 ++++++++++++++++ ...ested_logit_frozen_rand_individ_util.ipynb | 463 ++ 3 files changed, 8957 insertions(+) create mode 100644 notebooks/example_dev-Copy1.ipynb create mode 100644 notebooks/frozen_rand_mode_choice_dev.ipynb create mode 100644 notebooks/nested_logit_frozen_rand_individ_util.ipynb diff --git a/notebooks/example_dev-Copy1.ipynb b/notebooks/example_dev-Copy1.ipynb new file mode 100644 index 0000000000..95993575f5 --- /dev/null +++ b/notebooks/example_dev-Copy1.ipynb @@ -0,0 +1,4295 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", + "metadata": {}, + "source": [ + "# Copy of previous dev work\n", + "keep to make sure I can quickly re-trace my steps and understand Asim steps, but keep current dev notebook clean. Should probably start to properly pull out things during dev once they are clear.\n", + "\n", + "Problem below: scale of error term on lower levels needs to be given by nest, not the case atm" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.385153Z", + "iopub.status.busy": "2022-05-01T10:17:57.384881Z", + "iopub.status.idle": "2022-05-01T10:17:57.534433Z", + "shell.execute_reply": "2022-05-01T10:17:57.533096Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%load_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3dba451-1e10-403e-8614-35d57e6577f4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.536623Z", + "iopub.status.busy": "2022-05-01T10:17:57.536012Z", + "iopub.status.idle": "2022-05-01T10:17:57.542755Z", + "shell.execute_reply": "2022-05-01T10:17:57.541685Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:00.350944Z", + "iopub.status.busy": "2022-05-01T10:18:00.350730Z", + "iopub.status.idle": "2022-05-01T10:18:12.760977Z", + "shell.execute_reply": "2022-05-01T10:18:12.760013Z", + "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import argparse\n", + "from datetime import datetime\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from numpy.random import default_rng\n", + "\n", + "\n", + "from activitysim.cli import run\n", + "from activitysim.core import inject\n", + "from activitysim.core import tracing\n", + "from activitysim.core import config\n", + "from activitysim.core import pipeline\n", + "from activitysim.core import mem\n", + "from activitysim.core import chunk\n", + "from activitysim.core import simulate\n", + "from activitysim.core import logit\n", + "from activitysim.abm.models.util.mode import mode_choice_simulate\n", + "from activitysim.abm.models.util import estimation\n", + "from activitysim.core import expressions\n", + "from activitysim.core.util import assign_in_place" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:16.944537Z", + "iopub.status.busy": "2022-05-01T10:18:16.944291Z", + "iopub.status.idle": "2022-05-01T10:18:17.124764Z", + "shell.execute_reply": "2022-05-01T10:18:17.123725Z", + "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "pd.set_option(\"max_columns\", 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:17.839947Z", + "iopub.status.busy": "2022-05-01T10:18:17.839070Z", + "iopub.status.idle": "2022-05-01T10:18:18.019676Z", + "shell.execute_reply": "2022-05-01T10:18:18.018689Z", + "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_example_mtc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:18.894533Z", + "iopub.status.busy": "2022-05-01T10:18:18.894303Z", + "iopub.status.idle": "2022-05-01T10:18:19.078807Z", + "shell.execute_reply": "2022-05-01T10:18:19.077951Z", + "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "os.chdir(example_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:22.111723Z", + "iopub.status.busy": "2022-05-01T10:18:22.111490Z", + "iopub.status.idle": "2022-05-01T10:18:22.297437Z", + "shell.execute_reply": "2022-05-01T10:18:22.296501Z", + "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "run.add_run_args(parser)\n", + "# args = parser.parse_args()\n", + "# parser.parse_args(['--sum', '7', '-1', '42'])\n", + "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", + "#run.run(args) # 10mins full example run" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7c2e2f00-f396-4016-8545-dd959f232468", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:23.454776Z", + "iopub.status.busy": "2022-05-01T10:18:23.454538Z", + "iopub.status.idle": "2022-05-01T10:18:23.637711Z", + "shell.execute_reply": "2022-05-01T10:18:23.636943Z", + "shell.execute_reply.started": "2022-05-01T10:18:23.454751Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "if not inject.is_injectable('preload_injectables'):\n", + " from activitysim import abm # register abm steps and other abm-specific injectables\n", + "\n", + "run.handle_standard_args(args) # possibly update injectables\n", + "\n", + "resume_after = \"trip_scheduling\"\n", + "\n", + "\n", + "#####\n", + "## rest below replaces the next two lines\n", + "## pipeline.run(models=config.setting('models'), resume_after=resume_after)\n", + "## pipeline.close_pipeline()\n", + "#####\n", + "\n", + "# pipeline.open_pipeline(resume_after)\n", + "\n", + "# # preload any bulky injectables (e.g. skims) not in pipeline\n", + "# inject.get_injectable('preload_injectables', None)\n", + "\n", + "# #pipeline.run_model(resume_after) # from here on\n", + "# model_name = \"trip_mode_choice\"\n", + "# pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "# step_name = model_name\n", + "# args = {}\n", + "\n", + "# checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "\n", + "# inject.set_step_args(args)\n", + "\n", + "# pipeline.orca.run([step_name])\n", + "\n", + "# inject.set_step_args(None)\n", + "\n", + "# pipeline._PIPELINE.rng().end_step(model_name)\n", + "# pipeline.add_checkpoint(model_name)\n", + "\n", + "# # add checkpoint with final tables even if not intermediate checkpointing\n", + "# if not pipeline.intermediate_checkpoint():\n", + "# pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + "# pipeline.close_pipeline()" + ] + }, + { + "cell_type": "markdown", + "id": "6045438f-d1eb-4b5c-9737-185798b7f97f", + "metadata": {}, + "source": [ + "## trip mode choice by hand" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "600ae244-3e6c-4b66-8d39-aa9f6f60b378", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:26.399145Z", + "iopub.status.busy": "2022-05-01T10:18:26.398687Z", + "iopub.status.idle": "2022-05-01T10:18:26.583256Z", + "shell.execute_reply": "2022-05-01T10:18:26.582233Z", + "shell.execute_reply.started": "2022-05-01T10:18:26.399105Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#inject.get_table('trips').to_frame() #pipeline.orca.get_raw_table('trips').to_frame()\n", + "#inject.get_table('tours_merged').to_frame() #pipeline.orca.get_raw_table('tours_merged').to_frame()\n", + "#inject.get_injectable('network_los')" + ] + }, + { + "cell_type": "code", + "execution_count": 373, + "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:28:53.643404Z", + "iopub.status.busy": "2021-09-01T02:28:53.643055Z", + "iopub.status.idle": "2021-09-01T02:29:01.223951Z", + "shell.execute_reply": "2021-09-01T02:29:01.222865Z", + "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'atwork' (27 trips)\n", + "trip_mode_choice tour_type 'eatout' (33 trips)\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n", + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", + "trip_mode_choice tour_type 'othmaint' (46 trips)\n", + "trip_mode_choice tour_type 'school' (37 trips)\n", + "trip_mode_choice tour_type 'shopping' (77 trips)\n", + "trip_mode_choice tour_type 'social' (19 trips)\n", + "trip_mode_choice tour_type 'univ' (26 trips)\n", + "trip_mode_choice tour_type 'work' (168 trips)\n", + "Done\n" + ] + } + ], + "source": [ + "pipeline.open_pipeline(resume_after)\n", + "\n", + "# preload any bulky injectables (e.g. skims) not in pipeline\n", + "inject.get_injectable('preload_injectables', None)\n", + "\n", + "#pipeline.run_model(resume_after) # from here on\n", + "model_name = \"trip_mode_choice\"\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "step_name = model_name\n", + "args = {}\n", + "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "#####\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "chunk_size = 0\n", + "\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "\n", + "trips_df = trips.to_frame()\n", + "print(\"Running with %d trips\", trips_df.shape[0])\n", + "\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "\n", + "\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + "orig_col = 'origin'\n", + "dest_col = 'destination'\n", + "\n", + "constants = {}\n", + "constants.update(config.get_model_constants(model_settings))\n", + "constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + "})\n", + "\n", + "skim_dict = network_los.get_default_skim_dict()\n", + "\n", + "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + "skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + "}\n", + "\n", + "\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "\n", + "\n", + "\n", + "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + "\n", + "do_these_purposes = None #['escort']\n", + "\n", + "choices_list = []\n", + "for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + "\n", + " if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", + " continue\n", + "\n", + " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " (primary_purpose, len(trips_segment.index), ))\n", + "\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + " \n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + "################ Replace wrapper function\n", + "# choices = mode_choice_simulate(\n", + "# choosers=trips_segment,\n", + "# spec=simulate.eval_coefficients(model_spec, coefficients, estimator),\n", + "# nest_spec=simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label),\n", + "# skims=skims,\n", + "# locals_d=locals_dict,\n", + "# chunk_size=chunk_size,\n", + "# mode_column_name=mode_column_name,\n", + "# logsum_column_name=logsum_column_name,\n", + "# trace_label=segment_trace_label,\n", + "# trace_choice_name='trip_mode_choice',\n", + "# estimator=estimator)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simulate.simple_simulate(\n", + " choosers=trips_segment,\n", + " spec=spec,\n", + " nest_spec=nest_spec,\n", + " skims=skims,\n", + " locals_d=locals_dict,\n", + " chunk_size=chunk_size,\n", + " want_logsums=logsum_column_name is not None,\n", + " trace_label=segment_trace_label,\n", + " trace_choice_name='trip_mode_choice',\n", + " estimator=estimator,\n", + " trace_column_names=None)\n", + "\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + "\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + "\n", + " alts = spec.columns\n", + " choices[mode_column_name] = \\\n", + " choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", + "################\n", + "\n", + "\n", + " choices_list.append(choices)\n", + "\n", + "choices_df_asim = pd.concat(choices_list)\n", + "\n", + "# update trips table with choices (and potionally logssums)\n", + "trips_df = trips.to_frame()\n", + "\n", + "if (do_these_purposes is not None):\n", + " trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", + "\n", + "assign_in_place(trips_df, choices_df)\n", + "assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + "print(\"Done\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7eae2854-38bd-4da0-a161-3a15bbd17177", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:30:57.562848Z", + "iopub.status.busy": "2021-08-31T10:30:57.562615Z", + "iopub.status.idle": "2021-08-31T10:30:57.806655Z", + "shell.execute_reply": "2021-08-31T10:30:57.805135Z", + "shell.execute_reply.started": "2021-08-31T10:30:57.562824Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
person_idhousehold_idtour_idprimary_purposetrip_numoutboundtrip_countpurposedestinationorigindestination_logsumdeparttrip_modemode_choice_logsum
trip_id
13724872141844130403617156090escort1True1escort710NaN7.0WALK11.435800
13724872541844130403617156090escort1False1Home107NaN7.0WALK11.480440
21138820164447638676126423525escort1True1escort1116NaN5.0WALK_LOC4.789158
21138820564447638676126423525escort1False1Home1611NaN6.0WALK_LOC5.050171
80638840124585011173905100798550escort1True1escort168NaN15.0WALK_LOC6.451457
80638840524585011173905100798550escort1False1Home816NaN16.0WALK_LOC6.446188
\n", + "
" + ], + "text/plain": [ + " person_id household_id tour_id primary_purpose trip_num \\\n", + "trip_id \n", + "137248721 418441 304036 17156090 escort 1 \n", + "137248725 418441 304036 17156090 escort 1 \n", + "211388201 644476 386761 26423525 escort 1 \n", + "211388205 644476 386761 26423525 escort 1 \n", + "806388401 2458501 1173905 100798550 escort 1 \n", + "806388405 2458501 1173905 100798550 escort 1 \n", + "\n", + " outbound trip_count purpose destination origin \\\n", + "trip_id \n", + "137248721 True 1 escort 7 10 \n", + "137248725 False 1 Home 10 7 \n", + "211388201 True 1 escort 11 16 \n", + "211388205 False 1 Home 16 11 \n", + "806388401 True 1 escort 16 8 \n", + "806388405 False 1 Home 8 16 \n", + "\n", + " destination_logsum depart trip_mode mode_choice_logsum \n", + "trip_id \n", + "137248721 NaN 7.0 WALK 11.435800 \n", + "137248725 NaN 7.0 WALK 11.480440 \n", + "211388201 NaN 5.0 WALK_LOC 4.789158 \n", + "211388205 NaN 6.0 WALK_LOC 5.050171 \n", + "806388401 NaN 15.0 WALK_LOC 6.451457 \n", + "806388405 NaN 16.0 WALK_LOC 6.446188 " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trips_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca3206ce-df97-42d9-83ce-dc4067ea5675", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6c2752e-68a8-4319-85c2-fe6bf1fd6d59", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3851df2c-bb8c-43b6-8a27-3b04a7f85a50", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b596af94-85d1-4d25-99b2-c19292397882", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "8eebea31-499c-41ca-8411-883a88ca800a", + "metadata": {}, + "source": [ + "## nested dev" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3e2513e9-c03f-4891-9a1c-4053a4440a10", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:31:01.113060Z", + "iopub.status.busy": "2021-08-31T10:31:01.112829Z", + "iopub.status.idle": "2021-08-31T10:31:01.359912Z", + "shell.execute_reply": "2021-08-31T10:31:01.358948Z", + "shell.execute_reply.started": "2021-08-31T10:31:01.113034Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "spec = simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + "nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "36c100b3-3e39-4950-a586-4d42be695eaa", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:31:01.775068Z", + "iopub.status.busy": "2021-08-31T10:31:01.774846Z", + "iopub.status.idle": "2021-08-31T10:31:01.975816Z", + "shell.execute_reply": "2021-08-31T10:31:01.974499Z", + "shell.execute_reply.started": "2021-08-31T10:31:01.775044Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'root',\n", + " 'coefficient': 1.0,\n", + " 'alternatives': [{'name': 'AUTO',\n", + " 'coefficient': 0.72,\n", + " 'alternatives': [{'name': 'DRIVEALONE',\n", + " 'coefficient': 0.35,\n", + " 'alternatives': ['DRIVEALONEFREE', 'DRIVEALONEPAY']},\n", + " {'name': 'SHAREDRIDE2',\n", + " 'coefficient': 0.35,\n", + " 'alternatives': ['SHARED2FREE', 'SHARED2PAY']},\n", + " {'name': 'SHAREDRIDE3',\n", + " 'coefficient': 0.35,\n", + " 'alternatives': ['SHARED3FREE', 'SHARED3PAY']}]},\n", + " {'name': 'NONMOTORIZED',\n", + " 'coefficient': 0.72,\n", + " 'alternatives': ['WALK', 'BIKE']},\n", + " {'name': 'TRANSIT',\n", + " 'coefficient': 0.72,\n", + " 'alternatives': [{'name': 'WALKACCESS',\n", + " 'coefficient': 0.5,\n", + " 'alternatives': ['WALK_LOC',\n", + " 'WALK_LRF',\n", + " 'WALK_EXP',\n", + " 'WALK_HVY',\n", + " 'WALK_COM']},\n", + " {'name': 'DRIVEACCESS',\n", + " 'coefficient': 0.5,\n", + " 'alternatives': ['DRIVE_LOC',\n", + " 'DRIVE_LRF',\n", + " 'DRIVE_EXP',\n", + " 'DRIVE_HVY',\n", + " 'DRIVE_COM']}]},\n", + " {'name': 'RIDEHAIL',\n", + " 'coefficient': 0.36,\n", + " 'alternatives': ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']}]}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nest_spec" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f442cf50-e85c-4991-af39-68a6839b146e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:31:03.459794Z", + "iopub.status.busy": "2021-08-31T10:31:03.459560Z", + "iopub.status.idle": "2021-08-31T10:31:03.660759Z", + "shell.execute_reply": "2021-08-31T10:31:03.658857Z", + "shell.execute_reply.started": "2021-08-31T10:31:03.459767Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root'] alternatives: ['AUTO', 'NONMOTORIZED', 'TRANSIT', 'RIDEHAIL']\n", + "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] alternatives: ['DRIVEALONE', 'SHAREDRIDE2', 'SHAREDRIDE3']\n", + "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] alternatives: ['DRIVEALONEFREE', 'DRIVEALONEPAY']\n", + "Nest name: DRIVEALONEFREE level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE'] alternatives: None\n", + "Nest name: DRIVEALONEPAY level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY'] alternatives: None\n", + "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2'] alternatives: ['SHARED2FREE', 'SHARED2PAY']\n", + "Nest name: SHARED2FREE level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2FREE'] alternatives: None\n", + "Nest name: SHARED2PAY level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2PAY'] alternatives: None\n", + "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3'] alternatives: ['SHARED3FREE', 'SHARED3PAY']\n", + "Nest name: SHARED3FREE level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3FREE'] alternatives: None\n", + "Nest name: SHARED3PAY level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3PAY'] alternatives: None\n", + "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED'] alternatives: ['WALK', 'BIKE']\n", + "Nest name: WALK level: 3 coefficient: 0 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'WALK'] alternatives: None\n", + "Nest name: BIKE level: 3 coefficient: 0 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'BIKE'] alternatives: None\n", + "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT'] alternatives: ['WALKACCESS', 'DRIVEACCESS']\n", + "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS'] alternatives: ['WALK_LOC', 'WALK_LRF', 'WALK_EXP', 'WALK_HVY', 'WALK_COM']\n", + "Nest name: WALK_LOC level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LOC'] alternatives: None\n", + "Nest name: WALK_LRF level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LRF'] alternatives: None\n", + "Nest name: WALK_EXP level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_EXP'] alternatives: None\n", + "Nest name: WALK_HVY level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_HVY'] alternatives: None\n", + "Nest name: WALK_COM level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_COM'] alternatives: None\n", + "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS'] alternatives: ['DRIVE_LOC', 'DRIVE_LRF', 'DRIVE_EXP', 'DRIVE_HVY', 'DRIVE_COM']\n", + "Nest name: DRIVE_LOC level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LOC'] alternatives: None\n", + "Nest name: DRIVE_LRF level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LRF'] alternatives: None\n", + "Nest name: DRIVE_EXP level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_EXP'] alternatives: None\n", + "Nest name: DRIVE_HVY level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_HVY'] alternatives: None\n", + "Nest name: DRIVE_COM level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_COM'] alternatives: None\n", + "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL'] alternatives: ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']\n", + "Nest name: TAXI level: 3 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TAXI'] alternatives: None\n", + "Nest name: TNC_SINGLE level: 3 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SINGLE'] alternatives: None\n", + "Nest name: TNC_SHARED level: 3 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SHARED'] alternatives: None\n" + ] + } + ], + "source": [ + "for nest in logit.each_nest(nest_spec):\n", + " nest.print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d509b34d-a755-48ca-8079-386c14023d69", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 349, + "id": "e8d11f6a-f82a-40bd-8eef-fc28bcca8252", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:59:35.109079Z", + "iopub.status.busy": "2021-09-01T01:59:35.108781Z", + "iopub.status.idle": "2021-09-01T01:59:35.319301Z", + "shell.execute_reply": "2021-09-01T01:59:35.318402Z", + "shell.execute_reply.started": "2021-09-01T01:59:35.109050Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_dev(choosers, spec, nest_spec,\n", + " skims=None, locals_d=None,\n", + " chunk_size=0, custom_chooser=None,\n", + " log_alt_losers=False,\n", + " want_logsums=False,\n", + " estimator=None,\n", + " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", + " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", + " assert len(choosers) > 0\n", + " result_list = []\n", + " # segment by person type and pick the right spec for each person type\n", + " for i, chooser_chunk, chunk_trace_label \\\n", + " in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", + " # the following replaces choices = _simple_simulate(...)\n", + " if skims is not None:\n", + " simulate.set_skim_wrapper_targets(choosers, skims)\n", + "\n", + " # only do this for nested, logit is straight forward\n", + " assert nest_spec is not None\n", + " choices = simulate.eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser,\n", + " log_alt_losers=log_alt_losers,\n", + " want_logsums=want_logsums,\n", + " estimator=estimator,\n", + " trace_label=trace_label,\n", + " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", + "\n", + "\n", + " result_list.append(choices)\n", + " chunk.log_df(trace_label, f'result_list', result_list)\n", + "\n", + " if len(result_list) > 1:\n", + " choices = pd.concat(result_list)\n", + " assert len(choices.index == len(choosers.index))\n", + " return choices\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "05e06aa4-e943-4c34-ad1b-09c19c800bdc", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:35:06.100477Z", + "iopub.status.busy": "2021-08-31T10:35:06.100054Z", + "iopub.status.idle": "2021-08-31T10:35:06.974578Z", + "shell.execute_reply": "2021-08-31T10:35:06.973620Z", + "shell.execute_reply.started": "2021-08-31T10:35:06.100438Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "primary_purpose = 'escort'\n", + "trips_segment = trips_merged.loc[trips_merged.primary_purpose == primary_purpose].copy()\n", + "assert trips_segment.index.name == 'trip_id' # name index so tracing knows how to slice\n", + "coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "locals_dict = {}\n", + "locals_dict.update(constants)\n", + "locals_dict.update(coefficients)\n", + "segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + "locals_dict.update(skims)\n", + "\n", + "spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + "nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + "choices = simple_simulate_dev(\n", + " choosers=trips_segment,\n", + " spec=spec,\n", + " nest_spec=nest_spec,\n", + " skims=skims,\n", + " locals_d=locals_dict,\n", + " chunk_size=chunk_size,\n", + " want_logsums=logsum_column_name is not None,\n", + " trace_label=segment_trace_label,\n", + " trace_choice_name='trip_mode_choice',\n", + " estimator=estimator,\n", + " trace_column_names=None)\n", + "# for consistency, always return dataframe, whether or not logsums were requested\n", + "if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + "\n", + "choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + "\n", + "alts = spec.columns\n", + "choices[mode_column_name] = \\\n", + " choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec4eb1ff-2f35-4919-96b4-5acffa01f597", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07ef8fbc-beb2-4e57-9d56-939719026d58", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c60656db-075a-4eab-96b3-fc54eb785075", + "metadata": {}, + "source": [ + "## eval_nl dev" + ] + }, + { + "cell_type": "code", + "execution_count": 372, + "id": "56c8923c-e946-48d8-b964-87695c30163d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:27:40.290389Z", + "iopub.status.busy": "2021-09-01T02:27:40.290139Z", + "iopub.status.idle": "2021-09-01T02:27:40.977526Z", + "shell.execute_reply": "2021-09-01T02:27:40.976594Z", + "shell.execute_reply.started": "2021-09-01T02:27:40.290363Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# replaces simple_simulate_dev:\n", + "\n", + "want_logsums = True\n", + "trace_choice_name='trip_mode_choice'\n", + "trace_column_names=None\n", + "log_alt_losers=False\n", + "\n", + "tr_label = tracing.extend_trace_label(segment_trace_label, 'simple_simulate')\n", + "assert len(trips_segment) > 0\n", + "result_list = []\n", + "# segment by person type and pick the right spec for each person type\n", + "for i, chooser_chunk, chunk_trace_label \\\n", + " in chunk.adaptive_chunked_choosers(trips_segment, chunk_size, tr_label):\n", + " # the following replaces choices = _simple_simulate(...)\n", + " if skims is not None:\n", + " simulate.set_skim_wrapper_targets(trips_segment, skims)\n", + "\n", + " # only do this for nested, logit is straight forward\n", + " assert nest_spec is not None\n", + " choices = simulate.eval_nl(trips_segment, spec, nest_spec, locals_dict, custom_chooser=None,\n", + " log_alt_losers=log_alt_losers,\n", + " want_logsums=want_logsums,\n", + " estimator=estimator,\n", + " trace_label=tr_label,\n", + " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", + "\n", + "\n", + " result_list.append(choices)\n", + "\n", + "if len(result_list) > 1:\n", + " choices = pd.concat(result_list)\n", + "assert len(choices.index == len(trips_segment.index))" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "cbc2dfde-1bd9-4b03-96d9-ca9b7222d36c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T12:52:34.544865Z", + "iopub.status.busy": "2021-08-31T12:52:34.544586Z", + "iopub.status.idle": "2021-08-31T12:52:35.159615Z", + "shell.execute_reply": "2021-08-31T12:52:35.158682Z", + "shell.execute_reply.started": "2021-08-31T12:52:34.544840Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "locals_d = locals_dict\n", + "custom_chooser=None\n", + "\n", + "trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + "logit.validate_nest_spec(nest_spec, trace_label)\n", + "\n", + "raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + "nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + "\n", + "nested_probabilities = \\\n", + " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", + "\n", + "logsums = pd.Series(np.log(nested_exp_utilities.root), index=trips_segment.index)\n", + "\n", + "# global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + "base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", + "\n", + "choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "0cd9593b-b3fd-41f6-a7ca-4c5b73b63571", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T12:52:45.509057Z", + "iopub.status.busy": "2021-08-31T12:52:45.508819Z", + "iopub.status.idle": "2021-08-31T12:52:45.721956Z", + "shell.execute_reply": "2021-08-31T12:52:45.721064Z", + "shell.execute_reply.started": "2021-08-31T12:52:45.509021Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 0.990308\n", + "137248725 0.108433\n", + "211388201 0.806612\n", + "211388205 0.195994\n", + "806388401 0.854020\n", + "806388405 0.660550\n", + "dtype: float64" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rands" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "b467330b-f1cc-43bc-9325-c6dfeaa9b364", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T12:50:51.735273Z", + "iopub.status.busy": "2021-08-31T12:50:51.734962Z", + "iopub.status.idle": "2021-08-31T12:50:52.023440Z", + "shell.execute_reply": "2021-08-31T12:50:52.022582Z", + "shell.execute_reply.started": "2021-08-31T12:50:51.735231Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.798480-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.360270-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.435800
137248725-1.732971-3966.018685-0.606540-0.324578-3964.610293-0.113602-3966.009714-7930.295428-inf0.26146615.945036-1368.16910311.480426-2737.292402-5512.292402-5512.292402-5512.292402-5512.292402-inf-2737.782457-5522.136798-5522.136798-5516.076156-5525.080218-inf-inf-24.341318-25.104291-24.140831-8.40687011.480440
211388201-3966.000225-7930.285939-inf-24.120135-3988.405850-8.442047-29.480718-3993.766432-10.318251-5.975663-4.216264-1381.397292-3.03571013.295720-2761.150907-2761.150907-2761.150907-2761.1509076.647860-2761.648763-5543.048131-5543.048131-2769.692185-2773.867648-inf4.786459-15.861515-3.864497-5.068360-1.2967574.789158
211388205-3967.791945-7932.077659-inf-25.129596-3989.415310-8.795359-30.177275-3994.462989-10.562046-6.219063-4.216264-1381.397292-3.03571014.022438-2760.977562-2760.977562-2760.977562-2760.9775627.011219-2761.294814-5546.132585-5546.132585-2764.829835-2774.132982-inf5.048078-15.776424-3.845077-5.005482-1.2860945.050171
806388401-3966.492870-7930.778584-inf-12.618378-3976.904092-4.416432-14.908422-3979.194136-5.217948-2.9129776.192499-1378.4807234.45859917.513379-2755.845870-2755.208370-2755.728926-2755.7930938.756690-2757.559519-5540.685990-5540.685990-5542.838278-5546.780761-inf6.304817-19.143696-19.495908-16.361074-5.8539176.451457
806388405-3969.835821-7934.121535-inf-14.515916-3978.801631-5.080571-16.227796-3980.513510-5.679728-3.3428055.998749-1378.5194734.31909917.553369-2755.987742-2755.350242-2755.870797-2755.9349648.776685-2757.571454-5542.673358-5542.673358-5537.799228-5547.515590-inf6.319213-18.886576-19.495133-16.300395-5.8285406.446188
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", + "trip_id \n", + "137248721 -1.798480 -3966.084195 -0.629468 -0.389611 \n", + "137248725 -1.732971 -3966.018685 -0.606540 -0.324578 \n", + "211388201 -3966.000225 -7930.285939 -inf -24.120135 \n", + "211388205 -3967.791945 -7932.077659 -inf -25.129596 \n", + "806388401 -3966.492870 -7930.778584 -inf -12.618378 \n", + "806388405 -3969.835821 -7934.121535 -inf -14.515916 \n", + "\n", + " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", + "trip_id \n", + "137248721 -3964.675326 -0.136364 -3966.074556 -7930.360270 -inf \n", + "137248725 -3964.610293 -0.113602 -3966.009714 -7930.295428 -inf \n", + "211388201 -3988.405850 -8.442047 -29.480718 -3993.766432 -10.318251 \n", + "211388205 -3989.415310 -8.795359 -30.177275 -3994.462989 -10.562046 \n", + "806388401 -3976.904092 -4.416432 -14.908422 -3979.194136 -5.217948 \n", + "806388405 -3978.801631 -5.080571 -16.227796 -3980.513510 -5.679728 \n", + "\n", + " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", + "trip_id \n", + "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", + "137248725 0.261466 15.945036 -1368.169103 11.480426 -2737.292402 \n", + "211388201 -5.975663 -4.216264 -1381.397292 -3.035710 13.295720 \n", + "211388205 -6.219063 -4.216264 -1381.397292 -3.035710 14.022438 \n", + "806388401 -2.912977 6.192499 -1378.480723 4.458599 17.513379 \n", + "806388405 -3.342805 5.998749 -1378.519473 4.319099 17.553369 \n", + "\n", + " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", + "trip_id \n", + "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", + "137248725 -5512.292402 -5512.292402 -5512.292402 -5512.292402 -inf \n", + "211388201 -2761.150907 -2761.150907 -2761.150907 -2761.150907 6.647860 \n", + "211388205 -2760.977562 -2760.977562 -2760.977562 -2760.977562 7.011219 \n", + "806388401 -2755.845870 -2755.208370 -2755.728926 -2755.793093 8.756690 \n", + "806388405 -2755.987742 -2755.350242 -2755.870797 -2755.934964 8.776685 \n", + "\n", + " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", + "trip_id \n", + "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", + "137248725 -2737.782457 -5522.136798 -5522.136798 -5516.076156 -5525.080218 \n", + "211388201 -2761.648763 -5543.048131 -5543.048131 -2769.692185 -2773.867648 \n", + "211388205 -2761.294814 -5546.132585 -5546.132585 -2764.829835 -2774.132982 \n", + "806388401 -2757.559519 -5540.685990 -5540.685990 -5542.838278 -5546.780761 \n", + "806388405 -2757.571454 -5542.673358 -5542.673358 -5537.799228 -5547.515590 \n", + "\n", + " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", + "trip_id \n", + "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", + "137248725 -inf -inf -24.341318 -25.104291 -24.140831 -8.406870 \n", + "211388201 -inf 4.786459 -15.861515 -3.864497 -5.068360 -1.296757 \n", + "211388205 -inf 5.048078 -15.776424 -3.845077 -5.005482 -1.286094 \n", + "806388401 -inf 6.304817 -19.143696 -19.495908 -16.361074 -5.853917 \n", + "806388405 -inf 6.319213 -18.886576 -19.495133 -16.300395 -5.828540 \n", + "\n", + " root \n", + "trip_id \n", + "137248721 11.435800 \n", + "137248725 11.480440 \n", + "211388201 4.789158 \n", + "211388205 5.050171 \n", + "806388401 6.451457 \n", + "806388405 6.446188 " + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# root is logsum here, no need to take log again as for exponentiated utils\n", + "simulate.compute_nested_exp_utilities(raw_utilities, nest_spec, exponentiate=False)" + ] + }, + { + "cell_type": "markdown", + "id": "5c9ba018-167d-497f-ac65-f7c49036ee58", + "metadata": {}, + "source": [ + "#### scale of utils, including emus" + ] + }, + { + "cell_type": "code", + "execution_count": 283, + "id": "9febf6ef-7c6b-4f3c-bdd9-aa3326a82e95", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T23:23:00.020038Z", + "iopub.status.busy": "2021-08-31T23:23:00.019377Z", + "iopub.status.idle": "2021-08-31T23:23:00.220889Z", + "shell.execute_reply": "2021-08-31T23:23:00.220154Z", + "shell.execute_reply.started": "2021-08-31T23:23:00.019997Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "nests_ = list(logit.each_nest(nest_spec))" + ] + }, + { + "cell_type": "code", + "execution_count": 284, + "id": "48ff920e-0c9b-4b69-9cee-eb3c3427bd4f", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T23:23:01.085713Z", + "iopub.status.busy": "2021-08-31T23:23:01.085486Z", + "iopub.status.idle": "2021-08-31T23:23:01.283509Z", + "shell.execute_reply": "2021-08-31T23:23:01.282444Z", + "shell.execute_reply.started": "2021-08-31T23:23:01.085685Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] alternatives: ['DRIVEALONE', 'SHAREDRIDE2', 'SHAREDRIDE3']\n", + "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] alternatives: ['DRIVEALONEFREE', 'DRIVEALONEPAY']\n", + "Nest name: DRIVEALONEFREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE'] alternatives: None\n", + "Nest name: DRIVEALONEPAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY'] alternatives: None\n" + ] + } + ], + "source": [ + "#list(map(lambda x: x.print(), nests_));\n", + "# have a look at \n", + "nests_[1].print()\n", + "nests_[2].print()\n", + "nests_[3].print()\n", + "nests_[4].print()" + ] + }, + { + "cell_type": "code", + "execution_count": 312, + "id": "7b7e453d-da45-4751-8889-561a349bd373", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:22.371464Z", + "iopub.status.busy": "2021-09-01T01:11:22.371192Z", + "iopub.status.idle": "2021-09-01T01:11:22.593424Z", + "shell.execute_reply": "2021-09-01T01:11:22.592472Z", + "shell.execute_reply.started": "2021-09-01T01:11:22.371415Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation belowi s equivalent.\n", + "def compute_nested_utilities(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) / nest.product_of_coefficients #coefficient\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", + " return nested_utilities\n", + "\n", + "\n", + "utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", + "\n", + "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", + "seed = 9326543345\n", + "rng = default_rng(seed=seed)\n", + "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", + "rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", + "\n", + "probs_arr = utils_df - np.log(-np.log(rands))\n", + "\n", + "# Now replace the following with recursive nested choice\n", + "# choices = np.argmax(probs_arr, axis=1)\n", + "# choices = pd.Series(choices, index=utils_df.index)\n", + "# rands = pd.Series(np.asanyarray(rands).flatten(), index=utils_df.index)" + ] + }, + { + "cell_type": "code", + "execution_count": 313, + "id": "39b71df9-12ac-4d9c-8134-43d22f7e6a6f", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:23.703299Z", + "iopub.status.busy": "2021-09-01T01:11:23.703088Z", + "iopub.status.idle": "2021-09-01T01:11:23.937077Z", + "shell.execute_reply": "2021-09-01T01:11:23.935627Z", + "shell.execute_reply.started": "2021-09-01T01:11:23.703274Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.79848-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.36027-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.4358
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", + "trip_id \n", + "137248721 -1.79848 -3966.084195 -0.629468 -0.389611 \n", + "\n", + " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", + "trip_id \n", + "137248721 -3964.675326 -0.136364 -3966.074556 -7930.36027 -inf \n", + "\n", + " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", + "trip_id \n", + "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", + "\n", + " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", + "trip_id \n", + "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", + "\n", + " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", + "trip_id \n", + "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", + "\n", + " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", + "trip_id \n", + "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", + "\n", + " root \n", + "trip_id \n", + "137248721 11.4358 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-0.717963-3964.2474012.7217010.230429-3964.716506-0.274584-3965.126773-7930.610173-inf1.73337615.473633-1368.52552614.717027-2736.517471-5510.738449-5512.038516-5512.559241-5511.584293-inf-2738.143234-5518.606969-5519.009587-5519.830735-5521.525966-inf-inf-25.519792-21.698963-25.267185-7.44465211.689443
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", + "trip_id \n", + "137248721 -0.717963 -3964.247401 2.721701 0.230429 \n", + "\n", + " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", + "trip_id \n", + "137248721 -3964.716506 -0.274584 -3965.126773 -7930.610173 -inf \n", + "\n", + " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", + "trip_id \n", + "137248721 1.733376 15.473633 -1368.525526 14.717027 -2736.517471 \n", + "\n", + " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", + "trip_id \n", + "137248721 -5510.738449 -5512.038516 -5512.559241 -5511.584293 -inf \n", + "\n", + " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", + "trip_id \n", + "137248721 -2738.143234 -5518.606969 -5519.009587 -5519.830735 -5521.525966 \n", + "\n", + " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", + "trip_id \n", + "137248721 -inf -inf -25.519792 -21.698963 -25.267185 -7.444652 \n", + "\n", + " root \n", + "trip_id \n", + "137248721 11.689443 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(utils_df.head(1), probs_arr.head(1))" + ] + }, + { + "cell_type": "markdown", + "id": "2f080150-c15a-4059-8a26-42a8c0072606", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T23:16:40.014024Z", + "iopub.status.busy": "2021-08-31T23:16:40.013770Z", + "iopub.status.idle": "2021-08-31T23:16:40.236326Z", + "shell.execute_reply": "2021-08-31T23:16:40.235553Z", + "shell.execute_reply.started": "2021-08-31T23:16:40.013973Z" + }, + "tags": [] + }, + "source": [ + "### make choice at each level" + ] + }, + { + "cell_type": "code", + "execution_count": 315, + "id": "2ed71b54-67e4-4087-957a-75f4fa184144", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:43.778081Z", + "iopub.status.busy": "2021-09-01T01:11:43.777757Z", + "iopub.status.idle": "2021-09-01T01:11:43.975332Z", + "shell.execute_reply": "2021-09-01T01:11:43.974588Z", + "shell.execute_reply.started": "2021-09-01T01:11:43.778055Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def get_alternatives(nests, name):\n", + " alts = list(filter(lambda x: x.name == name, nests))\n", + " assert len(alts) == 1, f\"{len(alts)} not one\"\n", + " alts = alts[0].alternatives\n", + " return alts\n", + "\n", + "def recursive_choice(row, columns, nest_levels, nests):\n", + " choices = row[columns].idxmax() #axis=1).values[0]\n", + " next_level_columns = get_alternatives(nests, choices)\n", + " #print(f\"{choices} leads to columns {next_level_columns}\")\n", + " if next_level_columns is None:\n", + " return choices \n", + " new_choice = recursive_choice(row, next_level_columns, nest_levels, nests)\n", + " return new_choice" + ] + }, + { + "cell_type": "code", + "execution_count": 316, + "id": "30424562-2627-446b-abd5-7c763c52060e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:49.444289Z", + "iopub.status.busy": "2021-09-01T01:11:49.444002Z", + "iopub.status.idle": "2021-09-01T01:11:49.664214Z", + "shell.execute_reply": "2021-09-01T01:11:49.663088Z", + "shell.execute_reply.started": "2021-09-01T01:11:49.444266Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n" + ] + } + ], + "source": [ + "# group nests by level:\n", + "depth = np.max([x.level for x in nests_])\n", + "print(depth)\n", + "nest_levels = {x: [] for x in range(1, depth+1)}\n", + "for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + "assert len(nest_levels[1]) == 1\n", + "assert nest_levels[1][0] == 'root'" + ] + }, + { + "cell_type": "code", + "execution_count": 326, + "id": "bae6e7a8-d7c3-42a2-a82f-874b1e59a0b6", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:56:38.377472Z", + "iopub.status.busy": "2021-09-01T01:56:38.377253Z", + "iopub.status.idle": "2021-09-01T01:56:38.607858Z", + "shell.execute_reply": "2021-09-01T01:56:38.606913Z", + "shell.execute_reply.started": "2021-09-01T01:56:38.377447Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 WALK\n", + "137248725 WALK\n", + "211388201 WALK_LOC\n", + "211388205 WALK_LOC\n", + "806388401 WALK\n", + "806388405 WALK\n", + "dtype: object" + ] + }, + "execution_count": 326, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests_), axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 321, + "id": "fc315f63-542f-46fd-8d70-b7a1fcd492c6", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:53:15.500933Z", + "iopub.status.busy": "2021-09-01T01:53:15.500709Z", + "iopub.status.idle": "2021-09-01T01:53:15.703201Z", + "shell.execute_reply": "2021-09-01T01:53:15.702357Z", + "shell.execute_reply.started": "2021-09-01T01:53:15.500909Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#display(utils_df, probs_arr)" + ] + }, + { + "cell_type": "markdown", + "id": "8a45e1c0-0a32-4f7a-8803-7559d71abefd", + "metadata": {}, + "source": [ + "### some tests\n", + "seeds, average?" + ] + }, + { + "cell_type": "code", + "execution_count": 333, + "id": "f0e00814-2a14-4da1-bc6c-89b52b7fb286", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:58:26.002425Z", + "iopub.status.busy": "2021-09-01T01:58:26.002200Z", + "iopub.status.idle": "2021-09-01T01:58:26.247954Z", + "shell.execute_reply": "2021-09-01T01:58:26.247054Z", + "shell.execute_reply.started": "2021-09-01T01:58:26.002401Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 WALK\n", + "137248725 WALK\n", + "211388201 WALK_LOC\n", + "211388205 WALK_LOC\n", + "806388401 WALK\n", + "806388405 WALK\n", + "dtype: object" + ] + }, + "execution_count": 333, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", + "seed = 9326543345\n", + "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", + "\n", + "def make_choice(utils_df, nests, nest_levels, seed=None):\n", + " rng = default_rng(seed=seed)\n", + " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", + " probs_arr = utils_df - np.log(-np.log(rands))\n", + " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", + " return choices\n", + " \n", + "make_choice(utils_df, nests_, nest_levels, seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 348, + "id": "bca659db-4c88-4287-8f5e-9c9516e3ef41", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:59:00.410062Z", + "iopub.status.busy": "2021-09-01T01:59:00.409811Z", + "iopub.status.idle": "2021-09-01T01:59:00.621940Z", + "shell.execute_reply": "2021-09-01T01:59:00.621156Z", + "shell.execute_reply.started": "2021-09-01T01:59:00.410037Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 WALK\n", + "137248725 WALK\n", + "211388201 WALK_LOC\n", + "211388205 WALK_LOC\n", + "806388401 WALK_LOC\n", + "806388405 WALK_LOC\n", + "dtype: object" + ] + }, + "execution_count": 348, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "make_choice(utils_df, nests_, nest_levels)" + ] + }, + { + "cell_type": "code", + "execution_count": 453, + "id": "806ce752-0927-4d5b-a6cc-68d6c9b8a05e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:55:59.542669Z", + "iopub.status.busy": "2021-09-01T04:55:59.542360Z", + "iopub.status.idle": "2021-09-01T04:55:59.771722Z", + "shell.execute_reply": "2021-09-01T04:55:59.770801Z", + "shell.execute_reply.started": "2021-09-01T04:55:59.542615Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + " nested_probabilities = \\\n", + " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", + " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) \n", + " return base_probabilities\n", + "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 451, + "id": "054fd9b1-72fc-49cc-a790-48ef75bcbaed", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:55:08.260140Z", + "iopub.status.busy": "2021-09-01T04:55:08.259914Z", + "iopub.status.idle": "2021-09-01T04:55:09.140095Z", + "shell.execute_reply": "2021-09-01T04:55:09.139119Z", + "shell.execute_reply.started": "2021-09-01T04:55:08.260116Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "86627409 WALK_LRF\n", + "86627413 WALK_LRF\n", + "86673657 WALK_LOC\n", + "86673658 WALK\n", + "86673659 WALK\n", + " ... \n", + "2464446025 WALK\n", + "2464446029 WALK\n", + "2464449633 WALK\n", + "2464449634 WALK\n", + "2464449637 WALK\n", + "Length: 168, dtype: object" + ] + }, + "execution_count": 451, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def simple_simulate_rum(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", + "\n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices\n", + "\n", + "simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" + ] + }, + { + "cell_type": "code", + "execution_count": 380, + "id": "2da81b5e-7992-4437-8e0f-905caf026804", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:33:10.886502Z", + "iopub.status.busy": "2021-09-01T02:33:10.886252Z", + "iopub.status.idle": "2021-09-01T02:33:11.092145Z", + "shell.execute_reply": "2021-09-01T02:33:11.090725Z", + "shell.execute_reply.started": "2021-09-01T02:33:10.886465Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'trip_mode_choice'" + ] + }, + "execution_count": 380, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trace_label" + ] + }, + { + "cell_type": "code", + "execution_count": 393, + "id": "f3c5c1ba-e9c3-4511-98bf-9ac98c987da7", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:39:52.539983Z", + "iopub.status.busy": "2021-09-01T02:39:52.539668Z", + "iopub.status.idle": "2021-09-01T02:39:52.796170Z", + "shell.execute_reply": "2021-09-01T02:39:52.795498Z", + "shell.execute_reply.started": "2021-09-01T02:39:52.539944Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " segment_trace_label, log_alt_losers=log_alt_losers, \n", + " trace_column_names=trace_column_names, seed=seed)\n", + "\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " choices_list.append(choices)\n", + "\n", + " choices_df = pd.concat(choices_list)\n", + " return choices_df" + ] + }, + { + "cell_type": "code", + "execution_count": 454, + "id": "74eeedd3-4ade-4729-8170-79fa9dcf11f8", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:58:52.825042Z", + "iopub.status.busy": "2021-09-01T04:58:52.824768Z", + "iopub.status.idle": "2021-09-01T04:58:53.042884Z", + "shell.execute_reply": "2021-09-01T04:58:53.042061Z", + "shell.execute_reply.started": "2021-09-01T04:58:52.825004Z" + } + }, + "outputs": [], + "source": [ + "## caculate probabilities with Asim methodology, should be correct\n", + "def gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None):\n", + " full_probs = [] # analytical probs\n", + "\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " #choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " # segment_trace_label, log_alt_losers=log_alt_losers, \n", + " # trace_column_names=trace_column_names, seed=seed)\n", + " probs = simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, \n", + " estimator, segment_trace_label, log_alt_losers, \n", + " trace_column_names)\n", + " full_probs.append(probs)\n", + " probs_df = pd.concat(full_probs)\n", + " return probs_df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 455, + "id": "295a4fd1-8e85-47e2-af82-9f1beac31d63", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:59:46.126555Z", + "iopub.status.busy": "2021-09-01T04:59:46.126262Z", + "iopub.status.idle": "2021-09-01T04:59:52.979908Z", + "shell.execute_reply": "2021-09-01T04:59:52.979043Z", + "shell.execute_reply.started": "2021-09-01T04:59:46.126522Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2021-09-01 14:59:46.353007 Start\n", + "2021-09-01 14:59:52.976736 End\n" + ] + } + ], + "source": [ + "print(f\"{datetime.now()} Start\")\n", + "pipeline.open_pipeline(resume_after)\n", + "inject.get_injectable('preload_injectables', None)\n", + "model_name = \"trip_mode_choice\"\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "step_name = model_name\n", + "args = {}\n", + "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "chunk_size = 0\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "trips_df = trips.to_frame()\n", + "#print(\"Running with %d trips\", trips_df.shape[0])\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + "orig_col = 'origin'\n", + "dest_col = 'destination'\n", + "\n", + "constants = {}\n", + "constants.update(config.get_model_constants(model_settings))\n", + "constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + "})\n", + "\n", + "skim_dict = network_los.get_default_skim_dict()\n", + "\n", + "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + "skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + "}\n", + "\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "\n", + "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + "\n", + "all_choices = []\n", + "for i in range(100):\n", + " if i % 10 == 0:\n", + " print(f\"{datetime.now()} iteration {i}\")\n", + " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=None)\n", + " all_choices.append(choices_df)\n", + "all_choices = pd.concat(all_choices, axis=1)\n", + "\n", + "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", + " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", + " log_alt_losers=None, trace_column_names=None)\n", + "\n", + "# update trips table with choices (and potionally logssums)\n", + "#trips_df = trips.to_frame()\n", + "#\n", + "#assign_in_place(trips_df, choices_df)\n", + "#assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + "print(f\"{datetime.now()} End\")" + ] + }, + { + "cell_type": "code", + "execution_count": 463, + "id": "dc87e904-16d1-44d4-81b7-cafbe4e223c3", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:01:40.901982Z", + "iopub.status.busy": "2021-09-01T05:01:40.901759Z", + "iopub.status.idle": "2021-09-01T05:01:41.337758Z", + "shell.execute_reply": "2021-09-01T05:01:41.336757Z", + "shell.execute_reply.started": "2021-09-01T05:01:40.901946Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#all_choices.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", + "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts = val_counts / all_choices.shape[1]\n", + "#val_counts = val_counts.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", + "#val_counts['prob_of_asim_choice'] = val_counts.apply(lambda x: x[x.asim], axis=1) # this is what our simulation says w.r.t. to asim choice\n", + "# for 100% and many samples should mostly agree" + ] + }, + { + "cell_type": "code", + "execution_count": 464, + "id": "09bf151c-a39a-4823-acfd-9c8ae747c338", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:01:46.119117Z", + "iopub.status.busy": "2021-09-01T05:01:46.118849Z", + "iopub.status.idle": "2021-09-01T05:01:46.354114Z", + "shell.execute_reply": "2021-09-01T05:01:46.353248Z", + "shell.execute_reply.started": "2021-09-01T05:01:46.119092Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#val_counts['prob_of_asim_choice'].hist(bins=100);" + ] + }, + { + "cell_type": "code", + "execution_count": 490, + "id": "f3b6ade9-d875-4104-b438-b53ef42e342c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:10:08.653227Z", + "iopub.status.busy": "2021-09-01T05:10:08.652978Z", + "iopub.status.idle": "2021-09-01T05:10:08.915620Z", + "shell.execute_reply": "2021-09-01T05:10:08.914513Z", + "shell.execute_reply.started": "2021-09-01T05:10:08.653189Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", + "analytical 3.152 0.852 0.0 0.0 0.0 \n", + "experiment 3.243 0.880 0.0 0.0 0.0 \n", + "\n", + " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", + "analytical 0.0 0.0 0.0 0.685 0.0 \n", + "experiment 0.0 0.0 0.0 0.772 0.0 \n", + "\n", + " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK \\\n", + "analytical 0.129 0.0 0.182 0.260 1.334 63.708 \n", + "experiment 0.156 0.0 0.174 0.259 1.434 63.243 \n", + "\n", + " WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", + "analytical 0.0 0.0 0.455 18.355 10.887 \n", + "experiment 0.0 0.0 0.394 18.639 10.807 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mode_share_analytical = (probs_nl.sum(axis=0) / probs_nl.shape[0]).to_frame('analytical')\n", + "assert np.allclose(mode_share_analytical.sum(), 1)\n", + "mode_share_rum = (val_counts.sum(axis=0) / val_counts.shape[0]).to_frame('experiment')\n", + "assert np.allclose(mode_share_rum.sum(), 1)\n", + "full_share = mode_share_analytical.join(mode_share_rum, how='outer').fillna(0)\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * full_share).T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e48a56e-d613-4dfd-bef6-62bee524f12f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50914e1d-750d-440c-9617-f96bd2a46c56", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2d70e0e-520e-49a6-8024-ae31345f6ead", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f9e8ef02-1d57-4b21-8aef-25a6e1095c02", + "metadata": {}, + "source": [ + "### try zenith normalisation of simple_simulate_rum" + ] + }, + { + "cell_type": "code", + "execution_count": 629, + "id": "db235e90-d2e5-4e90-b41c-6d90a670e41d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:24.045881Z", + "iopub.status.busy": "2021-09-01T11:37:24.045637Z", + "iopub.status.idle": "2021-09-01T11:37:24.276488Z", + "shell.execute_reply": "2021-09-01T11:37:24.274898Z", + "shell.execute_reply.started": "2021-09-01T11:37:24.045844Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation belowi s equivalent.\n", + "def compute_nested_utilities_zenith(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) # / nest.product_of_coefficients #coefficient\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", + " \n", + " # now go over all leaves and correct for scale\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] /= nest.coefficient\n", + " \n", + " return nested_utilities\n", + "\n", + "\n", + "def simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " utils_df = compute_nested_utilities_zenith(raw_utilities, nest_spec)\n", + "\n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices\n", + "\n", + "#simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" + ] + }, + { + "cell_type": "code", + "execution_count": 630, + "id": "f69bc3dc-84e4-4dd5-9a57-c053cadba4b5", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:24.828926Z", + "iopub.status.busy": "2021-09-01T11:37:24.828707Z", + "iopub.status.idle": "2021-09-01T11:37:25.046409Z", + "shell.execute_reply": "2021-09-01T11:37:25.045399Z", + "shell.execute_reply.started": "2021-09-01T11:37:24.828902Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " segment_trace_label, log_alt_losers=log_alt_losers, \n", + " trace_column_names=trace_column_names, seed=seed)\n", + "\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " choices_list.append(choices)\n", + "\n", + " choices_df = pd.concat(choices_list)\n", + " return choices_df" + ] + }, + { + "cell_type": "code", + "execution_count": 631, + "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:32.250808Z", + "iopub.status.busy": "2021-09-01T11:37:32.250548Z", + "iopub.status.idle": "2021-09-01T20:20:35.257463Z", + "shell.execute_reply": "2021-09-01T20:20:35.255964Z", + "shell.execute_reply.started": "2021-09-01T11:37:32.250782Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "num_samples = 10\n", + "\n", + "rng_test = default_rng(23423)\n", + "\n", + "print(f\"{datetime.now()} Start\")\n", + "pipeline.open_pipeline(resume_after)\n", + "inject.get_injectable('preload_injectables', None)\n", + "model_name = \"trip_mode_choice\"\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "step_name = model_name\n", + "args = {}\n", + "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "chunk_size = 0\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "trips_df = trips.to_frame()\n", + "#print(\"Running with %d trips\", trips_df.shape[0])\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + "orig_col = 'origin'\n", + "dest_col = 'destination'\n", + "\n", + "constants = {}\n", + "constants.update(config.get_model_constants(model_settings))\n", + "constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + "})\n", + "\n", + "skim_dict = network_los.get_default_skim_dict()\n", + "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + "}\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + "all_choices_zenith = []\n", + "all_choices = []\n", + "for i in range(num_samples):\n", + " \n", + " seed = rng_test.integers(0, 100000) #int(9.3 * (i+1)**3) # why not\n", + " \n", + " if i % 50 == 0:\n", + " print(f\"{datetime.now()} iteration {i}\")\n", + " choices_df_zenith = stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", + " all_choices_zenith.append(choices_df_zenith)\n", + "\n", + " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", + " all_choices.append(choices_df)\n", + " \n", + " t_ = choices_df_zenith.merge(choices_df, left_index=True, right_index=True, suffixes=['_zenith', '_asim'])\n", + " diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", + " \n", + " #print(f\"seed {seed} leads to {diffs.shape[0]} differences. tripids {diffs.index}\")\n", + "\n", + "all_choices_zenith = pd.concat(all_choices_zenith, axis=1)\n", + "all_choices = pd.concat(all_choices, axis=1)\n", + "\n", + "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", + " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", + " log_alt_losers=None, trace_column_names=None)\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + "print(f\"{datetime.now()} End\")" + ] + }, + { + "cell_type": "code", + "execution_count": 643, + "id": "3abd7949-dc9c-469b-845e-26c17741f70e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:33:21.533110Z", + "iopub.status.busy": "2021-09-01T20:33:21.531915Z", + "iopub.status.idle": "2021-09-01T20:33:21.740946Z", + "shell.execute_reply": "2021-09-01T20:33:21.739713Z", + "shell.execute_reply.started": "2021-09-01T20:33:21.533074Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Have 2038 samples\n" + ] + } + ], + "source": [ + "print(f\"Have {all_choices_zenith.shape[1]} samples\")" + ] + }, + { + "cell_type": "code", + "execution_count": 633, + "id": "8762bd6d-ac04-458c-869f-be86d8297351", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:21:27.778803Z", + "iopub.status.busy": "2021-09-01T20:21:27.778531Z", + "iopub.status.idle": "2021-09-01T20:21:28.675645Z", + "shell.execute_reply": "2021-09-01T20:21:28.674734Z", + "shell.execute_reply.started": "2021-09-01T20:21:27.778754Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "val_counts_zenith = all_choices_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts_zenith = val_counts_zenith / all_choices_zenith.shape[1]\n", + "\n", + "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts = val_counts / all_choices.shape[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 634, + "id": "0f566105-9424-457b-86f5-d60c1d63aac5", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:21:30.954894Z", + "iopub.status.busy": "2021-09-01T20:21:30.954654Z", + "iopub.status.idle": "2021-09-01T20:21:31.179689Z", + "shell.execute_reply": "2021-09-01T20:21:31.178304Z", + "shell.execute_reply.started": "2021-09-01T20:21:30.954867Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
experiment_zenith3.1500.8740.00.00.00.00.00.00.6800.00.1360.00.1540.2361.28563.7670.00.00.51518.82410.379
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", + "analytical 3.152 0.852 0.0 0.0 0.0 \n", + "experiment 3.243 0.880 0.0 0.0 0.0 \n", + "experiment_zenith 3.150 0.874 0.0 0.0 0.0 \n", + "\n", + " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", + "analytical 0.0 0.0 0.0 0.685 0.0 \n", + "experiment 0.0 0.0 0.0 0.772 0.0 \n", + "experiment_zenith 0.0 0.0 0.0 0.680 0.0 \n", + "\n", + " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE \\\n", + "analytical 0.129 0.0 0.182 0.260 1.334 \n", + "experiment 0.156 0.0 0.174 0.259 1.434 \n", + "experiment_zenith 0.136 0.0 0.154 0.236 1.285 \n", + "\n", + " WALK WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", + "analytical 63.708 0.0 0.0 0.455 18.355 10.887 \n", + "experiment 63.243 0.0 0.0 0.394 18.639 10.807 \n", + "experiment_zenith 63.767 0.0 0.0 0.515 18.824 10.379 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mode_share_rum_zenith = (val_counts_zenith.sum(axis=0) / val_counts_zenith.shape[0]).to_frame('experiment_zenith')\n", + "assert np.allclose(mode_share_rum_zenith.sum(), 1)\n", + "full_share_incl_zenith = full_share.merge(mode_share_rum_zenith, left_index=True, right_index=True, how='outer').fillna(0)\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * full_share_incl_zenith).T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c0d3081-b45b-4573-9246-e4a9d39591dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "612b2f04-45e2-4eb2-b52a-9f753427877f", + "metadata": {}, + "source": [ + "## investigate diverging seed and look at diff in formulation" + ] + }, + { + "cell_type": "code", + "execution_count": 619, + "id": "ac09dc75-a92f-43fb-a0cd-fe7de1f8b66d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:12:02.988141Z", + "iopub.status.busy": "2021-09-01T11:12:02.987900Z", + "iopub.status.idle": "2021-09-01T11:12:03.212179Z", + "shell.execute_reply": "2021-09-01T11:12:03.210993Z", + "shell.execute_reply.started": "2021-09-01T11:12:02.988102Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation based on asim probabilities is equivalent\n", + "def compute_nested_utilities_asim(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) / nest.product_of_coefficients\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", + " return nested_utilities\n", + "\n", + "def compute_nested_utilities_zenith_check(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) # scale correction is below\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", + " \n", + " # now go over all leaves and correct for scale\n", + " for nest in logit.each_nest(nest_spec):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] /= nest.coefficient\n", + " \n", + " return nested_utilities" + ] + }, + { + "cell_type": "code", + "execution_count": 688, + "id": "4b7c6368-21ca-4442-969e-f2f8ff868842", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:49:56.958862Z", + "iopub.status.busy": "2021-09-01T20:49:56.958623Z", + "iopub.status.idle": "2021-09-01T20:49:57.191884Z", + "shell.execute_reply": "2021-09-01T20:49:57.190975Z", + "shell.execute_reply.started": "2021-09-01T20:49:56.958827Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None, use_zenith=True, raw_utilities=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " \n", + " if raw_utilities is None:\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " if use_zenith:\n", + " utils_df = compute_nested_utilities_zenith_check(raw_utilities, nest_spec)\n", + " else:\n", + " utils_df = compute_nested_utilities_asim(raw_utilities, nest_spec)\n", + "\n", + " # test\n", + " #return compute_nested_utilities_zenith_check(raw_utilities, nest_spec), compute_nested_utilities_asim(raw_utilities, nest_spec)\n", + " \n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices" + ] + }, + { + "cell_type": "code", + "execution_count": 626, + "id": "bd6cadc3-79f5-4824-b55e-639c39d5ab18", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:13:12.077886Z", + "iopub.status.busy": "2021-09-01T11:13:12.077652Z", + "iopub.status.idle": "2021-09-01T11:13:12.290222Z", + "shell.execute_reply": "2021-09-01T11:13:12.288861Z", + "shell.execute_reply.started": "2021-09-01T11:13:12.077863Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# t_z, t_a = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed)\n", + "# tr_id = 86673661\n", + "# display(t_z.loc[t_z.index==tr_id])\n", + "# display(t_a.loc[t_a.index==tr_id])" + ] + }, + { + "cell_type": "code", + "execution_count": 625, + "id": "42e6da62-58e8-4b16-9169-9faa1fc8a162", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:13:06.847583Z", + "iopub.status.busy": "2021-09-01T11:13:06.847204Z", + "iopub.status.idle": "2021-09-01T11:13:08.401700Z", + "shell.execute_reply": "2021-09-01T11:13:08.400711Z", + "shell.execute_reply.started": "2021-09-01T11:13:06.847554Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "seed 51006 leads to 34 differences out of 168. tripids Int64Index([ 86673657, 86673661, 106741681, 106741682, 106741685,\n", + " 106741686, 106741687, 106741688, 211327433, 211327437,\n", + " 444793574, 484173905, 484173909, 535170694, 535620053,\n", + " 708171014, 943749470, 943749471, 1060575853, 1091770617,\n", + " 1146472489, 1146472493, 1276281769, 1276281773, 1658748793,\n", + " 1658748797, 1767013726, 1767186577, 1767186578, 1768237161,\n", + " 1768237165, 1768237166, 2463663417, 2463663421],\n", + " dtype='int64', name='trip_id')\n" + ] + } + ], + "source": [ + "seed = 51006\n", + "x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True)\n", + "y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False)\n", + "t_ = x_.to_frame('trip_mode_zenith').merge(y_.to_frame('trip_mode_asim'), left_index=True, right_index=True)\n", + "diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", + "print(f\"seed {seed} leads to {diffs.shape[0]} differences out of {t_.shape[0]}. tripids {diffs.index}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f3c0506-a79d-4a31-9d56-71b9966d0a92", + "metadata": {}, + "outputs": [], + "source": [ + "seed 51006 leads to 3 differences. tripids Int64Index([86673661, 535170689, 1060575849], dtype='int64', name='trip_id')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a5f332a-b1d7-4a80-bb21-d37a1611cf9f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24c5e4a1-6142-49d4-8da8-a1e266b3cb9b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 753, + "id": "2098907f-28af-4f53-af74-6aaee8a6c53c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T23:04:23.861048Z", + "iopub.status.busy": "2021-09-01T23:04:23.860748Z", + "iopub.status.idle": "2021-09-01T23:04:24.069307Z", + "shell.execute_reply": "2021-09-01T23:04:24.068434Z", + "shell.execute_reply.started": "2021-09-01T23:04:23.861024Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "rng_ = default_rng(seed=100)\n", + "new_utils = pd.DataFrame(0.1 * -np.log(-np.log(rng_.uniform(0,1,raw_utilities.shape))), columns=raw_utilities.columns)\n", + "new_utils.index = raw_utilities.index" + ] + }, + { + "cell_type": "code", + "execution_count": 767, + "id": "da48a178-82b2-44a5-b45a-e9de3bb5e5c2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T23:17:20.369675Z", + "iopub.status.busy": "2021-09-01T23:17:20.369424Z", + "iopub.status.idle": "2021-09-01T23:17:20.666506Z", + "shell.execute_reply": "2021-09-01T23:17:20.665626Z", + "shell.execute_reply.started": "2021-09-01T23:17:20.369637Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
1372487210.0566420.0373050.0427800.0295760.0861540.0265860.0883130.1003310.0280210.0185160.0643910.0200090.0263990.0226220.0230070.0217540.0871260.0211710.0364750.0276500.135171
1372487250.1019020.0228760.0312270.0699900.0569130.0466850.0884110.0877850.0254140.0381620.0239880.0288000.0393180.0201600.0483640.0230030.0339590.0345970.0377080.0995230.041213
2113882010.0375620.0700330.0559510.0403890.0200410.0959740.0946780.0943680.0360140.0250510.0348600.0349530.0354990.0280400.0436500.0247130.0313880.0259310.0669990.0526780.051228
2113882050.0354800.0562560.0439440.0480850.0420780.0622210.1068190.0872650.0351170.0303990.0351540.0264690.0506460.0357270.0242840.0329050.0394270.0410920.0547140.0484590.063458
8063884010.0549180.0404880.0331490.0542940.0434770.0679820.1524650.0741520.0334040.0287350.0592690.0243390.0369680.0277600.0235220.0609320.0234450.0239310.0446430.0337730.058353
8063884050.0327640.0573010.0184370.0770990.0469630.0635210.0949510.0890720.0308260.0378960.0357520.0198780.0495360.0796100.0269220.0248330.0248400.0266950.0799360.0401460.043022
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n", + "trip_id \n", + "137248721 0.056642 0.037305 0.042780 0.029576 \n", + "137248725 0.101902 0.022876 0.031227 0.069990 \n", + "211388201 0.037562 0.070033 0.055951 0.040389 \n", + "211388205 0.035480 0.056256 0.043944 0.048085 \n", + "806388401 0.054918 0.040488 0.033149 0.054294 \n", + "806388405 0.032764 0.057301 0.018437 0.077099 \n", + "\n", + " SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\n", + "trip_id \n", + "137248721 0.086154 0.026586 0.088313 0.100331 0.028021 0.018516 \n", + "137248725 0.056913 0.046685 0.088411 0.087785 0.025414 0.038162 \n", + "211388201 0.020041 0.095974 0.094678 0.094368 0.036014 0.025051 \n", + "211388205 0.042078 0.062221 0.106819 0.087265 0.035117 0.030399 \n", + "806388401 0.043477 0.067982 0.152465 0.074152 0.033404 0.028735 \n", + "806388405 0.046963 0.063521 0.094951 0.089072 0.030826 0.037896 \n", + "\n", + " WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\n", + "trip_id \n", + "137248721 0.064391 0.020009 0.026399 0.022622 0.023007 0.021754 \n", + "137248725 0.023988 0.028800 0.039318 0.020160 0.048364 0.023003 \n", + "211388201 0.034860 0.034953 0.035499 0.028040 0.043650 0.024713 \n", + "211388205 0.035154 0.026469 0.050646 0.035727 0.024284 0.032905 \n", + "806388401 0.059269 0.024339 0.036968 0.027760 0.023522 0.060932 \n", + "806388405 0.035752 0.019878 0.049536 0.079610 0.026922 0.024833 \n", + "\n", + " DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n", + "trip_id \n", + "137248721 0.087126 0.021171 0.036475 0.027650 0.135171 \n", + "137248725 0.033959 0.034597 0.037708 0.099523 0.041213 \n", + "211388201 0.031388 0.025931 0.066999 0.052678 0.051228 \n", + "211388205 0.039427 0.041092 0.054714 0.048459 0.063458 \n", + "806388401 0.023445 0.023931 0.044643 0.033773 0.058353 \n", + "806388405 0.024840 0.026695 0.079936 0.040146 0.043022 " + ] + }, + "execution_count": 767, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nested_exp_utilities_cf = simulate.compute_nested_exp_utilities(new_utils, nest_spec)\n", + "nested_probabilities_cf = simulate.compute_nested_probabilities(nested_exp_utilities_cf, nest_spec, trace_label=None)\n", + "base_probabilities_cf = simulate.compute_base_probabilities(nested_probabilities_cf, nest_spec, spec)\n", + "base_probabilities_cf" + ] + }, + { + "cell_type": "code", + "execution_count": 799, + "id": "50dfd1ae-10c3-475c-94be-de783c2fa5c2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:15:29.056902Z", + "iopub.status.busy": "2021-09-02T00:15:29.056683Z", + "iopub.status.idle": "2021-09-02T00:27:21.965206Z", + "shell.execute_reply": "2021-09-02T00:27:21.964340Z", + "shell.execute_reply.started": "2021-09-02T00:15:29.056877Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 11min 49s, sys: 766 ms, total: 11min 50s\n", + "Wall time: 11min 52s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "num_samples = 10000 # 7.5s per 100\n", + "\n", + "data_zenith = []\n", + "data_asim = []\n", + "for i in range(num_samples):\n", + " seed = rng_.integers(0, 100000)\n", + " x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True, raw_utilities=new_utils)\n", + " y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False, raw_utilities=new_utils)\n", + " data_zenith.append(x_)\n", + " data_asim.append(y_)\n", + " \n", + "data_asim = pd.concat(data_asim, axis=1)\n", + "data_zenith = pd.concat(data_zenith, axis=1)\n", + "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", + "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_asim = counts_asim / data_asim.shape[1]\n", + "\n", + "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", + "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", + "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", + "# assert np.allclose(mode_share_zenith.sum(), 1)\n", + "# assert np.allclose(mode_share_asim.sum(), 1)\n", + "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", + "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 796, + "id": "bb16f9d7-44f5-4316-b981-d65216a5e217", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:14:11.772592Z", + "iopub.status.busy": "2021-09-02T00:14:11.772347Z", + "iopub.status.idle": "2021-09-02T00:14:12.001077Z", + "shell.execute_reply": "2021-09-02T00:14:12.000166Z", + "shell.execute_reply.started": "2021-09-02T00:14:11.772556Z" + } + }, + "outputs": [], + "source": [ + "#temp_z = data_zenith.copy()\n", + "#temp_a = data_asim.copy()\n", + "#data_asim = data_asim.join(temp_a, lsuffix=\"_o\", rsuffix=\"_n\")\n", + "#data_zenith = data_zenith.join(temp_z, lsuffix=\"_o\", rsuffix=\"_n\")\n", + "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", + "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_asim = counts_asim / data_asim.shape[1]\n", + "\n", + "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", + "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", + "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", + "# assert np.allclose(mode_share_zenith.sum(), 1)\n", + "# assert np.allclose(mode_share_asim.sum(), 1)\n", + "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", + "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 805, + "id": "031d8340-0af0-4608-8a83-ed2af4aaa3b2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:41:01.948669Z", + "iopub.status.busy": "2021-09-02T00:41:01.948422Z", + "iopub.status.idle": "2021-09-02T00:41:02.175972Z", + "shell.execute_reply": "2021-09-02T00:41:02.174759Z", + "shell.execute_reply.started": "2021-09-02T00:41:01.948633Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
zenith8.0555.1674.6313.5023.6064.2663.9633.4983.8955.1764.7995.8514.9996.1214.6689.4864.2284.1252.9773.6713.317
asim6.0966.6984.9773.2023.5224.3344.1293.1843.2245.1496.2838.2964.7936.5844.5327.4844.1524.2732.6293.4333.023
probs8.8835.3214.7382.8903.1364.0033.5653.1623.7585.3244.9276.0495.3416.5415.03710.4273.9734.2242.5743.1472.979
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP DRIVE_HVY \\\n", + "zenith 8.055 5.167 4.631 3.502 3.606 4.266 \n", + "asim 6.096 6.698 4.977 3.202 3.522 4.334 \n", + "probs 8.883 5.321 4.738 2.890 3.136 4.003 \n", + "\n", + " DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY SHARED3FREE \\\n", + "zenith 3.963 3.498 3.895 5.176 4.799 \n", + "asim 4.129 3.184 3.224 5.149 6.283 \n", + "probs 3.565 3.162 3.758 5.324 4.927 \n", + "\n", + " SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK WALK_COM WALK_EXP \\\n", + "zenith 5.851 4.999 6.121 4.668 9.486 4.228 4.125 \n", + "asim 8.296 4.793 6.584 4.532 7.484 4.152 4.273 \n", + "probs 6.049 5.341 6.541 5.037 10.427 3.973 4.224 \n", + "\n", + " WALK_HVY WALK_LOC WALK_LRF \n", + "zenith 2.977 3.671 3.317 \n", + "asim 2.629 3.433 3.023 \n", + "probs 2.574 3.147 2.979 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# sample of 20000\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * mode_share_comp).T)" + ] + }, + { + "cell_type": "code", + "execution_count": 806, + "id": "aece448f-4055-4d2a-b90d-8530caec9cb9", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:43:32.971972Z", + "iopub.status.busy": "2021-09-02T00:43:32.971684Z", + "iopub.status.idle": "2021-09-02T00:43:33.223345Z", + "shell.execute_reply": "2021-09-02T00:43:33.222488Z", + "shell.execute_reply.started": "2021-09-02T00:43:32.971932Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
trip_id137248721_z137248721_a137248721
BIKE0.092700.054350.100331
DRIVEALONEFREE0.053650.050050.056642
DRIVEALONEPAY0.038800.031550.037305
DRIVE_COM0.028700.024700.021171
DRIVE_EXP0.028800.026050.021754
DRIVE_HVY0.078150.099700.087126
DRIVE_LOC0.028750.027000.022622
DRIVE_LRF0.027800.024450.023007
SHARED2FREE0.043250.023000.042780
SHARED2PAY0.033600.015850.029576
SHARED3FREE0.073650.119200.086154
SHARED3PAY0.032600.039600.026586
TAXI0.035100.050300.036475
TNC_SHARED0.126200.181150.135171
TNC_SINGLE0.026250.038450.027650
WALK0.079400.048250.088313
WALK_COM0.031850.024600.026399
WALK_EXP0.057050.058950.064391
WALK_HVY0.025200.018400.020009
WALK_LOC0.034550.027400.028021
WALK_LRF0.023950.017000.018516
\n", + "
" + ], + "text/plain": [ + "trip_id 137248721_z 137248721_a 137248721\n", + "BIKE 0.09270 0.05435 0.100331\n", + "DRIVEALONEFREE 0.05365 0.05005 0.056642\n", + "DRIVEALONEPAY 0.03880 0.03155 0.037305\n", + "DRIVE_COM 0.02870 0.02470 0.021171\n", + "DRIVE_EXP 0.02880 0.02605 0.021754\n", + "DRIVE_HVY 0.07815 0.09970 0.087126\n", + "DRIVE_LOC 0.02875 0.02700 0.022622\n", + "DRIVE_LRF 0.02780 0.02445 0.023007\n", + "SHARED2FREE 0.04325 0.02300 0.042780\n", + "SHARED2PAY 0.03360 0.01585 0.029576\n", + "SHARED3FREE 0.07365 0.11920 0.086154\n", + "SHARED3PAY 0.03260 0.03960 0.026586\n", + "TAXI 0.03510 0.05030 0.036475\n", + "TNC_SHARED 0.12620 0.18115 0.135171\n", + "TNC_SINGLE 0.02625 0.03845 0.027650\n", + "WALK 0.07940 0.04825 0.088313\n", + "WALK_COM 0.03185 0.02460 0.026399\n", + "WALK_EXP 0.05705 0.05895 0.064391\n", + "WALK_HVY 0.02520 0.01840 0.020009\n", + "WALK_LOC 0.03455 0.02740 0.028021\n", + "WALK_LRF 0.02395 0.01700 0.018516" + ] + }, + "execution_count": 806, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# TODO: look at probs per trip, i.e. do not sum across trips\n", + "trip_id = 137248721\n", + "\n", + "counts_zenith.loc[counts_zenith.index == trip_id].T.merge(\n", + " counts_asim.loc[counts_asim.index == trip_id].T, suffixes=['_z', '_a'], left_index=True, right_index=True, how='outer').merge(\n", + " base_probabilities_cf.loc[base_probabilities_cf.index == trip_id].T, suffixes=['', '_probs'], left_index=True, right_index=True, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ef84f10-d616-40e4-87e5-425499027ff3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "431cee49-40a9-4dc7-93a5-75308fb302dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fcdc0ff-c510-4878-8672-8365e046442c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/frozen_rand_mode_choice_dev.ipynb b/notebooks/frozen_rand_mode_choice_dev.ipynb new file mode 100644 index 0000000000..f4203e92a6 --- /dev/null +++ b/notebooks/frozen_rand_mode_choice_dev.ipynb @@ -0,0 +1,4199 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", + "metadata": {}, + "source": [ + "# Start work here, clean up as you go\n", + "\n", + "Problem below: scale of error term on lower levels needs to be given by nest, not the case atm" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.385153Z", + "iopub.status.busy": "2022-05-01T10:17:57.384881Z", + "iopub.status.idle": "2022-05-01T10:17:57.534433Z", + "shell.execute_reply": "2022-05-01T10:17:57.533096Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%load_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3dba451-1e10-403e-8614-35d57e6577f4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.536623Z", + "iopub.status.busy": "2022-05-01T10:17:57.536012Z", + "iopub.status.idle": "2022-05-01T10:17:57.542755Z", + "shell.execute_reply": "2022-05-01T10:17:57.541685Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:00.350944Z", + "iopub.status.busy": "2022-05-01T10:18:00.350730Z", + "iopub.status.idle": "2022-05-01T10:18:12.760977Z", + "shell.execute_reply": "2022-05-01T10:18:12.760013Z", + "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import argparse\n", + "from datetime import datetime\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from numpy.random import default_rng\n", + "\n", + "\n", + "from activitysim.cli import run\n", + "from activitysim.core import inject\n", + "from activitysim.core import tracing\n", + "from activitysim.core import config\n", + "from activitysim.core import pipeline\n", + "from activitysim.core import mem\n", + "from activitysim.core import chunk\n", + "from activitysim.core import simulate\n", + "from activitysim.core import logit\n", + "from activitysim.abm.models.util.mode import mode_choice_simulate\n", + "from activitysim.abm.models.util import estimation\n", + "from activitysim.core import expressions\n", + "from activitysim.core.util import assign_in_place" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:16.944537Z", + "iopub.status.busy": "2022-05-01T10:18:16.944291Z", + "iopub.status.idle": "2022-05-01T10:18:17.124764Z", + "shell.execute_reply": "2022-05-01T10:18:17.123725Z", + "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "pd.set_option(\"max_columns\", 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:17.839947Z", + "iopub.status.busy": "2022-05-01T10:18:17.839070Z", + "iopub.status.idle": "2022-05-01T10:18:18.019676Z", + "shell.execute_reply": "2022-05-01T10:18:18.018689Z", + "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_example_mtc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:18.894533Z", + "iopub.status.busy": "2022-05-01T10:18:18.894303Z", + "iopub.status.idle": "2022-05-01T10:18:19.078807Z", + "shell.execute_reply": "2022-05-01T10:18:19.077951Z", + "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "os.chdir(example_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:22.111723Z", + "iopub.status.busy": "2022-05-01T10:18:22.111490Z", + "iopub.status.idle": "2022-05-01T10:18:22.297437Z", + "shell.execute_reply": "2022-05-01T10:18:22.296501Z", + "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "run.add_run_args(parser)\n", + "# args = parser.parse_args()\n", + "# parser.parse_args(['--sum', '7', '-1', '42'])\n", + "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", + "#run.run(args) # 2mins full example run\n", + "\n", + "\n", + "if not inject.is_injectable('preload_injectables'):\n", + " from activitysim import abm # register abm steps and other abm-specific injectables\n", + "run.handle_standard_args(args) # possibly update injectables" + ] + }, + { + "cell_type": "markdown", + "id": "6045438f-d1eb-4b5c-9737-185798b7f97f", + "metadata": {}, + "source": [ + "## trip mode choice by hand" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "600ae244-3e6c-4b66-8d39-aa9f6f60b378", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:26.399145Z", + "iopub.status.busy": "2022-05-01T10:18:26.398687Z", + "iopub.status.idle": "2022-05-01T10:18:26.583256Z", + "shell.execute_reply": "2022-05-01T10:18:26.582233Z", + "shell.execute_reply.started": "2022-05-01T10:18:26.399105Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#inject.get_table('trips').to_frame() #pipeline.orca.get_raw_table('trips').to_frame()\n", + "#inject.get_table('tours_merged').to_frame() #pipeline.orca.get_raw_table('tours_merged').to_frame()\n", + "#inject.get_injectable('network_los')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "outputs": [], + "source": [ + "def run_trip_mode_choice(do_these_purposes=None, simulate_function=simulate.simple_simulate):\n", + " #do_these_purposes=['escort']\n", + " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", + " resume_after = \"trip_scheduling\"\n", + " model_name = \"trip_mode_choice\"\n", + " chunk_size = 0 # test_mtc means no chunking\n", + "\n", + " pipeline.open_pipeline(resume_after)\n", + " # preload any bulky injectables (e.g. skims) not in pipeline\n", + " inject.get_injectable('preload_injectables', None)\n", + " pipeline._PIPELINE.rng().begin_step(model_name)\n", + " step_name = model_name\n", + " args = {}\n", + " checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + " inject.set_step_args(args)\n", + "\n", + " trips = inject.get_table('trips')\n", + " tours_merged = inject.get_table('tours_merged')\n", + " network_los = inject.get_injectable('network_los')\n", + "\n", + " trace_label = 'trip_mode_choice'\n", + " model_settings_file_name = 'trip_mode_choice.yaml'\n", + " model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + " mode_column_name = 'trip_mode'\n", + "\n", + " trips_df = trips.to_frame()\n", + " print(\"Running with %d trips\", trips_df.shape[0])\n", + "\n", + " tours_merged = tours_merged.to_frame()\n", + " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "\n", + " # - trips_merged - merge trips and tours_merged\n", + " trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + " assert trips_merged.index.equals(trips.index)\n", + "\n", + " # setup skim keys\n", + " assert ('trip_period' not in trips_merged)\n", + " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + " orig_col = 'origin'\n", + " dest_col = 'destination'\n", + "\n", + " constants = {}\n", + " constants.update(config.get_model_constants(model_settings))\n", + " constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + " })\n", + "\n", + " skim_dict = network_los.get_default_skim_dict()\n", + "\n", + " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + " skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + " }\n", + "\n", + " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + " nest_spec = config.get_logit_model_settings(model_settings)\n", + "\n", + " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + "\n", + " if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", + " continue\n", + "\n", + " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " (primary_purpose, len(trips_segment.index), ))\n", + "\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " ################ Replace wrapper function\n", + " # choices = mode_choice_simulate(...)\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simulate_function(\n", + " choosers=trips_segment,\n", + " spec=spec,\n", + " nest_spec=nest_spec,\n", + " skims=skims,\n", + " locals_d=locals_dict,\n", + " chunk_size=chunk_size,\n", + " want_logsums=logsum_column_name is not None,\n", + " trace_label=segment_trace_label,\n", + " trace_choice_name='trip_mode_choice',\n", + " estimator=estimator,\n", + " trace_column_names=None)\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " alts = spec.columns\n", + " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", + " ################\n", + " choices_list.append(choices)\n", + " choices_df_asim = pd.concat(choices_list)\n", + "\n", + " # update trips table with choices (and potionally logssums)\n", + " trips_df = trips.to_frame()\n", + "\n", + " if (do_these_purposes is not None):\n", + " trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", + "\n", + " assign_in_place(trips_df, choices_df_asim)\n", + " assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + " finalise = True\n", + " if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + " print(\"Done\")\n", + "\n", + " return trips_df" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:28:53.643404Z", + "iopub.status.busy": "2021-09-01T02:28:53.643055Z", + "iopub.status.idle": "2021-09-01T02:29:01.223951Z", + "shell.execute_reply": "2021-09-01T02:29:01.222865Z", + "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'atwork' (27 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'eatout' (33 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'escort' (6 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'othmaint' (46 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'school' (37 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'shopping' (77 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'social' (19 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'univ' (26 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'work' (168 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n" + ] + } + ], + "source": [ + "trips_df = run_trip_mode_choice()" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7eae2854-38bd-4da0-a161-3a15bbd17177", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:30:57.562848Z", + "iopub.status.busy": "2021-08-31T10:30:57.562615Z", + "iopub.status.idle": "2021-08-31T10:30:57.806655Z", + "shell.execute_reply": "2021-08-31T10:30:57.805135Z", + "shell.execute_reply.started": "2021-08-31T10:30:57.562824Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n8684833 26478 26478 eatout 1 True \n8684837 26478 26478 eatout 1 False \n8685009 26478 26478 othmaint 1 True \n8685013 26478 26478 othmaint 1 False \n8753057 26686 26686 eatout 1 True \n... ... ... ... ... ... \n2472945113 7539466 2848131 shopping 1 True \n2472945117 7539466 2848131 shopping 1 False \n2472945118 7539466 2848131 shopping 2 False \n2473024473 7539708 2848373 univ 1 True \n2473024477 7539708 2848373 univ 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n8684833 1 13 8 1085604 eatout \n8684837 1 8 13 1085604 home \n8685009 1 10 8 1085626 othmaint \n8685013 1 8 10 1085626 home \n8753057 1 5 8 1094132 eatout \n... ... ... ... ... ... \n2472945113 1 8 3 309118139 shopping \n2472945117 2 25 8 309118139 shopping \n2472945118 2 3 25 309118139 home \n2473024473 1 13 18 309128059 univ \n2473024477 1 18 13 309128059 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n8684833 NaN 11.0 WALK -1.171760 \n8684837 NaN 11.0 WALK -1.238719 \n8685009 NaN 12.0 BIKE 6.198626 \n8685013 NaN 13.0 BIKE 6.175681 \n8753057 NaN 19.0 WALK 4.457539 \n... ... ... ... ... \n2472945113 NaN 18.0 WALK_LOC 12.537675 \n2472945117 56.842247 21.0 WALK_LOC 11.880804 \n2472945118 NaN 22.0 WALK 13.710030 \n2473024473 NaN 16.0 WALK_LOC -0.530696 \n2473024477 NaN 23.0 WALK_LRF 0.624304 \n\n[482 rows x 14 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
86848332647826478eatout1True11381085604eatoutNaN11.0WALK-1.171760
86848372647826478eatout1False18131085604homeNaN11.0WALK-1.238719
86850092647826478othmaint1True11081085626othmaintNaN12.0BIKE6.198626
86850132647826478othmaint1False18101085626homeNaN13.0BIKE6.175681
87530572668626686eatout1True1581094132eatoutNaN19.0WALK4.457539
.............................................
247294511375394662848131shopping1True183309118139shoppingNaN18.0WALK_LOC12.537675
247294511775394662848131shopping1False2258309118139shopping56.84224721.0WALK_LOC11.880804
247294511875394662848131shopping2False2325309118139homeNaN22.0WALK13.710030
247302447375397082848373univ1True11318309128059univNaN16.0WALK_LOC-0.530696
247302447775397082848373univ1False11813309128059homeNaN23.0WALK_LRF0.624304
\n

482 rows × 14 columns

\n
" + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trips_df" + ] + }, + { + "cell_type": "markdown", + "id": "8eebea31-499c-41ca-8411-883a88ca800a", + "metadata": {}, + "source": [ + "## nested dev" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "3e2513e9-c03f-4891-9a1c-4053a4440a10", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:31:01.113060Z", + "iopub.status.busy": "2021-08-31T10:31:01.112829Z", + "iopub.status.idle": "2021-08-31T10:31:01.359912Z", + "shell.execute_reply": "2021-08-31T10:31:01.358948Z", + "shell.execute_reply.started": "2021-08-31T10:31:01.113034Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# see fct above - return if necessary\n", + "#spec = simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + "#nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "36c100b3-3e39-4950-a586-4d42be695eaa", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:31:01.775068Z", + "iopub.status.busy": "2021-08-31T10:31:01.774846Z", + "iopub.status.idle": "2021-08-31T10:31:01.975816Z", + "shell.execute_reply": "2021-08-31T10:31:01.974499Z", + "shell.execute_reply.started": "2021-08-31T10:31:01.775044Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#print(nest_spec)\n", + "#for nest in logit.each_nest(nest_spec):\n", + "# nest.print()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "e8d11f6a-f82a-40bd-8eef-fc28bcca8252", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:59:35.109079Z", + "iopub.status.busy": "2021-09-01T01:59:35.108781Z", + "iopub.status.idle": "2021-09-01T01:59:35.319301Z", + "shell.execute_reply": "2021-09-01T01:59:35.318402Z", + "shell.execute_reply.started": "2021-09-01T01:59:35.109050Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_dev(choosers, spec, nest_spec,\n", + " skims=None, locals_d=None,\n", + " chunk_size=0, custom_chooser=None,\n", + " log_alt_losers=False,\n", + " want_logsums=False,\n", + " estimator=None,\n", + " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", + " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", + " assert len(choosers) > 0\n", + " result_list = []\n", + " # segment by person type and pick the right spec for each person type\n", + " for i, chooser_chunk, chunk_trace_label \\\n", + " in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", + " # the following replaces choices = _simple_simulate(...)\n", + " if skims is not None:\n", + " simulate.set_skim_wrapper_targets(choosers, skims)\n", + "\n", + " # only do this for nested, logit is straight forward\n", + " assert nest_spec is not None\n", + " choices = simulate.eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser,\n", + " log_alt_losers=log_alt_losers,\n", + " want_logsums=want_logsums,\n", + " estimator=estimator,\n", + " trace_label=trace_label,\n", + " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", + "\n", + "\n", + " result_list.append(choices)\n", + " chunk.log_df(trace_label, f'result_list', result_list)\n", + "\n", + " if len(result_list) > 1:\n", + " choices = pd.concat(result_list)\n", + " assert len(choices.index == len(choosers.index))\n", + " return choices\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "ec4eb1ff-2f35-4919-96b4-5acffa01f597", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n" + ] + } + ], + "source": [ + "test_trips = run_trip_mode_choice(do_these_purposes=[\"escort\"], simulate_function=simple_simulate_dev)" + ] + }, + { + "cell_type": "markdown", + "id": "c60656db-075a-4eab-96b3-fc54eb785075", + "metadata": {}, + "source": [ + "## eval_nl dev" + ] + }, + { + "cell_type": "code", + "execution_count": 372, + "id": "56c8923c-e946-48d8-b964-87695c30163d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:27:40.290389Z", + "iopub.status.busy": "2021-09-01T02:27:40.290139Z", + "iopub.status.idle": "2021-09-01T02:27:40.977526Z", + "shell.execute_reply": "2021-09-01T02:27:40.976594Z", + "shell.execute_reply.started": "2021-09-01T02:27:40.290363Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# replaces simple_simulate_dev:\n", + "\n", + "want_logsums = True\n", + "trace_choice_name='trip_mode_choice'\n", + "trace_column_names=None\n", + "log_alt_losers=False\n", + "\n", + "tr_label = tracing.extend_trace_label(segment_trace_label, 'simple_simulate')\n", + "assert len(trips_segment) > 0\n", + "result_list = []\n", + "# segment by person type and pick the right spec for each person type\n", + "for i, chooser_chunk, chunk_trace_label \\\n", + " in chunk.adaptive_chunked_choosers(trips_segment, chunk_size, tr_label):\n", + " # the following replaces choices = _simple_simulate(...)\n", + " if skims is not None:\n", + " simulate.set_skim_wrapper_targets(trips_segment, skims)\n", + "\n", + " # only do this for nested, logit is straight forward\n", + " assert nest_spec is not None\n", + " choices = simulate.eval_nl(trips_segment, spec, nest_spec, locals_dict, custom_chooser=None,\n", + " log_alt_losers=log_alt_losers,\n", + " want_logsums=want_logsums,\n", + " estimator=estimator,\n", + " trace_label=tr_label,\n", + " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", + "\n", + "\n", + " result_list.append(choices)\n", + "\n", + "if len(result_list) > 1:\n", + " choices = pd.concat(result_list)\n", + "assert len(choices.index == len(trips_segment.index))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "simple_simulate_dev" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "cbc2dfde-1bd9-4b03-96d9-ca9b7222d36c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T12:52:34.544865Z", + "iopub.status.busy": "2021-08-31T12:52:34.544586Z", + "iopub.status.idle": "2021-08-31T12:52:35.159615Z", + "shell.execute_reply": "2021-08-31T12:52:35.158682Z", + "shell.execute_reply.started": "2021-08-31T12:52:34.544840Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "locals_d = locals_dict\n", + "custom_chooser=None\n", + "\n", + "trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + "logit.validate_nest_spec(nest_spec, trace_label)\n", + "\n", + "raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + "nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + "\n", + "nested_probabilities = \\\n", + " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", + "\n", + "logsums = pd.Series(np.log(nested_exp_utilities.root), index=trips_segment.index)\n", + "\n", + "# global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + "base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", + "\n", + "choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "0cd9593b-b3fd-41f6-a7ca-4c5b73b63571", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T12:52:45.509057Z", + "iopub.status.busy": "2021-08-31T12:52:45.508819Z", + "iopub.status.idle": "2021-08-31T12:52:45.721956Z", + "shell.execute_reply": "2021-08-31T12:52:45.721064Z", + "shell.execute_reply.started": "2021-08-31T12:52:45.509021Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 0.990308\n", + "137248725 0.108433\n", + "211388201 0.806612\n", + "211388205 0.195994\n", + "806388401 0.854020\n", + "806388405 0.660550\n", + "dtype: float64" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rands" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "b467330b-f1cc-43bc-9325-c6dfeaa9b364", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T12:50:51.735273Z", + "iopub.status.busy": "2021-08-31T12:50:51.734962Z", + "iopub.status.idle": "2021-08-31T12:50:52.023440Z", + "shell.execute_reply": "2021-08-31T12:50:52.022582Z", + "shell.execute_reply.started": "2021-08-31T12:50:51.735231Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.798480-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.360270-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.435800
137248725-1.732971-3966.018685-0.606540-0.324578-3964.610293-0.113602-3966.009714-7930.295428-inf0.26146615.945036-1368.16910311.480426-2737.292402-5512.292402-5512.292402-5512.292402-5512.292402-inf-2737.782457-5522.136798-5522.136798-5516.076156-5525.080218-inf-inf-24.341318-25.104291-24.140831-8.40687011.480440
211388201-3966.000225-7930.285939-inf-24.120135-3988.405850-8.442047-29.480718-3993.766432-10.318251-5.975663-4.216264-1381.397292-3.03571013.295720-2761.150907-2761.150907-2761.150907-2761.1509076.647860-2761.648763-5543.048131-5543.048131-2769.692185-2773.867648-inf4.786459-15.861515-3.864497-5.068360-1.2967574.789158
211388205-3967.791945-7932.077659-inf-25.129596-3989.415310-8.795359-30.177275-3994.462989-10.562046-6.219063-4.216264-1381.397292-3.03571014.022438-2760.977562-2760.977562-2760.977562-2760.9775627.011219-2761.294814-5546.132585-5546.132585-2764.829835-2774.132982-inf5.048078-15.776424-3.845077-5.005482-1.2860945.050171
806388401-3966.492870-7930.778584-inf-12.618378-3976.904092-4.416432-14.908422-3979.194136-5.217948-2.9129776.192499-1378.4807234.45859917.513379-2755.845870-2755.208370-2755.728926-2755.7930938.756690-2757.559519-5540.685990-5540.685990-5542.838278-5546.780761-inf6.304817-19.143696-19.495908-16.361074-5.8539176.451457
806388405-3969.835821-7934.121535-inf-14.515916-3978.801631-5.080571-16.227796-3980.513510-5.679728-3.3428055.998749-1378.5194734.31909917.553369-2755.987742-2755.350242-2755.870797-2755.9349648.776685-2757.571454-5542.673358-5542.673358-5537.799228-5547.515590-inf6.319213-18.886576-19.495133-16.300395-5.8285406.446188
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", + "trip_id \n", + "137248721 -1.798480 -3966.084195 -0.629468 -0.389611 \n", + "137248725 -1.732971 -3966.018685 -0.606540 -0.324578 \n", + "211388201 -3966.000225 -7930.285939 -inf -24.120135 \n", + "211388205 -3967.791945 -7932.077659 -inf -25.129596 \n", + "806388401 -3966.492870 -7930.778584 -inf -12.618378 \n", + "806388405 -3969.835821 -7934.121535 -inf -14.515916 \n", + "\n", + " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", + "trip_id \n", + "137248721 -3964.675326 -0.136364 -3966.074556 -7930.360270 -inf \n", + "137248725 -3964.610293 -0.113602 -3966.009714 -7930.295428 -inf \n", + "211388201 -3988.405850 -8.442047 -29.480718 -3993.766432 -10.318251 \n", + "211388205 -3989.415310 -8.795359 -30.177275 -3994.462989 -10.562046 \n", + "806388401 -3976.904092 -4.416432 -14.908422 -3979.194136 -5.217948 \n", + "806388405 -3978.801631 -5.080571 -16.227796 -3980.513510 -5.679728 \n", + "\n", + " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", + "trip_id \n", + "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", + "137248725 0.261466 15.945036 -1368.169103 11.480426 -2737.292402 \n", + "211388201 -5.975663 -4.216264 -1381.397292 -3.035710 13.295720 \n", + "211388205 -6.219063 -4.216264 -1381.397292 -3.035710 14.022438 \n", + "806388401 -2.912977 6.192499 -1378.480723 4.458599 17.513379 \n", + "806388405 -3.342805 5.998749 -1378.519473 4.319099 17.553369 \n", + "\n", + " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", + "trip_id \n", + "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", + "137248725 -5512.292402 -5512.292402 -5512.292402 -5512.292402 -inf \n", + "211388201 -2761.150907 -2761.150907 -2761.150907 -2761.150907 6.647860 \n", + "211388205 -2760.977562 -2760.977562 -2760.977562 -2760.977562 7.011219 \n", + "806388401 -2755.845870 -2755.208370 -2755.728926 -2755.793093 8.756690 \n", + "806388405 -2755.987742 -2755.350242 -2755.870797 -2755.934964 8.776685 \n", + "\n", + " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", + "trip_id \n", + "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", + "137248725 -2737.782457 -5522.136798 -5522.136798 -5516.076156 -5525.080218 \n", + "211388201 -2761.648763 -5543.048131 -5543.048131 -2769.692185 -2773.867648 \n", + "211388205 -2761.294814 -5546.132585 -5546.132585 -2764.829835 -2774.132982 \n", + "806388401 -2757.559519 -5540.685990 -5540.685990 -5542.838278 -5546.780761 \n", + "806388405 -2757.571454 -5542.673358 -5542.673358 -5537.799228 -5547.515590 \n", + "\n", + " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", + "trip_id \n", + "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", + "137248725 -inf -inf -24.341318 -25.104291 -24.140831 -8.406870 \n", + "211388201 -inf 4.786459 -15.861515 -3.864497 -5.068360 -1.296757 \n", + "211388205 -inf 5.048078 -15.776424 -3.845077 -5.005482 -1.286094 \n", + "806388401 -inf 6.304817 -19.143696 -19.495908 -16.361074 -5.853917 \n", + "806388405 -inf 6.319213 -18.886576 -19.495133 -16.300395 -5.828540 \n", + "\n", + " root \n", + "trip_id \n", + "137248721 11.435800 \n", + "137248725 11.480440 \n", + "211388201 4.789158 \n", + "211388205 5.050171 \n", + "806388401 6.451457 \n", + "806388405 6.446188 " + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# root is logsum here, no need to take log again as for exponentiated utils\n", + "simulate.compute_nested_exp_utilities(raw_utilities, nest_spec, exponentiate=False)" + ] + }, + { + "cell_type": "markdown", + "id": "5c9ba018-167d-497f-ac65-f7c49036ee58", + "metadata": {}, + "source": [ + "#### scale of utils, including emus" + ] + }, + { + "cell_type": "code", + "execution_count": 283, + "id": "9febf6ef-7c6b-4f3c-bdd9-aa3326a82e95", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T23:23:00.020038Z", + "iopub.status.busy": "2021-08-31T23:23:00.019377Z", + "iopub.status.idle": "2021-08-31T23:23:00.220889Z", + "shell.execute_reply": "2021-08-31T23:23:00.220154Z", + "shell.execute_reply.started": "2021-08-31T23:23:00.019997Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "nests_ = list(logit.each_nest(nest_spec))" + ] + }, + { + "cell_type": "code", + "execution_count": 284, + "id": "48ff920e-0c9b-4b69-9cee-eb3c3427bd4f", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T23:23:01.085713Z", + "iopub.status.busy": "2021-08-31T23:23:01.085486Z", + "iopub.status.idle": "2021-08-31T23:23:01.283509Z", + "shell.execute_reply": "2021-08-31T23:23:01.282444Z", + "shell.execute_reply.started": "2021-08-31T23:23:01.085685Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] alternatives: ['DRIVEALONE', 'SHAREDRIDE2', 'SHAREDRIDE3']\n", + "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] alternatives: ['DRIVEALONEFREE', 'DRIVEALONEPAY']\n", + "Nest name: DRIVEALONEFREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE'] alternatives: None\n", + "Nest name: DRIVEALONEPAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY'] alternatives: None\n" + ] + } + ], + "source": [ + "#list(map(lambda x: x.print(), nests_));\n", + "# have a look at \n", + "nests_[1].print()\n", + "nests_[2].print()\n", + "nests_[3].print()\n", + "nests_[4].print()" + ] + }, + { + "cell_type": "code", + "execution_count": 312, + "id": "7b7e453d-da45-4751-8889-561a349bd373", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:22.371464Z", + "iopub.status.busy": "2021-09-01T01:11:22.371192Z", + "iopub.status.idle": "2021-09-01T01:11:22.593424Z", + "shell.execute_reply": "2021-09-01T01:11:22.592472Z", + "shell.execute_reply.started": "2021-09-01T01:11:22.371415Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation belowi s equivalent.\n", + "def compute_nested_utilities(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) / nest.product_of_coefficients #coefficient\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", + " return nested_utilities\n", + "\n", + "\n", + "utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", + "\n", + "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", + "seed = 9326543345\n", + "rng = default_rng(seed=seed)\n", + "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", + "rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", + "\n", + "probs_arr = utils_df - np.log(-np.log(rands))\n", + "\n", + "# Now replace the following with recursive nested choice\n", + "# choices = np.argmax(probs_arr, axis=1)\n", + "# choices = pd.Series(choices, index=utils_df.index)\n", + "# rands = pd.Series(np.asanyarray(rands).flatten(), index=utils_df.index)" + ] + }, + { + "cell_type": "code", + "execution_count": 313, + "id": "39b71df9-12ac-4d9c-8134-43d22f7e6a6f", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:23.703299Z", + "iopub.status.busy": "2021-09-01T01:11:23.703088Z", + "iopub.status.idle": "2021-09-01T01:11:23.937077Z", + "shell.execute_reply": "2021-09-01T01:11:23.935627Z", + "shell.execute_reply.started": "2021-09-01T01:11:23.703274Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.79848-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.36027-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.4358
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", + "trip_id \n", + "137248721 -1.79848 -3966.084195 -0.629468 -0.389611 \n", + "\n", + " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", + "trip_id \n", + "137248721 -3964.675326 -0.136364 -3966.074556 -7930.36027 -inf \n", + "\n", + " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", + "trip_id \n", + "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", + "\n", + " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", + "trip_id \n", + "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", + "\n", + " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", + "trip_id \n", + "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", + "\n", + " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", + "trip_id \n", + "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", + "\n", + " root \n", + "trip_id \n", + "137248721 11.4358 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-0.717963-3964.2474012.7217010.230429-3964.716506-0.274584-3965.126773-7930.610173-inf1.73337615.473633-1368.52552614.717027-2736.517471-5510.738449-5512.038516-5512.559241-5511.584293-inf-2738.143234-5518.606969-5519.009587-5519.830735-5521.525966-inf-inf-25.519792-21.698963-25.267185-7.44465211.689443
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", + "trip_id \n", + "137248721 -0.717963 -3964.247401 2.721701 0.230429 \n", + "\n", + " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", + "trip_id \n", + "137248721 -3964.716506 -0.274584 -3965.126773 -7930.610173 -inf \n", + "\n", + " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", + "trip_id \n", + "137248721 1.733376 15.473633 -1368.525526 14.717027 -2736.517471 \n", + "\n", + " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", + "trip_id \n", + "137248721 -5510.738449 -5512.038516 -5512.559241 -5511.584293 -inf \n", + "\n", + " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", + "trip_id \n", + "137248721 -2738.143234 -5518.606969 -5519.009587 -5519.830735 -5521.525966 \n", + "\n", + " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", + "trip_id \n", + "137248721 -inf -inf -25.519792 -21.698963 -25.267185 -7.444652 \n", + "\n", + " root \n", + "trip_id \n", + "137248721 11.689443 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(utils_df.head(1), probs_arr.head(1))" + ] + }, + { + "cell_type": "markdown", + "id": "2f080150-c15a-4059-8a26-42a8c0072606", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T23:16:40.014024Z", + "iopub.status.busy": "2021-08-31T23:16:40.013770Z", + "iopub.status.idle": "2021-08-31T23:16:40.236326Z", + "shell.execute_reply": "2021-08-31T23:16:40.235553Z", + "shell.execute_reply.started": "2021-08-31T23:16:40.013973Z" + }, + "tags": [] + }, + "source": [ + "### make choice at each level" + ] + }, + { + "cell_type": "code", + "execution_count": 315, + "id": "2ed71b54-67e4-4087-957a-75f4fa184144", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:43.778081Z", + "iopub.status.busy": "2021-09-01T01:11:43.777757Z", + "iopub.status.idle": "2021-09-01T01:11:43.975332Z", + "shell.execute_reply": "2021-09-01T01:11:43.974588Z", + "shell.execute_reply.started": "2021-09-01T01:11:43.778055Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def get_alternatives(nests, name):\n", + " alts = list(filter(lambda x: x.name == name, nests))\n", + " assert len(alts) == 1, f\"{len(alts)} not one\"\n", + " alts = alts[0].alternatives\n", + " return alts\n", + "\n", + "def recursive_choice(row, columns, nest_levels, nests):\n", + " choices = row[columns].idxmax() #axis=1).values[0]\n", + " next_level_columns = get_alternatives(nests, choices)\n", + " #print(f\"{choices} leads to columns {next_level_columns}\")\n", + " if next_level_columns is None:\n", + " return choices \n", + " new_choice = recursive_choice(row, next_level_columns, nest_levels, nests)\n", + " return new_choice" + ] + }, + { + "cell_type": "code", + "execution_count": 316, + "id": "30424562-2627-446b-abd5-7c763c52060e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:49.444289Z", + "iopub.status.busy": "2021-09-01T01:11:49.444002Z", + "iopub.status.idle": "2021-09-01T01:11:49.664214Z", + "shell.execute_reply": "2021-09-01T01:11:49.663088Z", + "shell.execute_reply.started": "2021-09-01T01:11:49.444266Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n" + ] + } + ], + "source": [ + "# group nests by level:\n", + "depth = np.max([x.level for x in nests_])\n", + "print(depth)\n", + "nest_levels = {x: [] for x in range(1, depth+1)}\n", + "for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + "assert len(nest_levels[1]) == 1\n", + "assert nest_levels[1][0] == 'root'" + ] + }, + { + "cell_type": "code", + "execution_count": 326, + "id": "bae6e7a8-d7c3-42a2-a82f-874b1e59a0b6", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:56:38.377472Z", + "iopub.status.busy": "2021-09-01T01:56:38.377253Z", + "iopub.status.idle": "2021-09-01T01:56:38.607858Z", + "shell.execute_reply": "2021-09-01T01:56:38.606913Z", + "shell.execute_reply.started": "2021-09-01T01:56:38.377447Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 WALK\n", + "137248725 WALK\n", + "211388201 WALK_LOC\n", + "211388205 WALK_LOC\n", + "806388401 WALK\n", + "806388405 WALK\n", + "dtype: object" + ] + }, + "execution_count": 326, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests_), axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 321, + "id": "fc315f63-542f-46fd-8d70-b7a1fcd492c6", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:53:15.500933Z", + "iopub.status.busy": "2021-09-01T01:53:15.500709Z", + "iopub.status.idle": "2021-09-01T01:53:15.703201Z", + "shell.execute_reply": "2021-09-01T01:53:15.702357Z", + "shell.execute_reply.started": "2021-09-01T01:53:15.500909Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#display(utils_df, probs_arr)" + ] + }, + { + "cell_type": "markdown", + "id": "8a45e1c0-0a32-4f7a-8803-7559d71abefd", + "metadata": {}, + "source": [ + "### some tests\n", + "seeds, average?" + ] + }, + { + "cell_type": "code", + "execution_count": 333, + "id": "f0e00814-2a14-4da1-bc6c-89b52b7fb286", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:58:26.002425Z", + "iopub.status.busy": "2021-09-01T01:58:26.002200Z", + "iopub.status.idle": "2021-09-01T01:58:26.247954Z", + "shell.execute_reply": "2021-09-01T01:58:26.247054Z", + "shell.execute_reply.started": "2021-09-01T01:58:26.002401Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 WALK\n", + "137248725 WALK\n", + "211388201 WALK_LOC\n", + "211388205 WALK_LOC\n", + "806388401 WALK\n", + "806388405 WALK\n", + "dtype: object" + ] + }, + "execution_count": 333, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", + "seed = 9326543345\n", + "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", + "\n", + "def make_choice(utils_df, nests, nest_levels, seed=None):\n", + " rng = default_rng(seed=seed)\n", + " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", + " probs_arr = utils_df - np.log(-np.log(rands))\n", + " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", + " return choices\n", + " \n", + "make_choice(utils_df, nests_, nest_levels, seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 348, + "id": "bca659db-4c88-4287-8f5e-9c9516e3ef41", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:59:00.410062Z", + "iopub.status.busy": "2021-09-01T01:59:00.409811Z", + "iopub.status.idle": "2021-09-01T01:59:00.621940Z", + "shell.execute_reply": "2021-09-01T01:59:00.621156Z", + "shell.execute_reply.started": "2021-09-01T01:59:00.410037Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "137248721 WALK\n", + "137248725 WALK\n", + "211388201 WALK_LOC\n", + "211388205 WALK_LOC\n", + "806388401 WALK_LOC\n", + "806388405 WALK_LOC\n", + "dtype: object" + ] + }, + "execution_count": 348, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "make_choice(utils_df, nests_, nest_levels)" + ] + }, + { + "cell_type": "code", + "execution_count": 453, + "id": "806ce752-0927-4d5b-a6cc-68d6c9b8a05e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:55:59.542669Z", + "iopub.status.busy": "2021-09-01T04:55:59.542360Z", + "iopub.status.idle": "2021-09-01T04:55:59.771722Z", + "shell.execute_reply": "2021-09-01T04:55:59.770801Z", + "shell.execute_reply.started": "2021-09-01T04:55:59.542615Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + " nested_probabilities = \\\n", + " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", + " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) \n", + " return base_probabilities\n", + "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 451, + "id": "054fd9b1-72fc-49cc-a790-48ef75bcbaed", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:55:08.260140Z", + "iopub.status.busy": "2021-09-01T04:55:08.259914Z", + "iopub.status.idle": "2021-09-01T04:55:09.140095Z", + "shell.execute_reply": "2021-09-01T04:55:09.139119Z", + "shell.execute_reply.started": "2021-09-01T04:55:08.260116Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "86627409 WALK_LRF\n", + "86627413 WALK_LRF\n", + "86673657 WALK_LOC\n", + "86673658 WALK\n", + "86673659 WALK\n", + " ... \n", + "2464446025 WALK\n", + "2464446029 WALK\n", + "2464449633 WALK\n", + "2464449634 WALK\n", + "2464449637 WALK\n", + "Length: 168, dtype: object" + ] + }, + "execution_count": 451, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def simple_simulate_rum(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", + "\n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices\n", + "\n", + "simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" + ] + }, + { + "cell_type": "code", + "execution_count": 380, + "id": "2da81b5e-7992-4437-8e0f-905caf026804", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:33:10.886502Z", + "iopub.status.busy": "2021-09-01T02:33:10.886252Z", + "iopub.status.idle": "2021-09-01T02:33:11.092145Z", + "shell.execute_reply": "2021-09-01T02:33:11.090725Z", + "shell.execute_reply.started": "2021-09-01T02:33:10.886465Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'trip_mode_choice'" + ] + }, + "execution_count": 380, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trace_label" + ] + }, + { + "cell_type": "code", + "execution_count": 393, + "id": "f3c5c1ba-e9c3-4511-98bf-9ac98c987da7", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:39:52.539983Z", + "iopub.status.busy": "2021-09-01T02:39:52.539668Z", + "iopub.status.idle": "2021-09-01T02:39:52.796170Z", + "shell.execute_reply": "2021-09-01T02:39:52.795498Z", + "shell.execute_reply.started": "2021-09-01T02:39:52.539944Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " segment_trace_label, log_alt_losers=log_alt_losers, \n", + " trace_column_names=trace_column_names, seed=seed)\n", + "\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " choices_list.append(choices)\n", + "\n", + " choices_df = pd.concat(choices_list)\n", + " return choices_df" + ] + }, + { + "cell_type": "code", + "execution_count": 454, + "id": "74eeedd3-4ade-4729-8170-79fa9dcf11f8", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:58:52.825042Z", + "iopub.status.busy": "2021-09-01T04:58:52.824768Z", + "iopub.status.idle": "2021-09-01T04:58:53.042884Z", + "shell.execute_reply": "2021-09-01T04:58:53.042061Z", + "shell.execute_reply.started": "2021-09-01T04:58:52.825004Z" + } + }, + "outputs": [], + "source": [ + "## caculate probabilities with Asim methodology, should be correct\n", + "def gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None):\n", + " full_probs = [] # analytical probs\n", + "\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " #choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " # segment_trace_label, log_alt_losers=log_alt_losers, \n", + " # trace_column_names=trace_column_names, seed=seed)\n", + " probs = simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, \n", + " estimator, segment_trace_label, log_alt_losers, \n", + " trace_column_names)\n", + " full_probs.append(probs)\n", + " probs_df = pd.concat(full_probs)\n", + " return probs_df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 455, + "id": "295a4fd1-8e85-47e2-af82-9f1beac31d63", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:59:46.126555Z", + "iopub.status.busy": "2021-09-01T04:59:46.126262Z", + "iopub.status.idle": "2021-09-01T04:59:52.979908Z", + "shell.execute_reply": "2021-09-01T04:59:52.979043Z", + "shell.execute_reply.started": "2021-09-01T04:59:46.126522Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2021-09-01 14:59:46.353007 Start\n", + "2021-09-01 14:59:52.976736 End\n" + ] + } + ], + "source": [ + "print(f\"{datetime.now()} Start\")\n", + "pipeline.open_pipeline(resume_after)\n", + "inject.get_injectable('preload_injectables', None)\n", + "model_name = \"trip_mode_choice\"\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "step_name = model_name\n", + "args = {}\n", + "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "chunk_size = 0\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "trips_df = trips.to_frame()\n", + "#print(\"Running with %d trips\", trips_df.shape[0])\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + "orig_col = 'origin'\n", + "dest_col = 'destination'\n", + "\n", + "constants = {}\n", + "constants.update(config.get_model_constants(model_settings))\n", + "constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + "})\n", + "\n", + "skim_dict = network_los.get_default_skim_dict()\n", + "\n", + "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + "skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + "}\n", + "\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "\n", + "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + "\n", + "all_choices = []\n", + "for i in range(100):\n", + " if i % 10 == 0:\n", + " print(f\"{datetime.now()} iteration {i}\")\n", + " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=None)\n", + " all_choices.append(choices_df)\n", + "all_choices = pd.concat(all_choices, axis=1)\n", + "\n", + "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", + " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", + " log_alt_losers=None, trace_column_names=None)\n", + "\n", + "# update trips table with choices (and potionally logssums)\n", + "#trips_df = trips.to_frame()\n", + "#\n", + "#assign_in_place(trips_df, choices_df)\n", + "#assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + "print(f\"{datetime.now()} End\")" + ] + }, + { + "cell_type": "code", + "execution_count": 463, + "id": "dc87e904-16d1-44d4-81b7-cafbe4e223c3", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:01:40.901982Z", + "iopub.status.busy": "2021-09-01T05:01:40.901759Z", + "iopub.status.idle": "2021-09-01T05:01:41.337758Z", + "shell.execute_reply": "2021-09-01T05:01:41.336757Z", + "shell.execute_reply.started": "2021-09-01T05:01:40.901946Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#all_choices.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", + "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts = val_counts / all_choices.shape[1]\n", + "#val_counts = val_counts.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", + "#val_counts['prob_of_asim_choice'] = val_counts.apply(lambda x: x[x.asim], axis=1) # this is what our simulation says w.r.t. to asim choice\n", + "# for 100% and many samples should mostly agree" + ] + }, + { + "cell_type": "code", + "execution_count": 464, + "id": "09bf151c-a39a-4823-acfd-9c8ae747c338", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:01:46.119117Z", + "iopub.status.busy": "2021-09-01T05:01:46.118849Z", + "iopub.status.idle": "2021-09-01T05:01:46.354114Z", + "shell.execute_reply": "2021-09-01T05:01:46.353248Z", + "shell.execute_reply.started": "2021-09-01T05:01:46.119092Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#val_counts['prob_of_asim_choice'].hist(bins=100);" + ] + }, + { + "cell_type": "code", + "execution_count": 490, + "id": "f3b6ade9-d875-4104-b438-b53ef42e342c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:10:08.653227Z", + "iopub.status.busy": "2021-09-01T05:10:08.652978Z", + "iopub.status.idle": "2021-09-01T05:10:08.915620Z", + "shell.execute_reply": "2021-09-01T05:10:08.914513Z", + "shell.execute_reply.started": "2021-09-01T05:10:08.653189Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", + "analytical 3.152 0.852 0.0 0.0 0.0 \n", + "experiment 3.243 0.880 0.0 0.0 0.0 \n", + "\n", + " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", + "analytical 0.0 0.0 0.0 0.685 0.0 \n", + "experiment 0.0 0.0 0.0 0.772 0.0 \n", + "\n", + " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK \\\n", + "analytical 0.129 0.0 0.182 0.260 1.334 63.708 \n", + "experiment 0.156 0.0 0.174 0.259 1.434 63.243 \n", + "\n", + " WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", + "analytical 0.0 0.0 0.455 18.355 10.887 \n", + "experiment 0.0 0.0 0.394 18.639 10.807 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mode_share_analytical = (probs_nl.sum(axis=0) / probs_nl.shape[0]).to_frame('analytical')\n", + "assert np.allclose(mode_share_analytical.sum(), 1)\n", + "mode_share_rum = (val_counts.sum(axis=0) / val_counts.shape[0]).to_frame('experiment')\n", + "assert np.allclose(mode_share_rum.sum(), 1)\n", + "full_share = mode_share_analytical.join(mode_share_rum, how='outer').fillna(0)\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * full_share).T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e48a56e-d613-4dfd-bef6-62bee524f12f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50914e1d-750d-440c-9617-f96bd2a46c56", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2d70e0e-520e-49a6-8024-ae31345f6ead", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f9e8ef02-1d57-4b21-8aef-25a6e1095c02", + "metadata": {}, + "source": [ + "### try zenith normalisation of simple_simulate_rum" + ] + }, + { + "cell_type": "code", + "execution_count": 629, + "id": "db235e90-d2e5-4e90-b41c-6d90a670e41d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:24.045881Z", + "iopub.status.busy": "2021-09-01T11:37:24.045637Z", + "iopub.status.idle": "2021-09-01T11:37:24.276488Z", + "shell.execute_reply": "2021-09-01T11:37:24.274898Z", + "shell.execute_reply.started": "2021-09-01T11:37:24.045844Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation belowi s equivalent.\n", + "def compute_nested_utilities_zenith(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) # / nest.product_of_coefficients #coefficient\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", + " \n", + " # now go over all leaves and correct for scale\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] /= nest.coefficient\n", + " \n", + " return nested_utilities\n", + "\n", + "\n", + "def simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " utils_df = compute_nested_utilities_zenith(raw_utilities, nest_spec)\n", + "\n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices\n", + "\n", + "#simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" + ] + }, + { + "cell_type": "code", + "execution_count": 630, + "id": "f69bc3dc-84e4-4dd5-9a57-c053cadba4b5", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:24.828926Z", + "iopub.status.busy": "2021-09-01T11:37:24.828707Z", + "iopub.status.idle": "2021-09-01T11:37:25.046409Z", + "shell.execute_reply": "2021-09-01T11:37:25.045399Z", + "shell.execute_reply.started": "2021-09-01T11:37:24.828902Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " segment_trace_label, log_alt_losers=log_alt_losers, \n", + " trace_column_names=trace_column_names, seed=seed)\n", + "\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " choices_list.append(choices)\n", + "\n", + " choices_df = pd.concat(choices_list)\n", + " return choices_df" + ] + }, + { + "cell_type": "code", + "execution_count": 631, + "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:32.250808Z", + "iopub.status.busy": "2021-09-01T11:37:32.250548Z", + "iopub.status.idle": "2021-09-01T20:20:35.257463Z", + "shell.execute_reply": "2021-09-01T20:20:35.255964Z", + "shell.execute_reply.started": "2021-09-01T11:37:32.250782Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "num_samples = 10\n", + "\n", + "rng_test = default_rng(23423)\n", + "\n", + "print(f\"{datetime.now()} Start\")\n", + "pipeline.open_pipeline(resume_after)\n", + "inject.get_injectable('preload_injectables', None)\n", + "model_name = \"trip_mode_choice\"\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "step_name = model_name\n", + "args = {}\n", + "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "chunk_size = 0\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "trips_df = trips.to_frame()\n", + "#print(\"Running with %d trips\", trips_df.shape[0])\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + "orig_col = 'origin'\n", + "dest_col = 'destination'\n", + "\n", + "constants = {}\n", + "constants.update(config.get_model_constants(model_settings))\n", + "constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + "})\n", + "\n", + "skim_dict = network_los.get_default_skim_dict()\n", + "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + "}\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + "all_choices_zenith = []\n", + "all_choices = []\n", + "for i in range(num_samples):\n", + " \n", + " seed = rng_test.integers(0, 100000) #int(9.3 * (i+1)**3) # why not\n", + " \n", + " if i % 50 == 0:\n", + " print(f\"{datetime.now()} iteration {i}\")\n", + " choices_df_zenith = stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", + " all_choices_zenith.append(choices_df_zenith)\n", + "\n", + " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", + " all_choices.append(choices_df)\n", + " \n", + " t_ = choices_df_zenith.merge(choices_df, left_index=True, right_index=True, suffixes=['_zenith', '_asim'])\n", + " diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", + " \n", + " #print(f\"seed {seed} leads to {diffs.shape[0]} differences. tripids {diffs.index}\")\n", + "\n", + "all_choices_zenith = pd.concat(all_choices_zenith, axis=1)\n", + "all_choices = pd.concat(all_choices, axis=1)\n", + "\n", + "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", + " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", + " log_alt_losers=None, trace_column_names=None)\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + "print(f\"{datetime.now()} End\")" + ] + }, + { + "cell_type": "code", + "execution_count": 643, + "id": "3abd7949-dc9c-469b-845e-26c17741f70e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:33:21.533110Z", + "iopub.status.busy": "2021-09-01T20:33:21.531915Z", + "iopub.status.idle": "2021-09-01T20:33:21.740946Z", + "shell.execute_reply": "2021-09-01T20:33:21.739713Z", + "shell.execute_reply.started": "2021-09-01T20:33:21.533074Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Have 2038 samples\n" + ] + } + ], + "source": [ + "print(f\"Have {all_choices_zenith.shape[1]} samples\")" + ] + }, + { + "cell_type": "code", + "execution_count": 633, + "id": "8762bd6d-ac04-458c-869f-be86d8297351", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:21:27.778803Z", + "iopub.status.busy": "2021-09-01T20:21:27.778531Z", + "iopub.status.idle": "2021-09-01T20:21:28.675645Z", + "shell.execute_reply": "2021-09-01T20:21:28.674734Z", + "shell.execute_reply.started": "2021-09-01T20:21:27.778754Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "val_counts_zenith = all_choices_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts_zenith = val_counts_zenith / all_choices_zenith.shape[1]\n", + "\n", + "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts = val_counts / all_choices.shape[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 634, + "id": "0f566105-9424-457b-86f5-d60c1d63aac5", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:21:30.954894Z", + "iopub.status.busy": "2021-09-01T20:21:30.954654Z", + "iopub.status.idle": "2021-09-01T20:21:31.179689Z", + "shell.execute_reply": "2021-09-01T20:21:31.178304Z", + "shell.execute_reply.started": "2021-09-01T20:21:30.954867Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
experiment_zenith3.1500.8740.00.00.00.00.00.00.6800.00.1360.00.1540.2361.28563.7670.00.00.51518.82410.379
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", + "analytical 3.152 0.852 0.0 0.0 0.0 \n", + "experiment 3.243 0.880 0.0 0.0 0.0 \n", + "experiment_zenith 3.150 0.874 0.0 0.0 0.0 \n", + "\n", + " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", + "analytical 0.0 0.0 0.0 0.685 0.0 \n", + "experiment 0.0 0.0 0.0 0.772 0.0 \n", + "experiment_zenith 0.0 0.0 0.0 0.680 0.0 \n", + "\n", + " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE \\\n", + "analytical 0.129 0.0 0.182 0.260 1.334 \n", + "experiment 0.156 0.0 0.174 0.259 1.434 \n", + "experiment_zenith 0.136 0.0 0.154 0.236 1.285 \n", + "\n", + " WALK WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", + "analytical 63.708 0.0 0.0 0.455 18.355 10.887 \n", + "experiment 63.243 0.0 0.0 0.394 18.639 10.807 \n", + "experiment_zenith 63.767 0.0 0.0 0.515 18.824 10.379 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mode_share_rum_zenith = (val_counts_zenith.sum(axis=0) / val_counts_zenith.shape[0]).to_frame('experiment_zenith')\n", + "assert np.allclose(mode_share_rum_zenith.sum(), 1)\n", + "full_share_incl_zenith = full_share.merge(mode_share_rum_zenith, left_index=True, right_index=True, how='outer').fillna(0)\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * full_share_incl_zenith).T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c0d3081-b45b-4573-9246-e4a9d39591dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "612b2f04-45e2-4eb2-b52a-9f753427877f", + "metadata": {}, + "source": [ + "## investigate diverging seed and look at diff in formulation" + ] + }, + { + "cell_type": "code", + "execution_count": 619, + "id": "ac09dc75-a92f-43fb-a0cd-fe7de1f8b66d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:12:02.988141Z", + "iopub.status.busy": "2021-09-01T11:12:02.987900Z", + "iopub.status.idle": "2021-09-01T11:12:03.212179Z", + "shell.execute_reply": "2021-09-01T11:12:03.210993Z", + "shell.execute_reply.started": "2021-09-01T11:12:02.988102Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation based on asim probabilities is equivalent\n", + "def compute_nested_utilities_asim(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) / nest.product_of_coefficients\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", + " return nested_utilities\n", + "\n", + "def compute_nested_utilities_zenith_check(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) # scale correction is below\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", + " \n", + " # now go over all leaves and correct for scale\n", + " for nest in logit.each_nest(nest_spec):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] /= nest.coefficient\n", + " \n", + " return nested_utilities" + ] + }, + { + "cell_type": "code", + "execution_count": 688, + "id": "4b7c6368-21ca-4442-969e-f2f8ff868842", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:49:56.958862Z", + "iopub.status.busy": "2021-09-01T20:49:56.958623Z", + "iopub.status.idle": "2021-09-01T20:49:57.191884Z", + "shell.execute_reply": "2021-09-01T20:49:57.190975Z", + "shell.execute_reply.started": "2021-09-01T20:49:56.958827Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None, use_zenith=True, raw_utilities=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " \n", + " if raw_utilities is None:\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " if use_zenith:\n", + " utils_df = compute_nested_utilities_zenith_check(raw_utilities, nest_spec)\n", + " else:\n", + " utils_df = compute_nested_utilities_asim(raw_utilities, nest_spec)\n", + "\n", + " # test\n", + " #return compute_nested_utilities_zenith_check(raw_utilities, nest_spec), compute_nested_utilities_asim(raw_utilities, nest_spec)\n", + " \n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices" + ] + }, + { + "cell_type": "code", + "execution_count": 626, + "id": "bd6cadc3-79f5-4824-b55e-639c39d5ab18", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:13:12.077886Z", + "iopub.status.busy": "2021-09-01T11:13:12.077652Z", + "iopub.status.idle": "2021-09-01T11:13:12.290222Z", + "shell.execute_reply": "2021-09-01T11:13:12.288861Z", + "shell.execute_reply.started": "2021-09-01T11:13:12.077863Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# t_z, t_a = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed)\n", + "# tr_id = 86673661\n", + "# display(t_z.loc[t_z.index==tr_id])\n", + "# display(t_a.loc[t_a.index==tr_id])" + ] + }, + { + "cell_type": "code", + "execution_count": 625, + "id": "42e6da62-58e8-4b16-9169-9faa1fc8a162", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:13:06.847583Z", + "iopub.status.busy": "2021-09-01T11:13:06.847204Z", + "iopub.status.idle": "2021-09-01T11:13:08.401700Z", + "shell.execute_reply": "2021-09-01T11:13:08.400711Z", + "shell.execute_reply.started": "2021-09-01T11:13:06.847554Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "seed 51006 leads to 34 differences out of 168. tripids Int64Index([ 86673657, 86673661, 106741681, 106741682, 106741685,\n", + " 106741686, 106741687, 106741688, 211327433, 211327437,\n", + " 444793574, 484173905, 484173909, 535170694, 535620053,\n", + " 708171014, 943749470, 943749471, 1060575853, 1091770617,\n", + " 1146472489, 1146472493, 1276281769, 1276281773, 1658748793,\n", + " 1658748797, 1767013726, 1767186577, 1767186578, 1768237161,\n", + " 1768237165, 1768237166, 2463663417, 2463663421],\n", + " dtype='int64', name='trip_id')\n" + ] + } + ], + "source": [ + "seed = 51006\n", + "x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True)\n", + "y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False)\n", + "t_ = x_.to_frame('trip_mode_zenith').merge(y_.to_frame('trip_mode_asim'), left_index=True, right_index=True)\n", + "diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", + "print(f\"seed {seed} leads to {diffs.shape[0]} differences out of {t_.shape[0]}. tripids {diffs.index}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f3c0506-a79d-4a31-9d56-71b9966d0a92", + "metadata": {}, + "outputs": [], + "source": [ + "seed 51006 leads to 3 differences. tripids Int64Index([86673661, 535170689, 1060575849], dtype='int64', name='trip_id')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a5f332a-b1d7-4a80-bb21-d37a1611cf9f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24c5e4a1-6142-49d4-8da8-a1e266b3cb9b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 753, + "id": "2098907f-28af-4f53-af74-6aaee8a6c53c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T23:04:23.861048Z", + "iopub.status.busy": "2021-09-01T23:04:23.860748Z", + "iopub.status.idle": "2021-09-01T23:04:24.069307Z", + "shell.execute_reply": "2021-09-01T23:04:24.068434Z", + "shell.execute_reply.started": "2021-09-01T23:04:23.861024Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "rng_ = default_rng(seed=100)\n", + "new_utils = pd.DataFrame(0.1 * -np.log(-np.log(rng_.uniform(0,1,raw_utilities.shape))), columns=raw_utilities.columns)\n", + "new_utils.index = raw_utilities.index" + ] + }, + { + "cell_type": "code", + "execution_count": 767, + "id": "da48a178-82b2-44a5-b45a-e9de3bb5e5c2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T23:17:20.369675Z", + "iopub.status.busy": "2021-09-01T23:17:20.369424Z", + "iopub.status.idle": "2021-09-01T23:17:20.666506Z", + "shell.execute_reply": "2021-09-01T23:17:20.665626Z", + "shell.execute_reply.started": "2021-09-01T23:17:20.369637Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
1372487210.0566420.0373050.0427800.0295760.0861540.0265860.0883130.1003310.0280210.0185160.0643910.0200090.0263990.0226220.0230070.0217540.0871260.0211710.0364750.0276500.135171
1372487250.1019020.0228760.0312270.0699900.0569130.0466850.0884110.0877850.0254140.0381620.0239880.0288000.0393180.0201600.0483640.0230030.0339590.0345970.0377080.0995230.041213
2113882010.0375620.0700330.0559510.0403890.0200410.0959740.0946780.0943680.0360140.0250510.0348600.0349530.0354990.0280400.0436500.0247130.0313880.0259310.0669990.0526780.051228
2113882050.0354800.0562560.0439440.0480850.0420780.0622210.1068190.0872650.0351170.0303990.0351540.0264690.0506460.0357270.0242840.0329050.0394270.0410920.0547140.0484590.063458
8063884010.0549180.0404880.0331490.0542940.0434770.0679820.1524650.0741520.0334040.0287350.0592690.0243390.0369680.0277600.0235220.0609320.0234450.0239310.0446430.0337730.058353
8063884050.0327640.0573010.0184370.0770990.0469630.0635210.0949510.0890720.0308260.0378960.0357520.0198780.0495360.0796100.0269220.0248330.0248400.0266950.0799360.0401460.043022
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n", + "trip_id \n", + "137248721 0.056642 0.037305 0.042780 0.029576 \n", + "137248725 0.101902 0.022876 0.031227 0.069990 \n", + "211388201 0.037562 0.070033 0.055951 0.040389 \n", + "211388205 0.035480 0.056256 0.043944 0.048085 \n", + "806388401 0.054918 0.040488 0.033149 0.054294 \n", + "806388405 0.032764 0.057301 0.018437 0.077099 \n", + "\n", + " SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\n", + "trip_id \n", + "137248721 0.086154 0.026586 0.088313 0.100331 0.028021 0.018516 \n", + "137248725 0.056913 0.046685 0.088411 0.087785 0.025414 0.038162 \n", + "211388201 0.020041 0.095974 0.094678 0.094368 0.036014 0.025051 \n", + "211388205 0.042078 0.062221 0.106819 0.087265 0.035117 0.030399 \n", + "806388401 0.043477 0.067982 0.152465 0.074152 0.033404 0.028735 \n", + "806388405 0.046963 0.063521 0.094951 0.089072 0.030826 0.037896 \n", + "\n", + " WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\n", + "trip_id \n", + "137248721 0.064391 0.020009 0.026399 0.022622 0.023007 0.021754 \n", + "137248725 0.023988 0.028800 0.039318 0.020160 0.048364 0.023003 \n", + "211388201 0.034860 0.034953 0.035499 0.028040 0.043650 0.024713 \n", + "211388205 0.035154 0.026469 0.050646 0.035727 0.024284 0.032905 \n", + "806388401 0.059269 0.024339 0.036968 0.027760 0.023522 0.060932 \n", + "806388405 0.035752 0.019878 0.049536 0.079610 0.026922 0.024833 \n", + "\n", + " DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n", + "trip_id \n", + "137248721 0.087126 0.021171 0.036475 0.027650 0.135171 \n", + "137248725 0.033959 0.034597 0.037708 0.099523 0.041213 \n", + "211388201 0.031388 0.025931 0.066999 0.052678 0.051228 \n", + "211388205 0.039427 0.041092 0.054714 0.048459 0.063458 \n", + "806388401 0.023445 0.023931 0.044643 0.033773 0.058353 \n", + "806388405 0.024840 0.026695 0.079936 0.040146 0.043022 " + ] + }, + "execution_count": 767, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nested_exp_utilities_cf = simulate.compute_nested_exp_utilities(new_utils, nest_spec)\n", + "nested_probabilities_cf = simulate.compute_nested_probabilities(nested_exp_utilities_cf, nest_spec, trace_label=None)\n", + "base_probabilities_cf = simulate.compute_base_probabilities(nested_probabilities_cf, nest_spec, spec)\n", + "base_probabilities_cf" + ] + }, + { + "cell_type": "code", + "execution_count": 799, + "id": "50dfd1ae-10c3-475c-94be-de783c2fa5c2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:15:29.056902Z", + "iopub.status.busy": "2021-09-02T00:15:29.056683Z", + "iopub.status.idle": "2021-09-02T00:27:21.965206Z", + "shell.execute_reply": "2021-09-02T00:27:21.964340Z", + "shell.execute_reply.started": "2021-09-02T00:15:29.056877Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 11min 49s, sys: 766 ms, total: 11min 50s\n", + "Wall time: 11min 52s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "num_samples = 10000 # 7.5s per 100\n", + "\n", + "data_zenith = []\n", + "data_asim = []\n", + "for i in range(num_samples):\n", + " seed = rng_.integers(0, 100000)\n", + " x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True, raw_utilities=new_utils)\n", + " y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False, raw_utilities=new_utils)\n", + " data_zenith.append(x_)\n", + " data_asim.append(y_)\n", + " \n", + "data_asim = pd.concat(data_asim, axis=1)\n", + "data_zenith = pd.concat(data_zenith, axis=1)\n", + "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", + "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_asim = counts_asim / data_asim.shape[1]\n", + "\n", + "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", + "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", + "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", + "# assert np.allclose(mode_share_zenith.sum(), 1)\n", + "# assert np.allclose(mode_share_asim.sum(), 1)\n", + "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", + "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 796, + "id": "bb16f9d7-44f5-4316-b981-d65216a5e217", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:14:11.772592Z", + "iopub.status.busy": "2021-09-02T00:14:11.772347Z", + "iopub.status.idle": "2021-09-02T00:14:12.001077Z", + "shell.execute_reply": "2021-09-02T00:14:12.000166Z", + "shell.execute_reply.started": "2021-09-02T00:14:11.772556Z" + } + }, + "outputs": [], + "source": [ + "#temp_z = data_zenith.copy()\n", + "#temp_a = data_asim.copy()\n", + "#data_asim = data_asim.join(temp_a, lsuffix=\"_o\", rsuffix=\"_n\")\n", + "#data_zenith = data_zenith.join(temp_z, lsuffix=\"_o\", rsuffix=\"_n\")\n", + "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", + "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_asim = counts_asim / data_asim.shape[1]\n", + "\n", + "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", + "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", + "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", + "# assert np.allclose(mode_share_zenith.sum(), 1)\n", + "# assert np.allclose(mode_share_asim.sum(), 1)\n", + "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", + "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 805, + "id": "031d8340-0af0-4608-8a83-ed2af4aaa3b2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:41:01.948669Z", + "iopub.status.busy": "2021-09-02T00:41:01.948422Z", + "iopub.status.idle": "2021-09-02T00:41:02.175972Z", + "shell.execute_reply": "2021-09-02T00:41:02.174759Z", + "shell.execute_reply.started": "2021-09-02T00:41:01.948633Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
zenith8.0555.1674.6313.5023.6064.2663.9633.4983.8955.1764.7995.8514.9996.1214.6689.4864.2284.1252.9773.6713.317
asim6.0966.6984.9773.2023.5224.3344.1293.1843.2245.1496.2838.2964.7936.5844.5327.4844.1524.2732.6293.4333.023
probs8.8835.3214.7382.8903.1364.0033.5653.1623.7585.3244.9276.0495.3416.5415.03710.4273.9734.2242.5743.1472.979
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP DRIVE_HVY \\\n", + "zenith 8.055 5.167 4.631 3.502 3.606 4.266 \n", + "asim 6.096 6.698 4.977 3.202 3.522 4.334 \n", + "probs 8.883 5.321 4.738 2.890 3.136 4.003 \n", + "\n", + " DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY SHARED3FREE \\\n", + "zenith 3.963 3.498 3.895 5.176 4.799 \n", + "asim 4.129 3.184 3.224 5.149 6.283 \n", + "probs 3.565 3.162 3.758 5.324 4.927 \n", + "\n", + " SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK WALK_COM WALK_EXP \\\n", + "zenith 5.851 4.999 6.121 4.668 9.486 4.228 4.125 \n", + "asim 8.296 4.793 6.584 4.532 7.484 4.152 4.273 \n", + "probs 6.049 5.341 6.541 5.037 10.427 3.973 4.224 \n", + "\n", + " WALK_HVY WALK_LOC WALK_LRF \n", + "zenith 2.977 3.671 3.317 \n", + "asim 2.629 3.433 3.023 \n", + "probs 2.574 3.147 2.979 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# sample of 20000\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * mode_share_comp).T)" + ] + }, + { + "cell_type": "code", + "execution_count": 806, + "id": "aece448f-4055-4d2a-b90d-8530caec9cb9", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:43:32.971972Z", + "iopub.status.busy": "2021-09-02T00:43:32.971684Z", + "iopub.status.idle": "2021-09-02T00:43:33.223345Z", + "shell.execute_reply": "2021-09-02T00:43:33.222488Z", + "shell.execute_reply.started": "2021-09-02T00:43:32.971932Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
trip_id137248721_z137248721_a137248721
BIKE0.092700.054350.100331
DRIVEALONEFREE0.053650.050050.056642
DRIVEALONEPAY0.038800.031550.037305
DRIVE_COM0.028700.024700.021171
DRIVE_EXP0.028800.026050.021754
DRIVE_HVY0.078150.099700.087126
DRIVE_LOC0.028750.027000.022622
DRIVE_LRF0.027800.024450.023007
SHARED2FREE0.043250.023000.042780
SHARED2PAY0.033600.015850.029576
SHARED3FREE0.073650.119200.086154
SHARED3PAY0.032600.039600.026586
TAXI0.035100.050300.036475
TNC_SHARED0.126200.181150.135171
TNC_SINGLE0.026250.038450.027650
WALK0.079400.048250.088313
WALK_COM0.031850.024600.026399
WALK_EXP0.057050.058950.064391
WALK_HVY0.025200.018400.020009
WALK_LOC0.034550.027400.028021
WALK_LRF0.023950.017000.018516
\n", + "
" + ], + "text/plain": [ + "trip_id 137248721_z 137248721_a 137248721\n", + "BIKE 0.09270 0.05435 0.100331\n", + "DRIVEALONEFREE 0.05365 0.05005 0.056642\n", + "DRIVEALONEPAY 0.03880 0.03155 0.037305\n", + "DRIVE_COM 0.02870 0.02470 0.021171\n", + "DRIVE_EXP 0.02880 0.02605 0.021754\n", + "DRIVE_HVY 0.07815 0.09970 0.087126\n", + "DRIVE_LOC 0.02875 0.02700 0.022622\n", + "DRIVE_LRF 0.02780 0.02445 0.023007\n", + "SHARED2FREE 0.04325 0.02300 0.042780\n", + "SHARED2PAY 0.03360 0.01585 0.029576\n", + "SHARED3FREE 0.07365 0.11920 0.086154\n", + "SHARED3PAY 0.03260 0.03960 0.026586\n", + "TAXI 0.03510 0.05030 0.036475\n", + "TNC_SHARED 0.12620 0.18115 0.135171\n", + "TNC_SINGLE 0.02625 0.03845 0.027650\n", + "WALK 0.07940 0.04825 0.088313\n", + "WALK_COM 0.03185 0.02460 0.026399\n", + "WALK_EXP 0.05705 0.05895 0.064391\n", + "WALK_HVY 0.02520 0.01840 0.020009\n", + "WALK_LOC 0.03455 0.02740 0.028021\n", + "WALK_LRF 0.02395 0.01700 0.018516" + ] + }, + "execution_count": 806, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# TODO: look at probs per trip, i.e. do not sum across trips\n", + "trip_id = 137248721\n", + "\n", + "counts_zenith.loc[counts_zenith.index == trip_id].T.merge(\n", + " counts_asim.loc[counts_asim.index == trip_id].T, suffixes=['_z', '_a'], left_index=True, right_index=True, how='outer').merge(\n", + " base_probabilities_cf.loc[base_probabilities_cf.index == trip_id].T, suffixes=['', '_probs'], left_index=True, right_index=True, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ef84f10-d616-40e4-87e5-425499027ff3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "431cee49-40a9-4dc7-93a5-75308fb302dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fcdc0ff-c510-4878-8672-8365e046442c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/notebooks/nested_logit_frozen_rand_individ_util.ipynb b/notebooks/nested_logit_frozen_rand_individ_util.ipynb new file mode 100644 index 0000000000..2bb10981e5 --- /dev/null +++ b/notebooks/nested_logit_frozen_rand_individ_util.ipynb @@ -0,0 +1,463 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Frozen randomness\n", + "\n", + "Let's try to draw from nested logit model by drawing error terms. For logit models, this is simple because error terms are independent and therefore we can uniquely invert the CDF and simply draw from that. For nested models, this is not the case. However, we know we can write the probabilities as nested logits and therefore we think we can draw repeatedly like for logit models, taking the nesting structure into account.\n", + "\n", + "Let's start with two levels and a model where we know the probabilities, i.e. we fix the utility functions and the nesting scales, like for red bus/blue bus. We then draw like ActivitySim does, and like we want to do.\n", + "\n", + "Next, we extend to three levels, where the additional nest error term has not been derived yet." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T05:31:19.564033Z", + "iopub.status.busy": "2022-05-01T05:31:19.563663Z", + "iopub.status.idle": "2022-05-01T05:31:19.824207Z", + "shell.execute_reply": "2022-05-01T05:31:19.823432Z", + "shell.execute_reply.started": "2022-05-01T05:31:19.563960Z" + } + }, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "\n", + "import numpy as np\n", + "from numpy.random import default_rng" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T05:31:20.023960Z", + "iopub.status.busy": "2022-05-01T05:31:20.023246Z", + "iopub.status.idle": "2022-05-01T05:31:20.029538Z", + "shell.execute_reply": "2022-05-01T05:31:20.028264Z", + "shell.execute_reply.started": "2022-05-01T05:31:20.023927Z" + } + }, + "outputs": [], + "source": [ + "def logsum(utilities, nest_scale=1.0):\n", + " scaled_utils = utilities / nest_scale\n", + " max_util = np.max(scaled_utils)\n", + " return max_util + np.log(np.sum(np.exp(scaled_utils - max_util)))\n", + "\n", + "\n", + "# total probability of alternative being chosen is product of two terms:\n", + "# conditional probability of alternative given nest is chosen: exp(util / nest_scale)\n", + "# marginal probability that any alternative in nest is chosen: exp(nest_scale * logsum)\n", + " \n", + "# If you think about a single case, the probabilities are indicator variables and we take the max of each. This is what Zenith does I think.\n", + "# Given that these expressions are those of two logits, it seems natural to draw correspondingly.\n", + "# This must be related to the strict derivation of max() distributions of Hunt." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Two-level" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T09:55:14.957470Z", + "iopub.status.busy": "2022-05-01T09:55:14.957183Z", + "iopub.status.idle": "2022-05-01T09:55:14.964046Z", + "shell.execute_reply": "2022-05-01T09:55:14.963056Z", + "shell.execute_reply.started": "2022-05-01T09:55:14.957441Z" + } + }, + "outputs": [], + "source": [ + "alternatives = {1: \"car\", 2: \"blue bus\", 3: \"red bus\"}\n", + "\n", + "utility_spec = {\n", + " 1: {\"cost\": -1.0, \"asc\": 0.0},\n", + " 2: {\"cost\": -1.0, \"asc\": 0.2},\n", + " 3: {\"cost\": -1.5, \"asc\": 0.1},\n", + "}\n", + "\n", + "# blue and red bus are nested together with scale 0.5\n", + "\n", + "def utility(x, utility_spec, alternative):\n", + " assert alternative in utility_spec.keys()\n", + " return utility_spec[alternative][\"cost\"] * x + utility_spec[alternative][\"asc\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T09:55:15.467608Z", + "iopub.status.busy": "2022-05-01T09:55:15.467366Z", + "iopub.status.idle": "2022-05-01T09:55:15.478279Z", + "shell.execute_reply": "2022-05-01T09:55:15.476833Z", + "shell.execute_reply.started": "2022-05-01T09:55:15.467571Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.4452265282367507 0.5330453677531714 0.02172810401007798\n", + "1.0\n" + ] + } + ], + "source": [ + "cost = 3.0\n", + "nest_scale = 0.5\n", + "\n", + "util_3 = utility(cost, utility_spec, 3)\n", + "util_2 = utility(cost, utility_spec, 2)\n", + "logsum_bus = logsum(np.array([util_2, util_3]), nest_scale=nest_scale)\n", + "nest_util = nest_scale * logsum_bus\n", + "\n", + "util_1 = utility(cost, utility_spec, 1)\n", + "prob_1 = np.exp(util_1) / (np.exp(util_1) + np.exp(nest_util))\n", + "\n", + "nest_cond_prob = np.exp(nest_util) / (np.exp(util_1) + np.exp(nest_util))\n", + "nest_marg_prob_2 = np.exp(util_2 / nest_scale) / (np.exp(util_2 / nest_scale) + np.exp(util_3 / nest_scale))\n", + "nest_marg_prob_3 = np.exp(util_3 / nest_scale) / (np.exp(util_2 / nest_scale) + np.exp(util_3 / nest_scale))\n", + "\n", + "prob_2 = nest_cond_prob * nest_marg_prob_2\n", + "prob_3 = nest_cond_prob * nest_marg_prob_3\n", + "\n", + "print(prob_1, prob_2, prob_3)\n", + "print(sum([prob_1, prob_2, prob_3]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The ActivitySim way\n", + "Kind of pointless here but this is how we choose a single value - draw from U and pick whichever interval it falls into" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T09:55:20.523480Z", + "iopub.status.busy": "2022-05-01T09:55:20.523247Z", + "iopub.status.idle": "2022-05-01T09:55:21.341578Z", + "shell.execute_reply": "2022-05-01T09:55:21.340682Z", + "shell.execute_reply.started": "2022-05-01T09:55:20.523453Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closed form: [0.4452265282367507, 0.5330453677531714, 0.02172810401007798],\n", + "simulated: [0.4450967 0.5331544 0.0217489]\n", + "CPU times: user 766 ms, sys: 46.9 ms, total: 812 ms\n", + "Wall time: 814 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "probs = [prob_1, prob_2, prob_3]\n", + "cum_probs = [0] + list(np.cumsum(probs))\n", + "\n", + "num_draws = 10000000\n", + "\n", + "# now draw from U and put into arrays, then value count?\n", + "rng = default_rng(999)\n", + "rands = rng.uniform(size=num_draws)\n", + "\n", + "hits, bins = np.histogram(rands, bins=cum_probs)\n", + "print(f\"closed form: {probs},\\nsimulated: {hits / num_draws}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T09:55:24.027745Z", + "iopub.status.busy": "2022-05-01T09:55:24.027421Z", + "iopub.status.idle": "2022-05-01T09:55:24.034035Z", + "shell.execute_reply": "2022-05-01T09:55:24.031703Z", + "shell.execute_reply.started": "2022-05-01T09:55:24.027708Z" + } + }, + "outputs": [], + "source": [ + "def inverse_ev1_cdf(x, location=0.0, scale=1.0):\n", + " \"quantile function of EV1\"\n", + " #return location - (1.0 / scale) * np.log(-np.log(x))\n", + " # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution where the scale is proportional to variance (not variance^{-1})\n", + " return location - scale * np.log(-np.log(x))\n", + "\n", + "# for utilities with full set of ascs location=0. Do we always assume location=0 in estimation anyways?\n", + "# the scale of the error term is unidentified and therefore set to 1 in most applications, meaning the standard deviation is pi/sqrt(6)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "execution": { + "iopub.execute_input": "2022-04-17T03:38:03.583638Z", + "iopub.status.busy": "2022-04-17T03:38:03.583273Z", + "iopub.status.idle": "2022-04-17T03:38:03.588839Z", + "shell.execute_reply": "2022-04-17T03:38:03.586978Z", + "shell.execute_reply.started": "2022-04-17T03:38:03.583594Z" + } + }, + "source": [ + "## The Zenith way\n", + "\n", + "Basically, probabilities are now indicators - choose at the lowest level, then walk up. Choice is product of these. We draw error terms for each alternative, where nest roots are now alternatives too.\n", + "\n", + "OR: do we choose a" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:02:14.035272Z", + "iopub.status.busy": "2022-05-01T10:02:14.035014Z", + "iopub.status.idle": "2022-05-01T10:02:14.040376Z", + "shell.execute_reply": "2022-05-01T10:02:14.039207Z", + "shell.execute_reply.started": "2022-05-01T10:02:14.035235Z" + } + }, + "outputs": [], + "source": [ + "util_3 = utility(cost, utility_spec, 3)\n", + "util_2 = utility(cost, utility_spec, 2)\n", + "logsum_bus = logsum(np.array([util_2, util_3]), nest_scale=nest_scale)\n", + "util_1 = utility(cost, utility_spec, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:03:23.999857Z", + "iopub.status.busy": "2022-05-01T10:03:23.999571Z", + "iopub.status.idle": "2022-05-01T10:03:30.033848Z", + "shell.execute_reply": "2022-05-01T10:03:30.032689Z", + "shell.execute_reply.started": "2022-05-01T10:03:23.999816Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closed form probs: 0.445227, 0.533045, 0.021728\n", + " simulated probs: 0.4452013, 0.5330709, 0.0217278\n" + ] + } + ], + "source": [ + "# conditional error term are given by logit with scale given by nest scale\n", + "num_draws_dec = 10000000\n", + "#mu = 1.0 / nest_scale\n", + "\n", + "rng_dec = default_rng(9)\n", + "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", + "ev1_lower = inverse_ev1_cdf(rands_dec, scale=nest_scale)\n", + "\n", + "lower_utils_2 = util_2 + ev1_lower[num_draws_dec:] \n", + "lower_utils_3 = util_3 + ev1_lower[:num_draws_dec] \n", + "\n", + "#logsum_bus = logsum(np.array([lower_utils_2, lower_utils_3]), nest_scale=nest_scale)\n", + "ev1_upper = inverse_ev1_cdf(rng_dec.uniform(size=2*num_draws_dec))\n", + "nest_util = nest_scale * logsum_bus + ev1_upper[num_draws_dec:]\n", + "\n", + "full_util_1 = util_1 + ev1_upper[:num_draws_dec]\n", + "\n", + "choices = np.array([full_util_1, nest_util]).argmax(axis=0)\n", + "nest_indexes = np.nonzero(choices == 1)[0]\n", + "nest_choices = np.array([lower_utils_2[nest_indexes], lower_utils_3[nest_indexes]]).argmax(axis=0)\n", + "nest_choices += 1\n", + "choices = np.append(choices[choices == 0], nest_choices)\n", + "\n", + "vals, counts = np.unique(choices, return_counts=True)\n", + "probs_dec = {i+1: counts[i] / num_draws_dec for i in vals}\n", + "\n", + "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", + "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### the error term decomposition way\n", + "\n", + "We can decompose the error term into one for the nest and one within nests. For the example structure here, do we need to introduce artificial levels? In other terms, does car and bus nest have a common component?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "observation: when setting all utilities equal and nest scale to 0.999, the probs should be nearly exacly equal 1/3, but the probs here are about 0.5 to 0.25,0.25 which is what you get when the nest scale is very small (i.e. strongly correlated).\n", + "\n", + "When the 1/scale in quantile function is turned into scale, the values are slightly off but much closer (0.368 to 0.3165,0.3165)\n", + "\n", + "# => check scale and 1/scale everywhere" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T09:55:25.164028Z", + "iopub.status.busy": "2022-05-01T09:55:25.163822Z", + "iopub.status.idle": "2022-05-01T09:55:25.473704Z", + "shell.execute_reply": "2022-05-01T09:55:25.472525Z", + "shell.execute_reply.started": "2022-05-01T09:55:25.164005Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closed form probs: 0.445227, 0.533045, 0.021728\n", + " simulated probs: 0.284564, 0.493765, 0.221671\n", + "CPU times: user 281 ms, sys: 0 ns, total: 281 ms\n", + "Wall time: 303 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "# conditional error term are given by logit with scale given by nest scale\n", + "num_draws_dec = 1000000\n", + "\n", + "mu = 1.0 / nest_scale\n", + "\n", + "rng_dec = default_rng(9)\n", + "\n", + "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", + "ev1_dec = inverse_ev1_cdf(rands_dec, scale=mu)\n", + "\n", + "lower_level_utils_2 = np.repeat(util_2, num_draws_dec) + ev1_dec[num_draws_dec:]\n", + "lower_level_utils_3 = np.repeat(util_3, num_draws_dec) + ev1_dec[:num_draws_dec]\n", + "\n", + "\n", + "location_nest = - np.log(2.0) / mu\n", + "#location_nest = (- np.log(2.0) / mu) - ((1.0 - 1.0 / (mu + 1.0)) * 0.57721 * mu / (mu**2 - 1.0))\n", + "#print(location_nest, - np.log(2.0) / mu)\n", + "scale_nest = mu**2 / np.sqrt(mu**2 - 1.0)\n", + "nest_error_terms = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec), location=location_nest, scale=scale_nest)\n", + "\n", + "full_utils_2 = lower_level_utils_2 + nest_error_terms\n", + "full_utils_3 = lower_level_utils_3 + nest_error_terms\n", + "\n", + "# what's the distribution of error term for car? it's a degenerate nest, so bottom level is 1\n", + "# this here agrees with Bhat and Koppelman's mode choice script.\n", + "full_utils_1 = util_1 + inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec))\n", + "\n", + "choices = np.array([full_utils_1, full_utils_2, full_utils_3]).argmax(axis=0)\n", + "vals, counts = np.unique(choices, return_counts=True)\n", + "probs_dec = {i+1: counts[i] / num_draws_dec for i in vals}\n", + "\n", + "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", + "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# WHY IS THIS NOT EQUAL\n", + "\n", + "see p.12 of Hildebrandt: scale in E(nu_m) and Var(nu_m) is not equal? Does this need correction factor for location?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Three-level\n", + "\n", + "and recursive - maybe use Asim structure directly?\n", + "\n", + "\n", + "Could also use larch to apply models, would be great to add there too?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From c973be8130388a7bf061f4d58c079518bcfdcefc Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 4 May 2022 15:52:43 +1000 Subject: [PATCH 002/298] prototype work - return leaf and node (exp max) utilities, nest spec --- activitysim/core/logit.py | 1 + activitysim/core/simulate.py | 40 + notebooks/frozen_rand_mode_choice_dev.ipynb | 2291 ++++++----------- ...ested_logit_frozen_rand_individ_util.ipynb | 89 +- 4 files changed, 864 insertions(+), 1557 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index b3dace2e0b..0359baab90 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -423,6 +423,7 @@ def _each_nest(spec, parent_nest, post_order): nest.level = parent_nest.level + 1 nest.product_of_coefficients = parent_nest.product_of_coefficients nest.ancestors = parent_nest.ancestors + [name] + nest.coefficient = parent_nest.coefficient yield spec, nest diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index c14939da95..6eff3c2e9f 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -709,6 +709,46 @@ def _check_for_variability(expression_values, trace_label): logger.warning("%s: %s columns have missing values" % (trace_label, has_missing_vals)) +def compute_nested_utilities(raw_utilities, nest_spec): + """ + compute nest utilities based on nesting coefficients + + For nest nodes this is the logsum of alternatives adjusted by nesting coefficient + + leaf <- raw_utility / nest_coefficient + nest <- ln(sum of exponentiated raw_utility of leaves) * nest_coefficient) + + Parameters + ---------- + raw_utilities : pandas.DataFrame + dataframe with the raw alternative utilities of all leaves + (what in non-nested logit would be the utilities of all the alternatives) + nest_spec : dict + Nest tree dict from the model spec yaml file + + Returns + ------- + nested_utilities : pandas.DataFrame + Will have the index of `raw_utilities` and columns for leaf and node utilities + """ + nested_utilities = pd.DataFrame(index=raw_utilities.index) + + for nest in logit.each_nest(nest_spec, post_order=True): + name = nest.name + if nest.is_leaf: + nested_utilities[name] = \ + raw_utilities[name].astype(float) / nest.coefficient #nest.product_of_coefficients + else: + # the alternative nested_utilities will already have been computed due to post_order + # this will RuntimeWarning: divide by zero encountered in log + # if all nest alternative utilities are zero and produce -inf + with np.errstate(divide='ignore'): + nested_utilities[name] = \ + nest.coefficient * np.log((np.exp(nested_utilities[nest.alternatives])).sum(axis=1)) + + return nested_utilities + + def compute_nested_exp_utilities(raw_utilities, nest_spec): """ compute exponentiated nest utilities based on nesting coefficients diff --git a/notebooks/frozen_rand_mode_choice_dev.ipynb b/notebooks/frozen_rand_mode_choice_dev.ipynb index f4203e92a6..b0228ab13f 100644 --- a/notebooks/frozen_rand_mode_choice_dev.ipynb +++ b/notebooks/frozen_rand_mode_choice_dev.ipynb @@ -7,7 +7,11 @@ "source": [ "# Start work here, clean up as you go\n", "\n", - "Problem below: scale of error term on lower levels needs to be given by nest, not the case atm" + "Problem below: scale of error term on lower levels needs to be given by nest, not the case atm\n", + "\n", + "Do I remember this correctly and probabilities are calculated as products of marginal and conditional probabilities?\n", + "if so, the corresponding utilities at leaf and node levels would need to be calculated, and I would be able to use\n", + "these directly, right? CHECK, would make it much easier!" ] }, { @@ -280,7 +284,7 @@ " }\n", "\n", " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - " nest_spec = config.get_logit_model_settings(model_settings)\n", + " nest_specs = config.get_logit_model_settings(model_settings)\n", "\n", " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", "\n", @@ -313,7 +317,7 @@ " ################ Replace wrapper function\n", " # choices = mode_choice_simulate(...)\n", " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", " choices = simulate_function(\n", " choosers=trips_segment,\n", " spec=spec,\n", @@ -371,7 +375,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -381,82 +385,267 @@ "shell.execute_reply": "2021-09-01T02:29:01.222865Z", "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" }, + "tags": [], + "pycharm": { + "is_executing": true + } + }, + "outputs": [], + "source": [ + "trips_df = run_trip_mode_choice()" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "7eae2854-38bd-4da0-a161-3a15bbd17177", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:30:57.562848Z", + "iopub.status.busy": "2021-08-31T10:30:57.562615Z", + "iopub.status.idle": "2021-08-31T10:30:57.806655Z", + "shell.execute_reply": "2021-08-31T10:30:57.805135Z", + "shell.execute_reply.started": "2021-08-31T10:30:57.562824Z" + }, "tags": [] }, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'atwork' (27 trips)\n" - ] + "data": { + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n8684833 26478 26478 eatout 1 True \n8684837 26478 26478 eatout 1 False \n8685009 26478 26478 othmaint 1 True \n8685013 26478 26478 othmaint 1 False \n8753057 26686 26686 eatout 1 True \n... ... ... ... ... ... \n2472945113 7539466 2848131 shopping 1 True \n2472945117 7539466 2848131 shopping 1 False \n2472945118 7539466 2848131 shopping 2 False \n2473024473 7539708 2848373 univ 1 True \n2473024477 7539708 2848373 univ 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n8684833 1 13 8 1085604 eatout \n8684837 1 8 13 1085604 home \n8685009 1 10 8 1085626 othmaint \n8685013 1 8 10 1085626 home \n8753057 1 5 8 1094132 eatout \n... ... ... ... ... ... \n2472945113 1 8 3 309118139 shopping \n2472945117 2 25 8 309118139 shopping \n2472945118 2 3 25 309118139 home \n2473024473 1 13 18 309128059 univ \n2473024477 1 18 13 309128059 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n8684833 NaN 11.0 WALK -1.171760 \n8684837 NaN 11.0 WALK -1.238719 \n8685009 NaN 12.0 BIKE 6.198626 \n8685013 NaN 13.0 BIKE 6.175681 \n8753057 NaN 19.0 WALK 4.457539 \n... ... ... ... ... \n2472945113 NaN 18.0 WALK_LOC 12.537675 \n2472945117 56.842247 21.0 WALK_LOC 11.880804 \n2472945118 NaN 22.0 WALK 13.710030 \n2473024473 NaN 16.0 WALK_LOC -0.530696 \n2473024477 NaN 23.0 WALK_LRF 0.624304 \n\n[482 rows x 14 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
86848332647826478eatout1True11381085604eatoutNaN11.0WALK-1.171760
86848372647826478eatout1False18131085604homeNaN11.0WALK-1.238719
86850092647826478othmaint1True11081085626othmaintNaN12.0BIKE6.198626
86850132647826478othmaint1False18101085626homeNaN13.0BIKE6.175681
87530572668626686eatout1True1581094132eatoutNaN19.0WALK4.457539
.............................................
247294511375394662848131shopping1True183309118139shoppingNaN18.0WALK_LOC12.537675
247294511775394662848131shopping1False2258309118139shopping56.84224721.0WALK_LOC11.880804
247294511875394662848131shopping2False2325309118139homeNaN22.0WALK13.710030
247302447375397082848373univ1True11318309128059univNaN16.0WALK_LOC-0.530696
247302447775397082848373univ1False11813309128059homeNaN23.0WALK_LRF0.624304
\n

482 rows × 14 columns

\n
" + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trips_df" + ] + }, + { + "cell_type": "markdown", + "id": "8eebea31-499c-41ca-8411-883a88ca800a", + "metadata": {}, + "source": [ + "## nested dev" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "3e2513e9-c03f-4891-9a1c-4053a4440a10", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:31:01.113060Z", + "iopub.status.busy": "2021-08-31T10:31:01.112829Z", + "iopub.status.idle": "2021-08-31T10:31:01.359912Z", + "shell.execute_reply": "2021-08-31T10:31:01.358948Z", + "shell.execute_reply.started": "2021-08-31T10:31:01.113034Z" }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] + "tags": [] + }, + "outputs": [], + "source": [ + "# see fct above - return if necessary\n", + "#spec = simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + "#nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "36c100b3-3e39-4950-a586-4d42be695eaa", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T10:31:01.775068Z", + "iopub.status.busy": "2021-08-31T10:31:01.774846Z", + "iopub.status.idle": "2021-08-31T10:31:01.975816Z", + "shell.execute_reply": "2021-08-31T10:31:01.974499Z", + "shell.execute_reply.started": "2021-08-31T10:31:01.775044Z" }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'eatout' (33 trips)\n" - ] + "tags": [] + }, + "outputs": [], + "source": [ + "#print(nest_spec)\n", + "#for nest in logit.each_nest(nest_spec):\n", + "# nest.print()" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "e8d11f6a-f82a-40bd-8eef-fc28bcca8252", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:59:35.109079Z", + "iopub.status.busy": "2021-09-01T01:59:35.108781Z", + "iopub.status.idle": "2021-09-01T01:59:35.319301Z", + "shell.execute_reply": "2021-09-01T01:59:35.318402Z", + "shell.execute_reply.started": "2021-09-01T01:59:35.109050Z" }, + "tags": [] + }, + "outputs": [], + "source": [ + "def eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, estimator,\n", + " log_alt_losers=False,\n", + " want_logsums=False, trace_label=None,\n", + " trace_choice_name=None, trace_column_names=None):\n", + "\n", + " trace_label = tracing.extend_trace_label(trace_label, 'eval_nl')\n", + " assert trace_label\n", + " have_trace_targets = tracing.has_trace_targets(choosers)\n", + "\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + "\n", + " if have_trace_targets:\n", + " tracing.trace_df(choosers, '%s.choosers' % trace_label)\n", + "\n", + " raw_utilities = simulate.eval_utilities(spec, choosers, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=have_trace_targets,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + " chunk.log_df(trace_label, \"raw_utilities\", raw_utilities)\n", + "\n", + " if have_trace_targets:\n", + " tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label,\n", + " column_labels=['alternative', 'utility'])\n", + "\n", + " # exponentiated utilities of leaves and nests\n", + " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + " chunk.log_df(trace_label, \"nested_exp_utilities\", nested_exp_utilities)\n", + "\n", + " del raw_utilities\n", + " chunk.log_df(trace_label, 'raw_utilities', None)\n", + "\n", + " if have_trace_targets:\n", + " tracing.trace_df(nested_exp_utilities, '%s.nested_exp_utilities' % trace_label,\n", + " column_labels=['alternative', 'utility'])\n", + "\n", + " # probabilities of alternatives relative to siblings sharing the same nest\n", + " nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec,\n", + " trace_label=trace_label)\n", + " chunk.log_df(trace_label, \"nested_probabilities\", nested_probabilities)\n", + "\n", + " if want_logsums:\n", + " # logsum of nest root\n", + " logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index)\n", + " chunk.log_df(trace_label, \"logsums\", logsums)\n", + "\n", + " del nested_exp_utilities\n", + " chunk.log_df(trace_label, 'nested_exp_utilities', None)\n", + "\n", + " if have_trace_targets:\n", + " tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label,\n", + " column_labels=['alternative', 'probability'])\n", + "\n", + " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", + " chunk.log_df(trace_label, \"base_probabilities\", base_probabilities)\n", + "\n", + " del nested_probabilities\n", + " chunk.log_df(trace_label, 'nested_probabilities', None)\n", + "\n", + " if have_trace_targets:\n", + " tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label,\n", + " column_labels=['alternative', 'probability'])\n", + "\n", + " # note base_probabilities could all be zero since we allowed all probs for nests to be zero\n", + " # check here to print a clear message but make_choices will raise error if probs don't sum to 1\n", + " BAD_PROB_THRESHOLD = 0.001\n", + " no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD\n", + "\n", + " if no_choices.any():\n", + "\n", + " logit.report_bad_choices(\n", + " no_choices, base_probabilities,\n", + " trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'),\n", + " trace_choosers=choosers,\n", + " msg=\"base_probabilities do not sum to one\")\n", + "\n", + " if custom_chooser:\n", + " choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec,\n", + " trace_label=trace_label)\n", + " else:\n", + " choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)\n", + "\n", + " del base_probabilities\n", + " chunk.log_df(trace_label, 'base_probabilities', None)\n", + "\n", + " if have_trace_targets:\n", + " tracing.trace_df(choices, '%s.choices' % trace_label,\n", + " columns=[None, trace_choice_name])\n", + " tracing.trace_df(rands, '%s.rands' % trace_label,\n", + " columns=[None, 'rand'])\n", + " if want_logsums:\n", + " tracing.trace_df(logsums, '%s.logsums' % trace_label,\n", + " columns=[None, 'logsum'])\n", + "\n", + " if want_logsums:\n", + " choices = choices.to_frame('choice')\n", + " choices['logsum'] = logsums\n", + "\n", + " return choices\n", + "\n", + "\n", + "def simple_simulate_dev(choosers, spec, nest_spec,\n", + " skims=None, locals_d=None,\n", + " chunk_size=0, custom_chooser=None,\n", + " log_alt_losers=False,\n", + " want_logsums=False,\n", + " estimator=None,\n", + " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", + " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", + " assert len(choosers) > 0\n", + " result_list = []\n", + " # segment by person type and pick the right spec for each person type\n", + " for i, chooser_chunk, chunk_trace_label \\\n", + " in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", + " # the following replaces choices = _simple_simulate(...)\n", + " if skims is not None:\n", + " simulate.set_skim_wrapper_targets(choosers, skims)\n", + "\n", + " # only do this for nested, logit is straight forward\n", + " assert nest_spec is not None\n", + " choices = eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser,\n", + " log_alt_losers=log_alt_losers,\n", + " want_logsums=want_logsums,\n", + " estimator=estimator,\n", + " trace_label=trace_label,\n", + " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", + "\n", + "\n", + " result_list.append(choices)\n", + " chunk.log_df(trace_label, f'result_list', result_list)\n", + "\n", + " if len(result_list) > 1:\n", + " choices = pd.concat(result_list)\n", + " assert len(choices.index == len(choosers.index))\n", + " return choices\n" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "ec4eb1ff-2f35-4919-96b4-5acffa01f597", + "metadata": {}, + "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ + "Running with %d trips 482\n", "trip_mode_choice tour_type 'escort' (6 trips)\n" ] }, @@ -486,67 +675,303 @@ "name": "stdout", "output_type": "stream", "text": [ - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + "Done\n" ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'othmaint' (46 trips)\n" - ] - }, + "data": { + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n137248721 418441 304036 escort 1 True \n137248725 418441 304036 escort 1 False \n211388201 644476 386761 escort 1 True \n211388205 644476 386761 escort 1 False \n806388401 2458501 1173905 escort 1 True \n806388405 2458501 1173905 escort 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n137248721 1 7 10 17156090 escort \n137248725 1 10 7 17156090 home \n211388201 1 11 16 26423525 escort \n211388205 1 16 11 26423525 home \n806388401 1 16 8 100798550 escort \n806388405 1 8 16 100798550 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n137248721 NaN 7.0 WALK 11.435800 \n137248725 NaN 7.0 WALK 11.480440 \n211388201 NaN 5.0 WALK_LOC 4.789158 \n211388205 NaN 6.0 WALK_LOC 5.050171 \n806388401 NaN 15.0 WALK_LOC 6.451457 \n806388405 NaN 16.0 WALK_LOC 6.446188 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
137248721418441304036escort1True171017156090escortNaN7.0WALK11.435800
137248725418441304036escort1False110717156090homeNaN7.0WALK11.480440
211388201644476386761escort1True1111626423525escortNaN5.0WALK_LOC4.789158
211388205644476386761escort1False1161126423525homeNaN6.0WALK_LOC5.050171
80638840124585011173905escort1True1168100798550escortNaN15.0WALK_LOC6.451457
80638840524585011173905escort1False1816100798550homeNaN16.0WALK_LOC6.446188
\n
" + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_trips = run_trip_mode_choice(do_these_purposes=[\"escort\"], simulate_function=simple_simulate_dev)\n", + "test_trips" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Get raw utilities, etc" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 105, + "outputs": [], + "source": [ + "def eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, estimator,\n", + " log_alt_losers=False,\n", + " want_logsums=False, trace_label=None,\n", + " trace_choice_name=None, trace_column_names=None):\n", + "\n", + " trace_label = tracing.extend_trace_label(trace_label, 'eval_nl')\n", + " assert trace_label\n", + " have_trace_targets = tracing.has_trace_targets(choosers)\n", + "\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, choosers, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=have_trace_targets,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + " # exponentiated utilities of leaves and nests\n", + " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + " nested_utils = simulate.compute_nested_utilities(raw_utilities, nest_spec)\n", + " # probabilities of alternatives relative to siblings sharing the same nest\n", + " nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec,\n", + " trace_label=trace_label)\n", + " if want_logsums:\n", + " # logsum of nest root\n", + " logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index)\n", + " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", + " # note base_probabilities could all be zero since we allowed all probs for nests to be zero\n", + " # check here to print a clear message but make_choices will raise error if probs don't sum to 1\n", + " BAD_PROB_THRESHOLD = 0.001\n", + " no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD\n", + " if no_choices.any():\n", + " print(\"BAD\")\n", + " choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)\n", + " if want_logsums:\n", + " choices = choices.to_frame('choice')\n", + " choices['logsum'] = logsums\n", + " return choices, raw_utilities, nested_exp_utilities, nested_utils\n", + "\n", + "\n", + "def simple_simulate_dev(choosers, spec, nest_spec,\n", + " skims=None, locals_d=None,\n", + " chunk_size=0, custom_chooser=None,\n", + " log_alt_losers=False,\n", + " want_logsums=False,\n", + " estimator=None,\n", + " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", + " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", + " assert len(choosers) > 0\n", + " result_list = []\n", + " # segment by person type and pick the right spec for each person type\n", + " for i, chooser_chunk, chunk_trace_label in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", + " # the following replaces choices = _simple_simulate(...)\n", + " if skims is not None:\n", + " simulate.set_skim_wrapper_targets(choosers, skims)\n", + "\n", + " # only do this for nested, logit is straight forward\n", + " assert nest_spec is not None\n", + " choices, raw_utilities, nested_exp_utilities, nested_utils = eval_nl_dev(choosers, spec, nest_spec, locals_d,\n", + " custom_chooser,\n", + " log_alt_losers=log_alt_losers,\n", + " want_logsums=want_logsums,\n", + " estimator=estimator,\n", + " trace_label=trace_label,\n", + " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", + "\n", + "\n", + " result_list.append(choices)\n", + " chunk.log_df(trace_label, f'result_list', result_list)\n", + "\n", + " if len(result_list) > 1:\n", + " choices = pd.concat(result_list)\n", + " assert len(choices.index == len(choosers.index))\n", + " return choices, raw_utilities, nested_exp_utilities, nested_utils\n", + "\n", + "\n", + "def get_stuff(do_these_purposes=None):\n", + " #do_these_purposes=['escort']\n", + " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", + " resume_after = \"trip_scheduling\"\n", + " model_name = \"trip_mode_choice\"\n", + " chunk_size = 0 # test_mtc means no chunking\n", + "\n", + " pipeline.open_pipeline(resume_after)\n", + " # preload any bulky injectables (e.g. skims) not in pipeline\n", + " inject.get_injectable('preload_injectables', None)\n", + " pipeline._PIPELINE.rng().begin_step(model_name)\n", + " step_name = model_name\n", + " args = {}\n", + " checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + " inject.set_step_args(args)\n", + "\n", + " trips = inject.get_table('trips')\n", + " tours_merged = inject.get_table('tours_merged')\n", + " network_los = inject.get_injectable('network_los')\n", + "\n", + " trace_label = 'trip_mode_choice'\n", + " model_settings_file_name = 'trip_mode_choice.yaml'\n", + " model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + " mode_column_name = 'trip_mode'\n", + "\n", + " trips_df = trips.to_frame()\n", + " print(\"Running with %d trips\", trips_df.shape[0])\n", + "\n", + " tours_merged = tours_merged.to_frame()\n", + " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "\n", + " # - trips_merged - merge trips and tours_merged\n", + " trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + " assert trips_merged.index.equals(trips.index)\n", + "\n", + " # setup skim keys\n", + " assert ('trip_period' not in trips_merged)\n", + " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + " orig_col = 'origin'\n", + " dest_col = 'destination'\n", + "\n", + " constants = {}\n", + " constants.update(config.get_model_constants(model_settings))\n", + " constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + " })\n", + "\n", + " skim_dict = network_los.get_default_skim_dict()\n", + "\n", + " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + " skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + " }\n", + "\n", + " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + " nest_specs = config.get_logit_model_settings(model_settings)\n", + "\n", + " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + " choices_list = []\n", + " raw_util_list = []\n", + " nest_list = []\n", + " nu_list = []\n", + " nest_spec_list = []\n", + "\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + "\n", + " if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", + " continue\n", + "\n", + " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " (primary_purpose, len(trips_segment.index), ))\n", + "\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " ################ Replace wrapper function\n", + " # choices = mode_choice_simulate(...)\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", + " choices, raw_utilities, nested_exp_utilities, nested_utils = simple_simulate_dev(\n", + " choosers=trips_segment,\n", + " spec=spec,\n", + " nest_spec=nest_spec,\n", + " skims=skims,\n", + " locals_d=locals_dict,\n", + " chunk_size=chunk_size,\n", + " want_logsums=logsum_column_name is not None,\n", + " trace_label=segment_trace_label,\n", + " trace_choice_name='trip_mode_choice',\n", + " estimator=estimator,\n", + " trace_column_names=None)\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " alts = spec.columns\n", + " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", + " ################\n", + " choices_list.append(choices)\n", + " raw_util_list.append(raw_utilities)\n", + " nest_list.append(nested_exp_utilities)\n", + " nu_list.append(nested_utils)\n", + " nest_spec_list.append(nest_spec)\n", + "\n", + " choices_df_asim = pd.concat(choices_list)\n", + "\n", + " # update trips table with choices (and potionally logssums)\n", + " trips_df = trips.to_frame()\n", + "\n", + " if (do_these_purposes is not None):\n", + " trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", + "\n", + " assign_in_place(trips_df, choices_df_asim)\n", + " assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + " finalise = True\n", + " if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + " print(\"Done\")\n", + "\n", + " return trips_df, raw_util_list, nest_list, nu_list, nest_spec_list" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 106, + "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "trip_mode_choice tour_type 'school' (37 trips)\n" + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n" ] }, { @@ -568,7 +993,6 @@ "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" ] }, @@ -576,1317 +1000,239 @@ "name": "stdout", "output_type": "stream", "text": [ - "trip_mode_choice tour_type 'shopping' (77 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'social' (19 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'univ' (26 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'work' (168 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n" + "Done\n" ] } ], "source": [ - "trips_df = run_trip_mode_choice()" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "7eae2854-38bd-4da0-a161-3a15bbd17177", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:30:57.562848Z", - "iopub.status.busy": "2021-08-31T10:30:57.562615Z", - "iopub.status.idle": "2021-08-31T10:30:57.806655Z", - "shell.execute_reply": "2021-08-31T10:30:57.805135Z", - "shell.execute_reply.started": "2021-08-31T10:30:57.562824Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n8684833 26478 26478 eatout 1 True \n8684837 26478 26478 eatout 1 False \n8685009 26478 26478 othmaint 1 True \n8685013 26478 26478 othmaint 1 False \n8753057 26686 26686 eatout 1 True \n... ... ... ... ... ... \n2472945113 7539466 2848131 shopping 1 True \n2472945117 7539466 2848131 shopping 1 False \n2472945118 7539466 2848131 shopping 2 False \n2473024473 7539708 2848373 univ 1 True \n2473024477 7539708 2848373 univ 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n8684833 1 13 8 1085604 eatout \n8684837 1 8 13 1085604 home \n8685009 1 10 8 1085626 othmaint \n8685013 1 8 10 1085626 home \n8753057 1 5 8 1094132 eatout \n... ... ... ... ... ... \n2472945113 1 8 3 309118139 shopping \n2472945117 2 25 8 309118139 shopping \n2472945118 2 3 25 309118139 home \n2473024473 1 13 18 309128059 univ \n2473024477 1 18 13 309128059 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n8684833 NaN 11.0 WALK -1.171760 \n8684837 NaN 11.0 WALK -1.238719 \n8685009 NaN 12.0 BIKE 6.198626 \n8685013 NaN 13.0 BIKE 6.175681 \n8753057 NaN 19.0 WALK 4.457539 \n... ... ... ... ... \n2472945113 NaN 18.0 WALK_LOC 12.537675 \n2472945117 56.842247 21.0 WALK_LOC 11.880804 \n2472945118 NaN 22.0 WALK 13.710030 \n2473024473 NaN 16.0 WALK_LOC -0.530696 \n2473024477 NaN 23.0 WALK_LRF 0.624304 \n\n[482 rows x 14 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
86848332647826478eatout1True11381085604eatoutNaN11.0WALK-1.171760
86848372647826478eatout1False18131085604homeNaN11.0WALK-1.238719
86850092647826478othmaint1True11081085626othmaintNaN12.0BIKE6.198626
86850132647826478othmaint1False18101085626homeNaN13.0BIKE6.175681
87530572668626686eatout1True1581094132eatoutNaN19.0WALK4.457539
.............................................
247294511375394662848131shopping1True183309118139shoppingNaN18.0WALK_LOC12.537675
247294511775394662848131shopping1False2258309118139shopping56.84224721.0WALK_LOC11.880804
247294511875394662848131shopping2False2325309118139homeNaN22.0WALK13.710030
247302447375397082848373univ1True11318309128059univNaN16.0WALK_LOC-0.530696
247302447775397082848373univ1False11813309128059homeNaN23.0WALK_LRF0.624304
\n

482 rows × 14 columns

\n
" - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } + "t, ru, neu, nu, ns = get_stuff(do_these_purposes=[\"escort\"])" ], - "source": [ - "trips_df" - ] - }, - { - "cell_type": "markdown", - "id": "8eebea31-499c-41ca-8411-883a88ca800a", - "metadata": {}, - "source": [ - "## nested dev" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "3e2513e9-c03f-4891-9a1c-4053a4440a10", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:31:01.113060Z", - "iopub.status.busy": "2021-08-31T10:31:01.112829Z", - "iopub.status.idle": "2021-08-31T10:31:01.359912Z", - "shell.execute_reply": "2021-08-31T10:31:01.358948Z", - "shell.execute_reply.started": "2021-08-31T10:31:01.113034Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# see fct above - return if necessary\n", - "#spec = simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - "#nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "36c100b3-3e39-4950-a586-4d42be695eaa", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:31:01.775068Z", - "iopub.status.busy": "2021-08-31T10:31:01.774846Z", - "iopub.status.idle": "2021-08-31T10:31:01.975816Z", - "shell.execute_reply": "2021-08-31T10:31:01.974499Z", - "shell.execute_reply.started": "2021-08-31T10:31:01.775044Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#print(nest_spec)\n", - "#for nest in logit.each_nest(nest_spec):\n", - "# nest.print()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "e8d11f6a-f82a-40bd-8eef-fc28bcca8252", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:59:35.109079Z", - "iopub.status.busy": "2021-09-01T01:59:35.108781Z", - "iopub.status.idle": "2021-09-01T01:59:35.319301Z", - "shell.execute_reply": "2021-09-01T01:59:35.318402Z", - "shell.execute_reply.started": "2021-09-01T01:59:35.109050Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def simple_simulate_dev(choosers, spec, nest_spec,\n", - " skims=None, locals_d=None,\n", - " chunk_size=0, custom_chooser=None,\n", - " log_alt_losers=False,\n", - " want_logsums=False,\n", - " estimator=None,\n", - " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", - " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", - " assert len(choosers) > 0\n", - " result_list = []\n", - " # segment by person type and pick the right spec for each person type\n", - " for i, chooser_chunk, chunk_trace_label \\\n", - " in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", - " # the following replaces choices = _simple_simulate(...)\n", - " if skims is not None:\n", - " simulate.set_skim_wrapper_targets(choosers, skims)\n", - "\n", - " # only do this for nested, logit is straight forward\n", - " assert nest_spec is not None\n", - " choices = simulate.eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser,\n", - " log_alt_losers=log_alt_losers,\n", - " want_logsums=want_logsums,\n", - " estimator=estimator,\n", - " trace_label=trace_label,\n", - " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", - "\n", - "\n", - " result_list.append(choices)\n", - " chunk.log_df(trace_label, f'result_list', result_list)\n", - "\n", - " if len(result_list) > 1:\n", - " choices = pd.concat(result_list)\n", - " assert len(choices.index == len(choosers.index))\n", - " return choices\n" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "ec4eb1ff-2f35-4919-96b4-5acffa01f597", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n" - ] - } - ], - "source": [ - "test_trips = run_trip_mode_choice(do_these_purposes=[\"escort\"], simulate_function=simple_simulate_dev)" - ] - }, - { - "cell_type": "markdown", - "id": "c60656db-075a-4eab-96b3-fc54eb785075", - "metadata": {}, - "source": [ - "## eval_nl dev" - ] - }, - { - "cell_type": "code", - "execution_count": 372, - "id": "56c8923c-e946-48d8-b964-87695c30163d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:27:40.290389Z", - "iopub.status.busy": "2021-09-01T02:27:40.290139Z", - "iopub.status.idle": "2021-09-01T02:27:40.977526Z", - "shell.execute_reply": "2021-09-01T02:27:40.976594Z", - "shell.execute_reply.started": "2021-09-01T02:27:40.290363Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# replaces simple_simulate_dev:\n", - "\n", - "want_logsums = True\n", - "trace_choice_name='trip_mode_choice'\n", - "trace_column_names=None\n", - "log_alt_losers=False\n", - "\n", - "tr_label = tracing.extend_trace_label(segment_trace_label, 'simple_simulate')\n", - "assert len(trips_segment) > 0\n", - "result_list = []\n", - "# segment by person type and pick the right spec for each person type\n", - "for i, chooser_chunk, chunk_trace_label \\\n", - " in chunk.adaptive_chunked_choosers(trips_segment, chunk_size, tr_label):\n", - " # the following replaces choices = _simple_simulate(...)\n", - " if skims is not None:\n", - " simulate.set_skim_wrapper_targets(trips_segment, skims)\n", - "\n", - " # only do this for nested, logit is straight forward\n", - " assert nest_spec is not None\n", - " choices = simulate.eval_nl(trips_segment, spec, nest_spec, locals_dict, custom_chooser=None,\n", - " log_alt_losers=log_alt_losers,\n", - " want_logsums=want_logsums,\n", - " estimator=estimator,\n", - " trace_label=tr_label,\n", - " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", - "\n", - "\n", - " result_list.append(choices)\n", - "\n", - "if len(result_list) > 1:\n", - " choices = pd.concat(result_list)\n", - "assert len(choices.index == len(trips_segment.index))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "simple_simulate_dev" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 93, - "id": "cbc2dfde-1bd9-4b03-96d9-ca9b7222d36c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T12:52:34.544865Z", - "iopub.status.busy": "2021-08-31T12:52:34.544586Z", - "iopub.status.idle": "2021-08-31T12:52:35.159615Z", - "shell.execute_reply": "2021-08-31T12:52:35.158682Z", - "shell.execute_reply.started": "2021-08-31T12:52:34.544840Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "locals_d = locals_dict\n", - "custom_chooser=None\n", - "\n", - "trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - "logit.validate_nest_spec(nest_spec, trace_label)\n", - "\n", - "raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - "nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - "\n", - "nested_probabilities = \\\n", - " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", - "\n", - "logsums = pd.Series(np.log(nested_exp_utilities.root), index=trips_segment.index)\n", - "\n", - "# global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - "base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", - "\n", - "choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)" - ] - }, - { - "cell_type": "code", - "execution_count": 95, - "id": "0cd9593b-b3fd-41f6-a7ca-4c5b73b63571", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T12:52:45.509057Z", - "iopub.status.busy": "2021-08-31T12:52:45.508819Z", - "iopub.status.idle": "2021-08-31T12:52:45.721956Z", - "shell.execute_reply": "2021-08-31T12:52:45.721064Z", - "shell.execute_reply.started": "2021-08-31T12:52:45.509021Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 0.990308\n", - "137248725 0.108433\n", - "211388201 0.806612\n", - "211388205 0.195994\n", - "806388401 0.854020\n", - "806388405 0.660550\n", - "dtype: float64" - ] - }, - "execution_count": 95, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rands" - ] - }, - { - "cell_type": "code", - "execution_count": 92, - "id": "b467330b-f1cc-43bc-9325-c6dfeaa9b364", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T12:50:51.735273Z", - "iopub.status.busy": "2021-08-31T12:50:51.734962Z", - "iopub.status.idle": "2021-08-31T12:50:52.023440Z", - "shell.execute_reply": "2021-08-31T12:50:52.022582Z", - "shell.execute_reply.started": "2021-08-31T12:50:51.735231Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.798480-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.360270-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.435800
137248725-1.732971-3966.018685-0.606540-0.324578-3964.610293-0.113602-3966.009714-7930.295428-inf0.26146615.945036-1368.16910311.480426-2737.292402-5512.292402-5512.292402-5512.292402-5512.292402-inf-2737.782457-5522.136798-5522.136798-5516.076156-5525.080218-inf-inf-24.341318-25.104291-24.140831-8.40687011.480440
211388201-3966.000225-7930.285939-inf-24.120135-3988.405850-8.442047-29.480718-3993.766432-10.318251-5.975663-4.216264-1381.397292-3.03571013.295720-2761.150907-2761.150907-2761.150907-2761.1509076.647860-2761.648763-5543.048131-5543.048131-2769.692185-2773.867648-inf4.786459-15.861515-3.864497-5.068360-1.2967574.789158
211388205-3967.791945-7932.077659-inf-25.129596-3989.415310-8.795359-30.177275-3994.462989-10.562046-6.219063-4.216264-1381.397292-3.03571014.022438-2760.977562-2760.977562-2760.977562-2760.9775627.011219-2761.294814-5546.132585-5546.132585-2764.829835-2774.132982-inf5.048078-15.776424-3.845077-5.005482-1.2860945.050171
806388401-3966.492870-7930.778584-inf-12.618378-3976.904092-4.416432-14.908422-3979.194136-5.217948-2.9129776.192499-1378.4807234.45859917.513379-2755.845870-2755.208370-2755.728926-2755.7930938.756690-2757.559519-5540.685990-5540.685990-5542.838278-5546.780761-inf6.304817-19.143696-19.495908-16.361074-5.8539176.451457
806388405-3969.835821-7934.121535-inf-14.515916-3978.801631-5.080571-16.227796-3980.513510-5.679728-3.3428055.998749-1378.5194734.31909917.553369-2755.987742-2755.350242-2755.870797-2755.9349648.776685-2757.571454-5542.673358-5542.673358-5537.799228-5547.515590-inf6.319213-18.886576-19.495133-16.300395-5.8285406.446188
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", - "trip_id \n", - "137248721 -1.798480 -3966.084195 -0.629468 -0.389611 \n", - "137248725 -1.732971 -3966.018685 -0.606540 -0.324578 \n", - "211388201 -3966.000225 -7930.285939 -inf -24.120135 \n", - "211388205 -3967.791945 -7932.077659 -inf -25.129596 \n", - "806388401 -3966.492870 -7930.778584 -inf -12.618378 \n", - "806388405 -3969.835821 -7934.121535 -inf -14.515916 \n", - "\n", - " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", - "trip_id \n", - "137248721 -3964.675326 -0.136364 -3966.074556 -7930.360270 -inf \n", - "137248725 -3964.610293 -0.113602 -3966.009714 -7930.295428 -inf \n", - "211388201 -3988.405850 -8.442047 -29.480718 -3993.766432 -10.318251 \n", - "211388205 -3989.415310 -8.795359 -30.177275 -3994.462989 -10.562046 \n", - "806388401 -3976.904092 -4.416432 -14.908422 -3979.194136 -5.217948 \n", - "806388405 -3978.801631 -5.080571 -16.227796 -3980.513510 -5.679728 \n", - "\n", - " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", - "trip_id \n", - "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", - "137248725 0.261466 15.945036 -1368.169103 11.480426 -2737.292402 \n", - "211388201 -5.975663 -4.216264 -1381.397292 -3.035710 13.295720 \n", - "211388205 -6.219063 -4.216264 -1381.397292 -3.035710 14.022438 \n", - "806388401 -2.912977 6.192499 -1378.480723 4.458599 17.513379 \n", - "806388405 -3.342805 5.998749 -1378.519473 4.319099 17.553369 \n", - "\n", - " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", - "trip_id \n", - "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", - "137248725 -5512.292402 -5512.292402 -5512.292402 -5512.292402 -inf \n", - "211388201 -2761.150907 -2761.150907 -2761.150907 -2761.150907 6.647860 \n", - "211388205 -2760.977562 -2760.977562 -2760.977562 -2760.977562 7.011219 \n", - "806388401 -2755.845870 -2755.208370 -2755.728926 -2755.793093 8.756690 \n", - "806388405 -2755.987742 -2755.350242 -2755.870797 -2755.934964 8.776685 \n", - "\n", - " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", - "trip_id \n", - "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", - "137248725 -2737.782457 -5522.136798 -5522.136798 -5516.076156 -5525.080218 \n", - "211388201 -2761.648763 -5543.048131 -5543.048131 -2769.692185 -2773.867648 \n", - "211388205 -2761.294814 -5546.132585 -5546.132585 -2764.829835 -2774.132982 \n", - "806388401 -2757.559519 -5540.685990 -5540.685990 -5542.838278 -5546.780761 \n", - "806388405 -2757.571454 -5542.673358 -5542.673358 -5537.799228 -5547.515590 \n", - "\n", - " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", - "trip_id \n", - "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", - "137248725 -inf -inf -24.341318 -25.104291 -24.140831 -8.406870 \n", - "211388201 -inf 4.786459 -15.861515 -3.864497 -5.068360 -1.296757 \n", - "211388205 -inf 5.048078 -15.776424 -3.845077 -5.005482 -1.286094 \n", - "806388401 -inf 6.304817 -19.143696 -19.495908 -16.361074 -5.853917 \n", - "806388405 -inf 6.319213 -18.886576 -19.495133 -16.300395 -5.828540 \n", - "\n", - " root \n", - "trip_id \n", - "137248721 11.435800 \n", - "137248725 11.480440 \n", - "211388201 4.789158 \n", - "211388205 5.050171 \n", - "806388401 6.451457 \n", - "806388405 6.446188 " - ] - }, - "execution_count": 92, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# root is logsum here, no need to take log again as for exponentiated utils\n", - "simulate.compute_nested_exp_utilities(raw_utilities, nest_spec, exponentiate=False)" - ] - }, - { - "cell_type": "markdown", - "id": "5c9ba018-167d-497f-ac65-f7c49036ee58", - "metadata": {}, - "source": [ - "#### scale of utils, including emus" - ] - }, - { - "cell_type": "code", - "execution_count": 283, - "id": "9febf6ef-7c6b-4f3c-bdd9-aa3326a82e95", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T23:23:00.020038Z", - "iopub.status.busy": "2021-08-31T23:23:00.019377Z", - "iopub.status.idle": "2021-08-31T23:23:00.220889Z", - "shell.execute_reply": "2021-08-31T23:23:00.220154Z", - "shell.execute_reply.started": "2021-08-31T23:23:00.019997Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "nests_ = list(logit.each_nest(nest_spec))" - ] - }, - { - "cell_type": "code", - "execution_count": 284, - "id": "48ff920e-0c9b-4b69-9cee-eb3c3427bd4f", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T23:23:01.085713Z", - "iopub.status.busy": "2021-08-31T23:23:01.085486Z", - "iopub.status.idle": "2021-08-31T23:23:01.283509Z", - "shell.execute_reply": "2021-08-31T23:23:01.282444Z", - "shell.execute_reply.started": "2021-08-31T23:23:01.085685Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] alternatives: ['DRIVEALONE', 'SHAREDRIDE2', 'SHAREDRIDE3']\n", - "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] alternatives: ['DRIVEALONEFREE', 'DRIVEALONEPAY']\n", - "Nest name: DRIVEALONEFREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE'] alternatives: None\n", - "Nest name: DRIVEALONEPAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY'] alternatives: None\n" - ] - } - ], - "source": [ - "#list(map(lambda x: x.print(), nests_));\n", - "# have a look at \n", - "nests_[1].print()\n", - "nests_[2].print()\n", - "nests_[3].print()\n", - "nests_[4].print()" - ] - }, - { - "cell_type": "code", - "execution_count": 312, - "id": "7b7e453d-da45-4751-8889-561a349bd373", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:22.371464Z", - "iopub.status.busy": "2021-09-01T01:11:22.371192Z", - "iopub.status.idle": "2021-09-01T01:11:22.593424Z", - "shell.execute_reply": "2021-09-01T01:11:22.592472Z", - "shell.execute_reply.started": "2021-09-01T01:11:22.371415Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation belowi s equivalent.\n", - "def compute_nested_utilities(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) / nest.product_of_coefficients #coefficient\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", - " return nested_utilities\n", - "\n", - "\n", - "utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", - "\n", - "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", - "seed = 9326543345\n", - "rng = default_rng(seed=seed)\n", - "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", - "rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", - "\n", - "probs_arr = utils_df - np.log(-np.log(rands))\n", - "\n", - "# Now replace the following with recursive nested choice\n", - "# choices = np.argmax(probs_arr, axis=1)\n", - "# choices = pd.Series(choices, index=utils_df.index)\n", - "# rands = pd.Series(np.asanyarray(rands).flatten(), index=utils_df.index)" - ] - }, - { - "cell_type": "code", - "execution_count": 313, - "id": "39b71df9-12ac-4d9c-8134-43d22f7e6a6f", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:23.703299Z", - "iopub.status.busy": "2021-09-01T01:11:23.703088Z", - "iopub.status.idle": "2021-09-01T01:11:23.937077Z", - "shell.execute_reply": "2021-09-01T01:11:23.935627Z", - "shell.execute_reply.started": "2021-09-01T01:11:23.703274Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.79848-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.36027-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.4358
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", - "trip_id \n", - "137248721 -1.79848 -3966.084195 -0.629468 -0.389611 \n", - "\n", - " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", - "trip_id \n", - "137248721 -3964.675326 -0.136364 -3966.074556 -7930.36027 -inf \n", - "\n", - " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", - "trip_id \n", - "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", - "\n", - " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", - "trip_id \n", - "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", - "\n", - " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", - "trip_id \n", - "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", - "\n", - " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", - "trip_id \n", - "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", - "\n", - " root \n", - "trip_id \n", - "137248721 11.4358 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-0.717963-3964.2474012.7217010.230429-3964.716506-0.274584-3965.126773-7930.610173-inf1.73337615.473633-1368.52552614.717027-2736.517471-5510.738449-5512.038516-5512.559241-5511.584293-inf-2738.143234-5518.606969-5519.009587-5519.830735-5521.525966-inf-inf-25.519792-21.698963-25.267185-7.44465211.689443
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", - "trip_id \n", - "137248721 -0.717963 -3964.247401 2.721701 0.230429 \n", - "\n", - " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", - "trip_id \n", - "137248721 -3964.716506 -0.274584 -3965.126773 -7930.610173 -inf \n", - "\n", - " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", - "trip_id \n", - "137248721 1.733376 15.473633 -1368.525526 14.717027 -2736.517471 \n", - "\n", - " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", - "trip_id \n", - "137248721 -5510.738449 -5512.038516 -5512.559241 -5511.584293 -inf \n", - "\n", - " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", - "trip_id \n", - "137248721 -2738.143234 -5518.606969 -5519.009587 -5519.830735 -5521.525966 \n", - "\n", - " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", - "trip_id \n", - "137248721 -inf -inf -25.519792 -21.698963 -25.267185 -7.444652 \n", - "\n", - " root \n", - "trip_id \n", - "137248721 11.689443 " - ] + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 108, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n137248721 -1.294906 -2855.580620 -0.453217 -0.280520 \n137248725 -1.247739 -2855.533453 -0.436709 -0.233697 \n211388201 -2855.520162 -5709.805876 -inf -17.366498 \n211388205 -2856.810200 -5711.095914 -inf -18.093309 \n806388401 -2855.874866 -5710.160580 -inf -9.085232 \n806388405 -2858.281791 -5712.567505 -inf -10.451460 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n137248721 -2854.566235 -0.098182 -2855.573680 -5709.859395 -inf \n137248725 -2854.519411 -0.081794 -2855.526994 -5709.812708 -inf \n211388201 -2871.652212 -6.078274 -21.226117 -2875.511831 -7.429141 \n211388205 -2872.379023 -6.332658 -21.727638 -2876.013352 -7.604673 \n806388401 -2863.370946 -3.179831 -10.734064 -2865.019778 -3.756922 \n806388405 -2864.737174 -3.658011 -11.684013 -2865.969727 -4.089405 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n137248721 0.311848 15.883036 -1368.200102 11.435786 -1970.783011 \n137248725 0.323683 15.945036 -1368.169103 11.480426 -1970.850529 \n211388201 -4.210520 -4.216264 -1381.397292 -3.035710 9.572919 \n211388205 -4.381625 -4.216264 -1381.397292 -3.035710 10.096155 \n806388401 -1.968599 6.192499 -1378.480723 4.458599 12.609633 \n806388405 -2.273383 5.998749 -1378.519473 4.319099 12.638426 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n137248721 -3968.783011 -3968.783011 -3968.783011 -3968.783011 -inf \n137248725 -3968.850529 -3968.850529 -3968.850529 -3968.850529 -inf \n211388201 -1988.028653 -1988.028653 -1988.028653 -1988.028653 4.786459 \n211388205 -1987.903845 -1987.903845 -1987.903845 -1987.903845 5.048078 \n806388401 -1984.209027 -1983.750027 -1984.124827 -1984.171027 6.304817 \n806388405 -1984.311174 -1983.852174 -1984.226974 -1984.273174 6.319213 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n137248721 -1970.903118 -3973.401006 -3973.401006 -3974.669500 -3975.688637 \n137248725 -1971.203369 -3975.938495 -3975.938495 -3971.574832 -3978.057757 \n211388201 -1988.387109 -3990.994654 -3990.994654 -1994.178373 -1997.184706 \n211388205 -1988.132266 -3993.215462 -3993.215462 -1990.677481 -1997.375747 \n806388401 -1985.442854 -3989.293913 -3989.293913 -3990.843560 -3993.682148 \n806388405 -1985.451447 -3990.724818 -3990.724818 -3987.215444 -3994.211225 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\ntrip_id \n137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n137248725 -inf -inf -24.341318 -25.104291 -24.140831 -8.406870 \n211388201 -inf 3.446251 -15.861515 -3.864497 -5.068360 -1.296757 \n211388205 -inf 3.634616 -15.776424 -3.845077 -5.005482 -1.286094 \n806388401 -inf 4.539468 -19.143696 -19.495908 -16.361074 -5.853917 \n806388405 -inf 4.549833 -18.886576 -19.495133 -16.300395 -5.828540 \n\n root \ntrip_id \n137248721 11.435801 \n137248725 11.480441 \n211388201 3.456910 \n211388205 3.643469 \n806388401 5.193789 \n806388405 5.134877 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.294906-2855.580620-0.453217-0.280520-2854.566235-0.098182-2855.573680-5709.859395-inf0.31184815.883036-1368.20010211.435786-1970.783011-3968.783011-3968.783011-3968.783011-3968.783011-inf-1970.903118-3973.401006-3973.401006-3974.669500-3975.688637-inf-inf-24.503675-25.143041-24.249471-8.44847311.435801
137248725-1.247739-2855.533453-0.436709-0.233697-2854.519411-0.081794-2855.526994-5709.812708-inf0.32368315.945036-1368.16910311.480426-1970.850529-3968.850529-3968.850529-3968.850529-3968.850529-inf-1971.203369-3975.938495-3975.938495-3971.574832-3978.057757-inf-inf-24.341318-25.104291-24.140831-8.40687011.480441
211388201-2855.520162-5709.805876-inf-17.366498-2871.652212-6.078274-21.226117-2875.511831-7.429141-4.210520-4.216264-1381.397292-3.0357109.572919-1988.028653-1988.028653-1988.028653-1988.0286534.786459-1988.387109-3990.994654-3990.994654-1994.178373-1997.184706-inf3.446251-15.861515-3.864497-5.068360-1.2967573.456910
211388205-2856.810200-5711.095914-inf-18.093309-2872.379023-6.332658-21.727638-2876.013352-7.604673-4.381625-4.216264-1381.397292-3.03571010.096155-1987.903845-1987.903845-1987.903845-1987.9038455.048078-1988.132266-3993.215462-3993.215462-1990.677481-1997.375747-inf3.634616-15.776424-3.845077-5.005482-1.2860943.643469
806388401-2855.874866-5710.160580-inf-9.085232-2863.370946-3.179831-10.734064-2865.019778-3.756922-1.9685996.192499-1378.4807234.45859912.609633-1984.209027-1983.750027-1984.124827-1984.1710276.304817-1985.442854-3989.293913-3989.293913-3990.843560-3993.682148-inf4.539468-19.143696-19.495908-16.361074-5.8539175.193789
806388405-2858.281791-5712.567505-inf-10.451460-2864.737174-3.658011-11.684013-2865.969727-4.089405-2.2733835.998749-1378.5194734.31909912.638426-1984.311174-1983.852174-1984.226974-1984.2731746.319213-1985.451447-3990.724818-3990.724818-3987.215444-3994.211225-inf4.549833-18.886576-19.495133-16.300395-5.8285405.134877
\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": "{'name': 'root',\n 'coefficient': 1.0,\n 'alternatives': [{'name': 'AUTO',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'DRIVEALONE',\n 'coefficient': 0.35,\n 'alternatives': ['DRIVEALONEFREE', 'DRIVEALONEPAY']},\n {'name': 'SHAREDRIDE2',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED2FREE', 'SHARED2PAY']},\n {'name': 'SHAREDRIDE3',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED3FREE', 'SHARED3PAY']}]},\n {'name': 'NONMOTORIZED',\n 'coefficient': 0.72,\n 'alternatives': ['WALK', 'BIKE']},\n {'name': 'TRANSIT',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'WALKACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['WALK_LOC',\n 'WALK_LRF',\n 'WALK_EXP',\n 'WALK_HVY',\n 'WALK_COM']},\n {'name': 'DRIVEACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['DRIVE_LOC',\n 'DRIVE_LRF',\n 'DRIVE_EXP',\n 'DRIVE_HVY',\n 'DRIVE_COM']}]},\n {'name': 'RIDEHAIL',\n 'coefficient': 0.36,\n 'alternatives': ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']}]}" }, "metadata": {}, "output_type": "display_data" } ], "source": [ - "display(utils_df.head(1), probs_arr.head(1))" - ] + "display(nu[0], ns[0])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 111, + "outputs": [], + "source": [ + "# next: add error terms to alternatives and nodes - can be done with this right here by iterating over nests and\n", + "# adding at each level.\n", + "# will probably want to vectorise, but that's for later.\n", + "# add_random returns a random number per row - this seems like the right thing to use while iterating over nest nodes\n", + "# and leafs (which gives us single columns per alternative)\n", + "# for destination choice, we might need to rethink this pattern though, but we'll cross that bridge when we come to it\n", + "\n", + "def inverse_ev1_cdf(x, location=0.0, scale=1.0):\n", + " #quantile function of EV1\n", + " # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution where the scale is proportional to variance (not variance^{-1})\n", + " # this means nested scales are between 0 and 1\n", + " # x can be number or np array for vecops\n", + " return location - scale * np.log(-np.log(x))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 112, + "outputs": [], + "source": [ + "utils_df = nu[0]\n", + "nest_spec = ns[0]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 115, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nest name: DRIVEALONEFREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE']\n", + "Nest name: DRIVEALONEPAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY']\n", + "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE']\n", + "Nest name: SHARED2FREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2FREE']\n", + "Nest name: SHARED2PAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2PAY']\n", + "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2']\n", + "Nest name: SHARED3FREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3FREE']\n", + "Nest name: SHARED3PAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3PAY']\n", + "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3']\n", + "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO']\n", + "Nest name: WALK level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'WALK']\n", + "Nest name: BIKE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'BIKE']\n", + "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED']\n", + "Nest name: WALK_LOC level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LOC']\n", + "Nest name: WALK_LRF level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LRF']\n", + "Nest name: WALK_EXP level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_EXP']\n", + "Nest name: WALK_HVY level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_HVY']\n", + "Nest name: WALK_COM level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_COM']\n", + "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS']\n", + "Nest name: DRIVE_LOC level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LOC']\n", + "Nest name: DRIVE_LRF level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LRF']\n", + "Nest name: DRIVE_EXP level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_EXP']\n", + "Nest name: DRIVE_HVY level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_HVY']\n", + "Nest name: DRIVE_COM level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_COM']\n", + "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS']\n", + "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT']\n", + "Nest name: TAXI level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TAXI']\n", + "Nest name: TNC_SINGLE level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SINGLE']\n", + "Nest name: TNC_SHARED level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SHARED']\n", + "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL']\n", + "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root']\n" + ] + } + ], + "source": [ + "for n in logit.each_nest(nest_spec, post_order=True):\n", + " n.print()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# OLD\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "markdown", @@ -1934,9 +1280,34 @@ " if next_level_columns is None:\n", " return choices \n", " new_choice = recursive_choice(row, next_level_columns, nest_levels, nests)\n", - " return new_choice" + " return new_choice\n", + "\n", + "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", + "\n", + "def make_choice(utils_df, nests, nest_levels, seed=None):\n", + " rng = default_rng(seed=seed)\n", + " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", + " probs_arr = utils_df - np.log(-np.log(rands))\n", + " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", + " return choices" ] }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", + "seed = 9326543345\n", + "make_choice(utils_df, nests_, nest_levels, seed)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": 316, @@ -1973,7 +1344,7 @@ }, { "cell_type": "code", - "execution_count": 326, + "execution_count": 101, "id": "bae6e7a8-d7c3-42a2-a82f-874b1e59a0b6", "metadata": { "execution": { @@ -1985,55 +1356,9 @@ }, "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 WALK\n", - "137248725 WALK\n", - "211388201 WALK_LOC\n", - "211388205 WALK_LOC\n", - "806388401 WALK\n", - "806388405 WALK\n", - "dtype: object" - ] - }, - "execution_count": 326, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests_), axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 321, - "id": "fc315f63-542f-46fd-8d70-b7a1fcd492c6", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:53:15.500933Z", - "iopub.status.busy": "2021-09-01T01:53:15.500709Z", - "iopub.status.idle": "2021-09-01T01:53:15.703201Z", - "shell.execute_reply": "2021-09-01T01:53:15.702357Z", - "shell.execute_reply.started": "2021-09-01T01:53:15.500909Z" - }, - "tags": [] - }, "outputs": [], "source": [ - "#display(utils_df, probs_arr)" - ] - }, - { - "cell_type": "markdown", - "id": "8a45e1c0-0a32-4f7a-8803-7559d71abefd", - "metadata": {}, - "source": [ - "### some tests\n", - "seeds, average?" + "#probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests_), axis=1)" ] }, { @@ -2069,20 +1394,7 @@ "output_type": "execute_result" } ], - "source": [ - "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", - "seed = 9326543345\n", - "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", - "\n", - "def make_choice(utils_df, nests, nest_levels, seed=None):\n", - " rng = default_rng(seed=seed)\n", - " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", - " probs_arr = utils_df - np.log(-np.log(rands))\n", - " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", - " return choices\n", - " \n", - "make_choice(utils_df, nests_, nest_levels, seed)" - ] + "source": [] }, { "cell_type": "code", @@ -2218,35 +1530,6 @@ "simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" ] }, - { - "cell_type": "code", - "execution_count": 380, - "id": "2da81b5e-7992-4437-8e0f-905caf026804", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:33:10.886502Z", - "iopub.status.busy": "2021-09-01T02:33:10.886252Z", - "iopub.status.idle": "2021-09-01T02:33:11.092145Z", - "shell.execute_reply": "2021-09-01T02:33:11.090725Z", - "shell.execute_reply.started": "2021-09-01T02:33:10.886465Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'trip_mode_choice'" - ] - }, - "execution_count": 380, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trace_label" - ] - }, { "cell_type": "code", "execution_count": 393, diff --git a/notebooks/nested_logit_frozen_rand_individ_util.ipynb b/notebooks/nested_logit_frozen_rand_individ_util.ipynb index 2bb10981e5..2578b9068e 100644 --- a/notebooks/nested_logit_frozen_rand_individ_util.ipynb +++ b/notebooks/nested_logit_frozen_rand_individ_util.ipynb @@ -72,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:14.957470Z", @@ -101,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:15.467608Z", @@ -154,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:20.523480Z", @@ -171,8 +171,8 @@ "text": [ "closed form: [0.4452265282367507, 0.5330453677531714, 0.02172810401007798],\n", "simulated: [0.4450967 0.5331544 0.0217489]\n", - "CPU times: user 766 ms, sys: 46.9 ms, total: 812 ms\n", - "Wall time: 814 ms\n" + "CPU times: user 969 ms, sys: 78.1 ms, total: 1.05 s\n", + "Wall time: 1.14 s\n" ] } ], @@ -194,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:24.027745Z", @@ -237,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T10:02:14.035272Z", @@ -257,7 +257,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T10:03:23.999857Z", @@ -309,28 +309,32 @@ ] }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### the error term decomposition way\n", - "\n", - "We can decompose the error term into one for the nest and one within nests. For the example structure here, do we need to introduce artificial levels? In other terms, does car and bus nest have a common component?" - ] + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "markdown", "metadata": {}, "source": [ - "observation: when setting all utilities equal and nest scale to 0.999, the probs should be nearly exacly equal 1/3, but the probs here are about 0.5 to 0.25,0.25 which is what you get when the nest scale is very small (i.e. strongly correlated).\n", + "## The error term decomposition way -> not working. just use the zenith way and write it as indicators for individuals.\n", "\n", - "When the 1/scale in quantile function is turned into scale, the values are slightly off but much closer (0.368 to 0.3165,0.3165)\n", + "We can decompose the error term into one for the nest and one within nests according to Hildebrandt. However, I\n", + "cannot seem to reproduce the exact probabilities. Why?\n", "\n", - "# => check scale and 1/scale everywhere" + "Looks like one of the location parameters is wrong; 0.125 added to nest makes it right (tested for one set of params)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 79, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:25.164028Z", @@ -346,33 +350,34 @@ "output_type": "stream", "text": [ "closed form probs: 0.445227, 0.533045, 0.021728\n", - " simulated probs: 0.284564, 0.493765, 0.221671\n", - "CPU times: user 281 ms, sys: 0 ns, total: 281 ms\n", - "Wall time: 303 ms\n" + " simulated probs: 0.4755317, 0.5039483, 0.02052\n", + "0.9363, 1.0577, 1.0589\n", + "CPU times: user 4.12 s, sys: 2.3 s, total: 6.42 s\n", + "Wall time: 6.99 s\n" ] } ], "source": [ "%%time\n", "# conditional error term are given by logit with scale given by nest scale\n", - "num_draws_dec = 1000000\n", + "num_draws_dec = 10000000\n", "\n", "mu = 1.0 / nest_scale\n", "\n", "rng_dec = default_rng(9)\n", "\n", "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", - "ev1_dec = inverse_ev1_cdf(rands_dec, scale=mu)\n", - "\n", + "ev1_dec = inverse_ev1_cdf(rands_dec, scale=1.0/mu)\n", "lower_level_utils_2 = np.repeat(util_2, num_draws_dec) + ev1_dec[num_draws_dec:]\n", "lower_level_utils_3 = np.repeat(util_3, num_draws_dec) + ev1_dec[:num_draws_dec]\n", "\n", - "\n", + "#location_nest = - 1.0 / mu * np.log(2.0 * np.exp(mu * 0.5772))\n", "location_nest = - np.log(2.0) / mu\n", "#location_nest = (- np.log(2.0) / mu) - ((1.0 - 1.0 / (mu + 1.0)) * 0.57721 * mu / (mu**2 - 1.0))\n", "#print(location_nest, - np.log(2.0) / mu)\n", - "scale_nest = mu**2 / np.sqrt(mu**2 - 1.0)\n", - "nest_error_terms = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec), location=location_nest, scale=scale_nest)\n", + "\n", + "scale_nest = mu / np.sqrt(mu**2 - 1.0)\n", + "nest_error_terms = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec), location=location_nest, scale=1.0/scale_nest)\n", "\n", "full_utils_2 = lower_level_utils_2 + nest_error_terms\n", "full_utils_3 = lower_level_utils_3 + nest_error_terms\n", @@ -386,16 +391,8 @@ "probs_dec = {i+1: counts[i] / num_draws_dec for i in vals}\n", "\n", "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", - "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# WHY IS THIS NOT EQUAL\n", - "\n", - "see p.12 of Hildebrandt: scale in E(nu_m) and Var(nu_m) is not equal? Does this need correction factor for location?" + "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")\n", + "print(f\"{prob_1 / probs_dec[1]:.4f}, {prob_2 / probs_dec[2]:.4f}, {prob_3 / probs_dec[3]:.4f}\")" ] }, { @@ -405,20 +402,6 @@ "outputs": [], "source": [] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "metadata": {}, @@ -460,4 +443,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file From a4a76309fdbb0c1fd0ae7647a806e1db5724cbff Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 4 May 2022 20:53:30 +1000 Subject: [PATCH 003/298] adds choice based on walking nest levels --- activitysim/core/logit.py | 8 +- notebooks/frozen_rand_mode_choice_dev.ipynb | 382 ++++++++---------- ...ested_logit_frozen_rand_individ_util.ipynb | 8 +- 3 files changed, 174 insertions(+), 224 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 0359baab90..397e35e6c3 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -330,10 +330,12 @@ def __init__(self, name=None, level=0): self.ancestors = [] self.alternatives = None self.coefficient = 0 + self.parent_nest_scale = 1.0 def print(self): - print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s" % - (self.name, self.level, self.coefficient, self.product_of_coefficients, self.ancestors)) + print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s parent nest scale: " + "%s" % + (self.name, self.level, self.coefficient, self.product_of_coefficients, self.ancestors, self.parent_nest_scale)) @property def is_leaf(self): @@ -404,6 +406,7 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients * coefficient nest.alternatives = alternatives nest.ancestors = parent_nest.ancestors + [name] + nest.parent_nest_scale = parent_nest.coefficient if pre_order: yield spec, nest @@ -424,6 +427,7 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients nest.ancestors = parent_nest.ancestors + [name] nest.coefficient = parent_nest.coefficient + nest.parent_nest_scale = parent_nest.coefficient yield spec, nest diff --git a/notebooks/frozen_rand_mode_choice_dev.ipynb b/notebooks/frozen_rand_mode_choice_dev.ipynb index b0228ab13f..7003eb7cd1 100644 --- a/notebooks/frozen_rand_mode_choice_dev.ipynb +++ b/notebooks/frozen_rand_mode_choice_dev.ipynb @@ -16,7 +16,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { "execution": { @@ -35,7 +35,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { "execution": { @@ -54,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -94,7 +94,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -113,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -133,7 +133,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -152,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -210,7 +210,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 10, "outputs": [], "source": [ "def run_trip_mode_choice(do_these_purposes=None, simulate_function=simulate.simple_simulate):\n", @@ -375,7 +375,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -385,19 +385,43 @@ "shell.execute_reply": "2021-09-01T02:29:01.222865Z", "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" }, - "tags": [], - "pycharm": { - "is_executing": true - } + "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'atwork' (27 trips)\n", + "trip_mode_choice tour_type 'eatout' (33 trips)\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n", + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", + "trip_mode_choice tour_type 'othmaint' (46 trips)\n", + "trip_mode_choice tour_type 'school' (37 trips)\n", + "trip_mode_choice tour_type 'shopping' (77 trips)\n", + "trip_mode_choice tour_type 'social' (19 trips)\n", + "trip_mode_choice tour_type 'univ' (26 trips)\n", + "trip_mode_choice tour_type 'work' (168 trips)\n", + "Done\n" + ] + } + ], "source": [ "trips_df = run_trip_mode_choice()" ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 12, "id": "7eae2854-38bd-4da0-a161-3a15bbd17177", "metadata": { "execution": { @@ -415,7 +439,7 @@ "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n8684833 26478 26478 eatout 1 True \n8684837 26478 26478 eatout 1 False \n8685009 26478 26478 othmaint 1 True \n8685013 26478 26478 othmaint 1 False \n8753057 26686 26686 eatout 1 True \n... ... ... ... ... ... \n2472945113 7539466 2848131 shopping 1 True \n2472945117 7539466 2848131 shopping 1 False \n2472945118 7539466 2848131 shopping 2 False \n2473024473 7539708 2848373 univ 1 True \n2473024477 7539708 2848373 univ 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n8684833 1 13 8 1085604 eatout \n8684837 1 8 13 1085604 home \n8685009 1 10 8 1085626 othmaint \n8685013 1 8 10 1085626 home \n8753057 1 5 8 1094132 eatout \n... ... ... ... ... ... \n2472945113 1 8 3 309118139 shopping \n2472945117 2 25 8 309118139 shopping \n2472945118 2 3 25 309118139 home \n2473024473 1 13 18 309128059 univ \n2473024477 1 18 13 309128059 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n8684833 NaN 11.0 WALK -1.171760 \n8684837 NaN 11.0 WALK -1.238719 \n8685009 NaN 12.0 BIKE 6.198626 \n8685013 NaN 13.0 BIKE 6.175681 \n8753057 NaN 19.0 WALK 4.457539 \n... ... ... ... ... \n2472945113 NaN 18.0 WALK_LOC 12.537675 \n2472945117 56.842247 21.0 WALK_LOC 11.880804 \n2472945118 NaN 22.0 WALK 13.710030 \n2473024473 NaN 16.0 WALK_LOC -0.530696 \n2473024477 NaN 23.0 WALK_LRF 0.624304 \n\n[482 rows x 14 columns]", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
86848332647826478eatout1True11381085604eatoutNaN11.0WALK-1.171760
86848372647826478eatout1False18131085604homeNaN11.0WALK-1.238719
86850092647826478othmaint1True11081085626othmaintNaN12.0BIKE6.198626
86850132647826478othmaint1False18101085626homeNaN13.0BIKE6.175681
87530572668626686eatout1True1581094132eatoutNaN19.0WALK4.457539
.............................................
247294511375394662848131shopping1True183309118139shoppingNaN18.0WALK_LOC12.537675
247294511775394662848131shopping1False2258309118139shopping56.84224721.0WALK_LOC11.880804
247294511875394662848131shopping2False2325309118139homeNaN22.0WALK13.710030
247302447375397082848373univ1True11318309128059univNaN16.0WALK_LOC-0.530696
247302447775397082848373univ1False11813309128059homeNaN23.0WALK_LRF0.624304
\n

482 rows × 14 columns

\n
" }, - "execution_count": 41, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -434,7 +458,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 13, "id": "3e2513e9-c03f-4891-9a1c-4053a4440a10", "metadata": { "execution": { @@ -455,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 14, "id": "36c100b3-3e39-4950-a586-4d42be695eaa", "metadata": { "execution": { @@ -476,7 +500,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 15, "id": "e8d11f6a-f82a-40bd-8eef-fc28bcca8252", "metadata": { "execution": { @@ -629,7 +653,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 16, "id": "ec4eb1ff-2f35-4919-96b4-5acffa01f597", "metadata": {}, "outputs": [ @@ -646,35 +670,7 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "trip_mode_choice tour_type 'escort' (6 trips)\n", "Done\n" ] }, @@ -683,7 +679,7 @@ "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n137248721 418441 304036 escort 1 True \n137248725 418441 304036 escort 1 False \n211388201 644476 386761 escort 1 True \n211388205 644476 386761 escort 1 False \n806388401 2458501 1173905 escort 1 True \n806388405 2458501 1173905 escort 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n137248721 1 7 10 17156090 escort \n137248725 1 10 7 17156090 home \n211388201 1 11 16 26423525 escort \n211388205 1 16 11 26423525 home \n806388401 1 16 8 100798550 escort \n806388405 1 8 16 100798550 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n137248721 NaN 7.0 WALK 11.435800 \n137248725 NaN 7.0 WALK 11.480440 \n211388201 NaN 5.0 WALK_LOC 4.789158 \n211388205 NaN 6.0 WALK_LOC 5.050171 \n806388401 NaN 15.0 WALK_LOC 6.451457 \n806388405 NaN 16.0 WALK_LOC 6.446188 ", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
137248721418441304036escort1True171017156090escortNaN7.0WALK11.435800
137248725418441304036escort1False110717156090homeNaN7.0WALK11.480440
211388201644476386761escort1True1111626423525escortNaN5.0WALK_LOC4.789158
211388205644476386761escort1False1161126423525homeNaN6.0WALK_LOC5.050171
80638840124585011173905escort1True1168100798550escortNaN15.0WALK_LOC6.451457
80638840524585011173905escort1False1816100798550homeNaN16.0WALK_LOC6.446188
\n
" }, - "execution_count": 48, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -707,7 +703,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 61, "outputs": [], "source": [ "def eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, estimator,\n", @@ -956,7 +952,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 62, "outputs": [ { "name": "stderr", @@ -971,35 +967,7 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "trip_mode_choice tour_type 'escort' (6 trips)\n", "Done\n" ] } @@ -1016,7 +984,7 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 63, "outputs": [ { "data": { @@ -1025,17 +993,10 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "data": { - "text/plain": "{'name': 'root',\n 'coefficient': 1.0,\n 'alternatives': [{'name': 'AUTO',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'DRIVEALONE',\n 'coefficient': 0.35,\n 'alternatives': ['DRIVEALONEFREE', 'DRIVEALONEPAY']},\n {'name': 'SHAREDRIDE2',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED2FREE', 'SHARED2PAY']},\n {'name': 'SHAREDRIDE3',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED3FREE', 'SHARED3PAY']}]},\n {'name': 'NONMOTORIZED',\n 'coefficient': 0.72,\n 'alternatives': ['WALK', 'BIKE']},\n {'name': 'TRANSIT',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'WALKACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['WALK_LOC',\n 'WALK_LRF',\n 'WALK_EXP',\n 'WALK_HVY',\n 'WALK_COM']},\n {'name': 'DRIVEACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['DRIVE_LOC',\n 'DRIVE_LRF',\n 'DRIVE_EXP',\n 'DRIVE_HVY',\n 'DRIVE_COM']}]},\n {'name': 'RIDEHAIL',\n 'coefficient': 0.36,\n 'alternatives': ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']}]}" - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ - "display(nu[0], ns[0])" + "display(nu[0])#, ns[0])" ], "metadata": { "collapsed": false, @@ -1046,7 +1007,7 @@ }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 64, "outputs": [], "source": [ "# next: add error terms to alternatives and nodes - can be done with this right here by iterating over nests and\n", @@ -1060,7 +1021,7 @@ " #quantile function of EV1\n", " # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution where the scale is proportional to variance (not variance^{-1})\n", " # this means nested scales are between 0 and 1\n", - " # x can be number or np array for vecops\n", + " # x can be number or np array or pd df for vecops\n", " return location - scale * np.log(-np.log(x))" ], "metadata": { @@ -1072,7 +1033,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 65, "outputs": [], "source": [ "utils_df = nu[0]\n", @@ -1087,49 +1048,125 @@ }, { "cell_type": "code", - "execution_count": 115, + "execution_count": 66, + "outputs": [], + "source": [ + "# fake random channel for prototyping as per Asim tests\n", + "from activitysim.core.random import Random\n", + "rng = Random()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 102, + "outputs": [], + "source": [ + "nest_utils_for_choice = utils_df.copy() # we'll add random parts to this such that we can recursively choose from\n", + "# the top level\n", + "for n in logit.each_nest(nest_spec):\n", + " if n.level == 1:\n", + " assert n.name == \"root\" # TODO get this from where ever const is defined in code\n", + " continue\n", + " #n.print()\n", + " #print(nest_utils_for_choice.loc[:,n.name])\n", + " # TODO: check parent nest level scale is what we want this is right\n", + " rands = inverse_ev1_cdf(rng.random_for_df(nest_utils_for_choice, n=1), scale=n.parent_nest_scale)\n", + " #print(rands)\n", + " # this will be cleaner wtith xarrays\n", + " nest_utils_for_choice.loc[:,n.name] += rands[:,0]\n", + " #print(nest_utils_for_choice.loc[:,n.name])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 103, + "outputs": [], + "source": [ + "# alts = [\"DRIVEALONEFREE\", \"DRIVEALONEPAY\"]\n", + "# #print(nest_utils_for_choice[alts])\n", + "# t_ = nest_utils_for_choice[alts].idxmax(1)\n", + "# t_.apply(is_alternative)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 104, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Nest name: DRIVEALONEFREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE']\n", - "Nest name: DRIVEALONEPAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY']\n", - "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE']\n", - "Nest name: SHARED2FREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2FREE']\n", - "Nest name: SHARED2PAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2PAY']\n", - "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2']\n", - "Nest name: SHARED3FREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3FREE']\n", - "Nest name: SHARED3PAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3PAY']\n", - "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3']\n", - "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO']\n", - "Nest name: WALK level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'WALK']\n", - "Nest name: BIKE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'BIKE']\n", - "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED']\n", - "Nest name: WALK_LOC level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LOC']\n", - "Nest name: WALK_LRF level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LRF']\n", - "Nest name: WALK_EXP level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_EXP']\n", - "Nest name: WALK_HVY level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_HVY']\n", - "Nest name: WALK_COM level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_COM']\n", - "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS']\n", - "Nest name: DRIVE_LOC level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LOC']\n", - "Nest name: DRIVE_LRF level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LRF']\n", - "Nest name: DRIVE_EXP level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_EXP']\n", - "Nest name: DRIVE_HVY level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_HVY']\n", - "Nest name: DRIVE_COM level: 4 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_COM']\n", - "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS']\n", - "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT']\n", - "Nest name: TAXI level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TAXI']\n", - "Nest name: TNC_SINGLE level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SINGLE']\n", - "Nest name: TNC_SHARED level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SHARED']\n", - "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL']\n", - "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root']\n" + "Nesting depth is 4\n" ] } ], "source": [ - "for n in logit.each_nest(nest_spec, post_order=True):\n", - " n.print()" + "all_alternatives = list(map(lambda x: x.name, filter(lambda x: x.is_leaf, logit.each_nest(nest_spec))))\n", + "def is_alternative(name):\n", + " return name in all_alternatives\n", + "\n", + "def group_nests_by_level(nest_spec):\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", + " print(f\"Nesting depth is {depth}\")\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in logit.each_nest(nest_spec):\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " return nest_levels\n", + "\n", + "nest_utils_for_choice[\"choice\"] = None\n", + "\n", + "for level, alts in group_nests_by_level(nest_spec).items():\n", + " if level == 1:\n", + " continue\n", + " no_choices_made_yet = nest_utils_for_choice[\"choice\"].isnull()\n", + " choice_this_level = nest_utils_for_choice.loc[no_choices_made_yet][alts].idxmax(1)\n", + " nest_utils_for_choice.loc[no_choices_made_yet, \"choice\"] = \\\n", + " np.where(choice_this_level.apply(is_alternative), choice_this_level, None)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 105, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n137248721 -1.116115 -2855.401829 -0.085418 -0.101729 \n137248725 -0.865187 -2855.150902 0.350254 0.148855 \n211388201 -2855.281895 -5709.567609 -inf -17.128230 \n211388205 -2856.635577 -5710.921291 -inf -17.918686 \n806388401 -2855.821604 -5710.107318 -inf -9.031970 \n806388405 -2857.992149 -5712.277863 -inf -10.161818 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n137248721 -2854.387444 0.269616 -2855.394889 -5709.680604 -inf \n137248725 -2854.136859 0.705169 -2855.144442 -5709.430157 -inf \n211388201 -2871.413945 -5.588125 -20.987850 -2875.273564 -6.938992 \n211388205 -2872.204400 -5.973433 -21.553014 -2875.838729 -7.245448 \n806388401 -2863.317684 -3.070264 -10.680802 -2864.966516 -3.647355 \n806388405 -2864.447532 -3.062176 -11.394371 -2865.680085 -3.493570 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n137248721 0.822679 16.250835 -1367.832304 11.946618 -1970.527596 \n137248725 1.416687 16.732000 -1367.382139 12.573430 -1970.304027 \n211388201 -3.529757 -3.726114 -1380.907142 -2.354947 9.913300 \n211388205 -3.882701 -3.857039 -1381.038067 -2.536786 10.345617 \n806388401 -1.816422 6.302066 -1378.371156 4.610776 12.685721 \n806388405 -1.445835 6.594584 -1377.923639 5.146648 13.052200 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n137248721 -3968.527596 -3968.527596 -3968.527596 -3968.527596 -inf \n137248725 -3968.304027 -3968.304027 -3968.304027 -3968.304027 -inf \n211388201 -1987.688271 -1987.688271 -1987.688271 -1987.688271 5.276609 \n211388205 -1987.654383 -1987.654383 -1987.654383 -1987.654383 5.407303 \n806388401 -1984.132938 -1983.673938 -1984.048738 -1984.094938 6.414384 \n806388405 -1983.897400 -1983.438400 -1983.813200 -1983.859400 6.915048 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n137248721 -1970.647702 -3973.145591 -3973.145591 -3974.414085 -3975.433221 \n137248725 -1970.656867 -3975.391993 -3975.391993 -3971.028330 -3977.511255 \n211388201 -1988.046728 -3990.654273 -3990.654273 -1993.837992 -1996.844325 \n211388205 -1987.882804 -3992.966000 -3992.966000 -1990.428019 -1997.126285 \n806388401 -1985.366765 -3989.217824 -3989.217824 -3990.767471 -3993.606059 \n806388405 -1985.037673 -3990.311044 -3990.311044 -3986.801670 -3993.797451 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\ntrip_id \n137248721 -inf -inf -24.319775 -24.959142 -24.065572 -7.937642 \n137248725 -inf -inf -23.947836 -24.710809 -23.747349 -7.313866 \n211388201 -inf 4.127014 -15.616441 -3.619423 -4.823285 -0.615994 \n211388205 -inf 4.133540 -15.596812 -3.665464 -4.825869 -0.787171 \n806388401 -inf 4.691645 -19.088912 -19.441124 -16.306290 -5.701741 \n806388405 -inf 5.377382 -18.588659 -19.197216 -16.002477 -5.000992 \n\n root choice \ntrip_id \n137248721 11.435801 WALK \n137248725 11.480441 WALK \n211388201 3.456910 WALK_LOC \n211388205 3.643469 WALK_LOC \n806388401 5.193789 WALK_LOC \n806388405 5.134877 WALK_LOC ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILrootchoice
trip_id
137248721-1.116115-2855.401829-0.085418-0.101729-2854.3874440.269616-2855.394889-5709.680604-inf0.82267916.250835-1367.83230411.946618-1970.527596-3968.527596-3968.527596-3968.527596-3968.527596-inf-1970.647702-3973.145591-3973.145591-3974.414085-3975.433221-inf-inf-24.319775-24.959142-24.065572-7.93764211.435801WALK
137248725-0.865187-2855.1509020.3502540.148855-2854.1368590.705169-2855.144442-5709.430157-inf1.41668716.732000-1367.38213912.573430-1970.304027-3968.304027-3968.304027-3968.304027-3968.304027-inf-1970.656867-3975.391993-3975.391993-3971.028330-3977.511255-inf-inf-23.947836-24.710809-23.747349-7.31386611.480441WALK
211388201-2855.281895-5709.567609-inf-17.128230-2871.413945-5.588125-20.987850-2875.273564-6.938992-3.529757-3.726114-1380.907142-2.3549479.913300-1987.688271-1987.688271-1987.688271-1987.6882715.276609-1988.046728-3990.654273-3990.654273-1993.837992-1996.844325-inf4.127014-15.616441-3.619423-4.823285-0.6159943.456910WALK_LOC
211388205-2856.635577-5710.921291-inf-17.918686-2872.204400-5.973433-21.553014-2875.838729-7.245448-3.882701-3.857039-1381.038067-2.53678610.345617-1987.654383-1987.654383-1987.654383-1987.6543835.407303-1987.882804-3992.966000-3992.966000-1990.428019-1997.126285-inf4.133540-15.596812-3.665464-4.825869-0.7871713.643469WALK_LOC
806388401-2855.821604-5710.107318-inf-9.031970-2863.317684-3.070264-10.680802-2864.966516-3.647355-1.8164226.302066-1378.3711564.61077612.685721-1984.132938-1983.673938-1984.048738-1984.0949386.414384-1985.366765-3989.217824-3989.217824-3990.767471-3993.606059-inf4.691645-19.088912-19.441124-16.306290-5.7017415.193789WALK_LOC
806388405-2857.992149-5712.277863-inf-10.161818-2864.447532-3.062176-11.394371-2865.680085-3.493570-1.4458356.594584-1377.9236395.14664813.052200-1983.897400-1983.438400-1983.813200-1983.8594006.915048-1985.037673-3990.311044-3990.311044-3986.801670-3993.797451-inf5.377382-18.588659-19.197216-16.002477-5.0009925.134877WALK_LOC
\n
" + }, + "execution_count": 105, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nest_utils_for_choice" ], "metadata": { "collapsed": false, @@ -1342,97 +1379,6 @@ "assert nest_levels[1][0] == 'root'" ] }, - { - "cell_type": "code", - "execution_count": 101, - "id": "bae6e7a8-d7c3-42a2-a82f-874b1e59a0b6", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:56:38.377472Z", - "iopub.status.busy": "2021-09-01T01:56:38.377253Z", - "iopub.status.idle": "2021-09-01T01:56:38.607858Z", - "shell.execute_reply": "2021-09-01T01:56:38.606913Z", - "shell.execute_reply.started": "2021-09-01T01:56:38.377447Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests_), axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 333, - "id": "f0e00814-2a14-4da1-bc6c-89b52b7fb286", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:58:26.002425Z", - "iopub.status.busy": "2021-09-01T01:58:26.002200Z", - "iopub.status.idle": "2021-09-01T01:58:26.247954Z", - "shell.execute_reply": "2021-09-01T01:58:26.247054Z", - "shell.execute_reply.started": "2021-09-01T01:58:26.002401Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 WALK\n", - "137248725 WALK\n", - "211388201 WALK_LOC\n", - "211388205 WALK_LOC\n", - "806388401 WALK\n", - "806388405 WALK\n", - "dtype: object" - ] - }, - "execution_count": 333, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 348, - "id": "bca659db-4c88-4287-8f5e-9c9516e3ef41", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:59:00.410062Z", - "iopub.status.busy": "2021-09-01T01:59:00.409811Z", - "iopub.status.idle": "2021-09-01T01:59:00.621940Z", - "shell.execute_reply": "2021-09-01T01:59:00.621156Z", - "shell.execute_reply.started": "2021-09-01T01:59:00.410037Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 WALK\n", - "137248725 WALK\n", - "211388201 WALK_LOC\n", - "211388205 WALK_LOC\n", - "806388401 WALK_LOC\n", - "806388405 WALK_LOC\n", - "dtype: object" - ] - }, - "execution_count": 348, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "make_choice(utils_df, nests_, nest_levels)" - ] - }, { "cell_type": "code", "execution_count": 453, diff --git a/notebooks/nested_logit_frozen_rand_individ_util.ipynb b/notebooks/nested_logit_frozen_rand_individ_util.ipynb index 2578b9068e..dede399ca8 100644 --- a/notebooks/nested_logit_frozen_rand_individ_util.ipynb +++ b/notebooks/nested_logit_frozen_rand_individ_util.ipynb @@ -171,8 +171,8 @@ "text": [ "closed form: [0.4452265282367507, 0.5330453677531714, 0.02172810401007798],\n", "simulated: [0.4450967 0.5331544 0.0217489]\n", - "CPU times: user 969 ms, sys: 78.1 ms, total: 1.05 s\n", - "Wall time: 1.14 s\n" + "CPU times: user 750 ms, sys: 78.1 ms, total: 828 ms\n", + "Wall time: 811 ms\n" ] } ], @@ -237,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T10:02:14.035272Z", @@ -257,7 +257,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T10:03:23.999857Z", From c6b609afd6ffc881964ba560d66b2a12c8248d0f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 4 May 2022 21:43:07 +1000 Subject: [PATCH 004/298] prototype simple_simulate --- activitysim/core/logit.py | 53 +++++++++ activitysim/core/simulate.py | 116 ++++++++++++++++++++ notebooks/frozen_rand_mode_choice_dev.ipynb | 108 ++++++++---------- 3 files changed, 216 insertions(+), 61 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 397e35e6c3..18d933cbd6 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -184,6 +184,49 @@ def utils_to_probs(utils, trace_label=None, exponentiated=False, allow_zero_prob return probs +def inverse_ev1_cdf(x, location=0.0, scale=1.0): + # quantile function of EV1 + # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution (and biogeme and larch convention) where the scale + # is proportional to variance (not variance^{-1}). this means nested scales are between 0 and 1. + # x can be number or np array or pd df for vecops + return location - scale * np.log(-np.log(x)) + + +def add_ev1_random(df, nest_spec): + # TODO: generalise to logit for nest_spec==None by adding one rand (scale=1) to all columns + nest_utils_for_choice = df.copy() + for n in each_nest(nest_spec): + if n.level == 1: + continue # skip the root level + # TODO: check parent nest level scale is what we want this is right + uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice) + rands = inverse_ev1_cdf(uniform_rands, scale=n.parent_nest_scale) + nest_utils_for_choice.loc[:, n.name] += rands[:, 0] # inverse_ev1_cdf of single-row df adds dimension + return nest_utils_for_choice + + +def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_choosers=None): + trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') + nest_utils_for_choice = add_ev1_random(nested_utilities, nest_spec) + all_alternatives = list(map(lambda x: x.name, filter(lambda x: x.is_leaf, each_nest(nest_spec)))) + + def is_alternative(name): + return name in all_alternatives + + nest_utils_for_choice["choice"] = None + for level, alts in group_nests_by_level(nest_spec).items(): + if level == 1: + continue + no_choices_made_yet = nest_utils_for_choice["choice"].isnull() + choice_this_level = nest_utils_for_choice.loc[no_choices_made_yet][alts].idxmax(1) + nest_utils_for_choice.loc[no_choices_made_yet, "choice"] = \ + np.where(choice_this_level.apply(is_alternative), choice_this_level, None) + + assert not nest_utils_for_choice["choice"].isnull.any(), "No choice for XXX - implement reporting" + choices = pd.Series(nest_utils_for_choice["choice"], index=nested_utilities.index) + return choices + + def make_choices(probs, trace_label=None, trace_choosers=None, allow_bad_probs=False): """ Make choices for each chooser from among a set of alternatives. @@ -475,3 +518,13 @@ def count_each_nest(spec, count): return 1 return count_each_nest(nest_spec, 0) if nest_spec is not None else 0 + + +def group_nests_by_level(nest_spec): + # group nests by level, returns {level: [nest.name at that level]} + depth = np.max([x.level for x in each_nest(nest_spec)]) + nest_levels = {x: [] for x in range(1, depth+1)} + for n in each_nest(nest_spec): + nest_levels[n.level].append(n.name) + assert len(nest_levels[1]) == 1 # only one root + return nest_levels diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 6eff3c2e9f..6f659606c6 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -964,6 +964,122 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, return choices +def eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, + log_alt_losers=False, + want_logsums=False, trace_label=None, + trace_choice_name=None, trace_column_names=None): + """ + Run a nested-logit simulation for when the model spec does not involve alternative + specific data, e.g. there are no interactions with alternative + properties and no need to sample from alternatives. + + Parameters + ---------- + choosers : pandas.DataFrame + spec : pandas.DataFrame + A table of variable specifications and coefficient values. + Variable expressions should be in the table index and the table + should have a column for each alternative. + nest_spec: + dictionary specifying nesting structure and nesting coefficients + (from the model spec yaml file) + locals_d : Dict or None + This is a dictionary of local variables that will be the environment + for an evaluation of an expression that begins with @ + custom_chooser : function(probs, choosers, spec, trace_label) returns choices, rands + custom alternative to logit.make_choices + estimator : Estimator object + called to report intermediate table results (used for estimation) + trace_label: str + This is the label to be used for trace log file entries and dump file names + when household tracing enabled. No tracing occurs if label is empty or None. + trace_choice_name: str + This is the column label to be used in trace file csv dump of choices + trace_column_names: str or list of str + chooser columns to include when tracing expression_values + + Returns + ------- + choices : pandas.Series + Index will be that of `choosers`, values will match the columns + of `spec`. + """ + + trace_label = tracing.extend_trace_label(trace_label, 'eval_nl_fixed_ru') + assert trace_label + have_trace_targets = tracing.has_trace_targets(choosers) + + logit.validate_nest_spec(nest_spec, trace_label) + + if have_trace_targets: + tracing.trace_df(choosers, '%s.choosers' % trace_label) + + raw_utilities = eval_utilities(spec, choosers, locals_d, + log_alt_losers=log_alt_losers, + trace_label=trace_label, have_trace_targets=have_trace_targets, + estimator=estimator, trace_column_names=trace_column_names) + chunk.log_df(trace_label, "raw_utilities", raw_utilities) + + if have_trace_targets: + tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label, + column_labels=['alternative', 'utility']) + + # utilities of leaves and nests + nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) + chunk.log_df(trace_label, "nested_utilities", nested_utilities) + + del raw_utilities + chunk.log_df(trace_label, 'raw_utilities', None) + + if have_trace_targets: + tracing.trace_df(nested_utilities, '%s.nested_utilities' % trace_label, + column_labels=['alternative', 'utility']) + + # TODO - check this is correct + if want_logsums: + # logsum of nest root + logsums = pd.Series(nested_utilities.root, index=choosers.index) + chunk.log_df(trace_label, "logsums", logsums) + + + # TODO: add checks on utilities? + # # note base_probabilities could all be zero since we allowed all probs for nests to be zero + # # check here to print a clear message but make_choices will raise error if probs don't sum to 1 + # BAD_PROB_THRESHOLD = 0.001 + # no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD + # + # if no_choices.any(): + # + # logit.report_bad_choices( + # no_choices, base_probabilities, + # trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'), + # trace_choosers=choosers, + # msg="base_probabilities do not sum to one") + + # TODO: add custom_chooser + # if custom_chooser: + # choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, + # trace_label=trace_label) + # else: + + choices = logit.make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=trace_label) + + if have_trace_targets: + tracing.trace_df(choices, '%s.choices' % trace_label, + columns=[None, trace_choice_name]) + #tracing.trace_df(rands, '%s.rands' % trace_label, + # columns=[None, 'rand']) + if want_logsums: + tracing.trace_df(logsums, '%s.logsums' % trace_label, + columns=[None, 'logsum']) + + if want_logsums: + choices = choices.to_frame('choice') + choices['logsum'] = logsums + + return choices + + def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, log_alt_losers=False, want_logsums=False, trace_label=None, diff --git a/notebooks/frozen_rand_mode_choice_dev.ipynb b/notebooks/frozen_rand_mode_choice_dev.ipynb index 7003eb7cd1..4a85f62408 100644 --- a/notebooks/frozen_rand_mode_choice_dev.ipynb +++ b/notebooks/frozen_rand_mode_choice_dev.ipynb @@ -1033,27 +1033,24 @@ }, { "cell_type": "code", - "execution_count": 65, - "outputs": [], - "source": [ - "utils_df = nu[0]\n", - "nest_spec = ns[0]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 66, + "execution_count": 112, "outputs": [], "source": [ "# fake random channel for prototyping as per Asim tests\n", "from activitysim.core.random import Random\n", - "rng = Random()" + "rng = Random()\n", + "\n", + "def add_ev1_random(df, nest_spec):\n", + " # TODO: generalise to logit for nest_spec==None by adding one rand (scale=1) to all columns\n", + " nest_utils_for_choice = df.copy() # we'll add random parts to this such that we can recursively choose from\n", + " for n in logit.each_nest(nest_spec):\n", + " if n.level == 1:\n", + " continue # skip the root level\n", + " # TODO: check parent nest level scale is what we want this is right\n", + " rands = inverse_ev1_cdf(rng.random_for_df(nest_utils_for_choice, n=1), scale=n.parent_nest_scale)\n", + " # this will be cleaner wtith xarrays\n", + " nest_utils_for_choice.loc[:,n.name] += rands[:,0]\n", + " return nest_utils_for_choice" ], "metadata": { "collapsed": false, @@ -1064,23 +1061,12 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 115, "outputs": [], "source": [ - "nest_utils_for_choice = utils_df.copy() # we'll add random parts to this such that we can recursively choose from\n", - "# the top level\n", - "for n in logit.each_nest(nest_spec):\n", - " if n.level == 1:\n", - " assert n.name == \"root\" # TODO get this from where ever const is defined in code\n", - " continue\n", - " #n.print()\n", - " #print(nest_utils_for_choice.loc[:,n.name])\n", - " # TODO: check parent nest level scale is what we want this is right\n", - " rands = inverse_ev1_cdf(rng.random_for_df(nest_utils_for_choice, n=1), scale=n.parent_nest_scale)\n", - " #print(rands)\n", - " # this will be cleaner wtith xarrays\n", - " nest_utils_for_choice.loc[:,n.name] += rands[:,0]\n", - " #print(nest_utils_for_choice.loc[:,n.name])" + "utils_df = nu[0]\n", + "nest_spec = ns[0]\n", + "nest_utils_for_choice = add_ev1_random(utils_df, nest_spec)" ], "metadata": { "collapsed": false, @@ -1091,13 +1077,20 @@ }, { "cell_type": "code", - "execution_count": 103, - "outputs": [], + "execution_count": 116, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n137248721 -1.116115 -2855.401829 -0.085418 -0.101729 \n137248725 -0.865187 -2855.150902 0.350254 0.148855 \n211388201 -2855.281895 -5709.567609 -inf -17.128230 \n211388205 -2856.635577 -5710.921291 -inf -17.918686 \n806388401 -2855.821604 -5710.107318 -inf -9.031970 \n806388405 -2857.992149 -5712.277863 -inf -10.161818 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n137248721 -2854.387444 0.269616 -2855.394889 -5709.680604 -inf \n137248725 -2854.136859 0.705169 -2855.144442 -5709.430157 -inf \n211388201 -2871.413945 -5.588125 -20.987850 -2875.273564 -6.938992 \n211388205 -2872.204400 -5.973433 -21.553014 -2875.838729 -7.245448 \n806388401 -2863.317684 -3.070264 -10.680802 -2864.966516 -3.647355 \n806388405 -2864.447532 -3.062176 -11.394371 -2865.680085 -3.493570 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n137248721 0.822679 16.250835 -1367.832304 11.946618 -1970.527596 \n137248725 1.416687 16.732000 -1367.382139 12.573430 -1970.304027 \n211388201 -3.529757 -3.726114 -1380.907142 -2.354947 9.913300 \n211388205 -3.882701 -3.857039 -1381.038067 -2.536786 10.345617 \n806388401 -1.816422 6.302066 -1378.371156 4.610776 12.685721 \n806388405 -1.445835 6.594584 -1377.923639 5.146648 13.052200 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n137248721 -3968.527596 -3968.527596 -3968.527596 -3968.527596 -inf \n137248725 -3968.304027 -3968.304027 -3968.304027 -3968.304027 -inf \n211388201 -1987.688271 -1987.688271 -1987.688271 -1987.688271 5.276609 \n211388205 -1987.654383 -1987.654383 -1987.654383 -1987.654383 5.407303 \n806388401 -1984.132938 -1983.673938 -1984.048738 -1984.094938 6.414384 \n806388405 -1983.897400 -1983.438400 -1983.813200 -1983.859400 6.915048 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n137248721 -1970.647702 -3973.145591 -3973.145591 -3974.414085 -3975.433221 \n137248725 -1970.656867 -3975.391993 -3975.391993 -3971.028330 -3977.511255 \n211388201 -1988.046728 -3990.654273 -3990.654273 -1993.837992 -1996.844325 \n211388205 -1987.882804 -3992.966000 -3992.966000 -1990.428019 -1997.126285 \n806388401 -1985.366765 -3989.217824 -3989.217824 -3990.767471 -3993.606059 \n806388405 -1985.037673 -3990.311044 -3990.311044 -3986.801670 -3993.797451 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\ntrip_id \n137248721 -inf -inf -24.319775 -24.959142 -24.065572 -7.937642 \n137248725 -inf -inf -23.947836 -24.710809 -23.747349 -7.313866 \n211388201 -inf 4.127014 -15.616441 -3.619423 -4.823285 -0.615994 \n211388205 -inf 4.133540 -15.596812 -3.665464 -4.825869 -0.787171 \n806388401 -inf 4.691645 -19.088912 -19.441124 -16.306290 -5.701741 \n806388405 -inf 5.377382 -18.588659 -19.197216 -16.002477 -5.000992 \n\n root \ntrip_id \n137248721 11.435801 \n137248725 11.480441 \n211388201 3.456910 \n211388205 3.643469 \n806388401 5.193789 \n806388405 5.134877 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.116115-2855.401829-0.085418-0.101729-2854.3874440.269616-2855.394889-5709.680604-inf0.82267916.250835-1367.83230411.946618-1970.527596-3968.527596-3968.527596-3968.527596-3968.527596-inf-1970.647702-3973.145591-3973.145591-3974.414085-3975.433221-inf-inf-24.319775-24.959142-24.065572-7.93764211.435801
137248725-0.865187-2855.1509020.3502540.148855-2854.1368590.705169-2855.144442-5709.430157-inf1.41668716.732000-1367.38213912.573430-1970.304027-3968.304027-3968.304027-3968.304027-3968.304027-inf-1970.656867-3975.391993-3975.391993-3971.028330-3977.511255-inf-inf-23.947836-24.710809-23.747349-7.31386611.480441
211388201-2855.281895-5709.567609-inf-17.128230-2871.413945-5.588125-20.987850-2875.273564-6.938992-3.529757-3.726114-1380.907142-2.3549479.913300-1987.688271-1987.688271-1987.688271-1987.6882715.276609-1988.046728-3990.654273-3990.654273-1993.837992-1996.844325-inf4.127014-15.616441-3.619423-4.823285-0.6159943.456910
211388205-2856.635577-5710.921291-inf-17.918686-2872.204400-5.973433-21.553014-2875.838729-7.245448-3.882701-3.857039-1381.038067-2.53678610.345617-1987.654383-1987.654383-1987.654383-1987.6543835.407303-1987.882804-3992.966000-3992.966000-1990.428019-1997.126285-inf4.133540-15.596812-3.665464-4.825869-0.7871713.643469
806388401-2855.821604-5710.107318-inf-9.031970-2863.317684-3.070264-10.680802-2864.966516-3.647355-1.8164226.302066-1378.3711564.61077612.685721-1984.132938-1983.673938-1984.048738-1984.0949386.414384-1985.366765-3989.217824-3989.217824-3990.767471-3993.606059-inf4.691645-19.088912-19.441124-16.306290-5.7017415.193789
806388405-2857.992149-5712.277863-inf-10.161818-2864.447532-3.062176-11.394371-2865.680085-3.493570-1.4458356.594584-1377.9236395.14664813.052200-1983.897400-1983.438400-1983.813200-1983.8594006.915048-1985.037673-3990.311044-3990.311044-3986.801670-3993.797451-inf5.377382-18.588659-19.197216-16.002477-5.0009925.134877
\n
" + }, + "execution_count": 116, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# alts = [\"DRIVEALONEFREE\", \"DRIVEALONEPAY\"]\n", - "# #print(nest_utils_for_choice[alts])\n", - "# t_ = nest_utils_for_choice[alts].idxmax(1)\n", - "# t_.apply(is_alternative)" + "nest_utils_for_choice" ], "metadata": { "collapsed": false, @@ -1108,35 +1101,28 @@ }, { "cell_type": "code", - "execution_count": 104, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nesting depth is 4\n" - ] - } - ], + "execution_count": 106, + "outputs": [], "source": [ "all_alternatives = list(map(lambda x: x.name, filter(lambda x: x.is_leaf, logit.each_nest(nest_spec))))\n", "def is_alternative(name):\n", " return name in all_alternatives\n", "\n", - "def group_nests_by_level(nest_spec):\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", - " print(f\"Nesting depth is {depth}\")\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in logit.each_nest(nest_spec):\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " return nest_levels\n", + "# NOW IN logit.group_nests_by_level(nest_spec)\n", + "# def group_nests_by_level(nest_spec):\n", + "# # group nests by level:\n", + "# depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", + "# #print(f\"Nesting depth is {depth}\")\n", + "# nest_levels = {x: [] for x in range(1, depth+1)}\n", + "# for n in logit.each_nest(nest_spec):\n", + "# nest_levels[n.level].append(n.name)\n", + "# assert len(nest_levels[1]) == 1\n", + "# # assert nest_levels[1][0] == 'root'\n", + "# return nest_levels\n", "\n", "nest_utils_for_choice[\"choice\"] = None\n", "\n", - "for level, alts in group_nests_by_level(nest_spec).items():\n", + "for level, alts in logit.group_nests_by_level(nest_spec).items():\n", " if level == 1:\n", " continue\n", " no_choices_made_yet = nest_utils_for_choice[\"choice\"].isnull()\n", @@ -1153,14 +1139,14 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 107, "outputs": [ { "data": { "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n137248721 -1.116115 -2855.401829 -0.085418 -0.101729 \n137248725 -0.865187 -2855.150902 0.350254 0.148855 \n211388201 -2855.281895 -5709.567609 -inf -17.128230 \n211388205 -2856.635577 -5710.921291 -inf -17.918686 \n806388401 -2855.821604 -5710.107318 -inf -9.031970 \n806388405 -2857.992149 -5712.277863 -inf -10.161818 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n137248721 -2854.387444 0.269616 -2855.394889 -5709.680604 -inf \n137248725 -2854.136859 0.705169 -2855.144442 -5709.430157 -inf \n211388201 -2871.413945 -5.588125 -20.987850 -2875.273564 -6.938992 \n211388205 -2872.204400 -5.973433 -21.553014 -2875.838729 -7.245448 \n806388401 -2863.317684 -3.070264 -10.680802 -2864.966516 -3.647355 \n806388405 -2864.447532 -3.062176 -11.394371 -2865.680085 -3.493570 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n137248721 0.822679 16.250835 -1367.832304 11.946618 -1970.527596 \n137248725 1.416687 16.732000 -1367.382139 12.573430 -1970.304027 \n211388201 -3.529757 -3.726114 -1380.907142 -2.354947 9.913300 \n211388205 -3.882701 -3.857039 -1381.038067 -2.536786 10.345617 \n806388401 -1.816422 6.302066 -1378.371156 4.610776 12.685721 \n806388405 -1.445835 6.594584 -1377.923639 5.146648 13.052200 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n137248721 -3968.527596 -3968.527596 -3968.527596 -3968.527596 -inf \n137248725 -3968.304027 -3968.304027 -3968.304027 -3968.304027 -inf \n211388201 -1987.688271 -1987.688271 -1987.688271 -1987.688271 5.276609 \n211388205 -1987.654383 -1987.654383 -1987.654383 -1987.654383 5.407303 \n806388401 -1984.132938 -1983.673938 -1984.048738 -1984.094938 6.414384 \n806388405 -1983.897400 -1983.438400 -1983.813200 -1983.859400 6.915048 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n137248721 -1970.647702 -3973.145591 -3973.145591 -3974.414085 -3975.433221 \n137248725 -1970.656867 -3975.391993 -3975.391993 -3971.028330 -3977.511255 \n211388201 -1988.046728 -3990.654273 -3990.654273 -1993.837992 -1996.844325 \n211388205 -1987.882804 -3992.966000 -3992.966000 -1990.428019 -1997.126285 \n806388401 -1985.366765 -3989.217824 -3989.217824 -3990.767471 -3993.606059 \n806388405 -1985.037673 -3990.311044 -3990.311044 -3986.801670 -3993.797451 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\ntrip_id \n137248721 -inf -inf -24.319775 -24.959142 -24.065572 -7.937642 \n137248725 -inf -inf -23.947836 -24.710809 -23.747349 -7.313866 \n211388201 -inf 4.127014 -15.616441 -3.619423 -4.823285 -0.615994 \n211388205 -inf 4.133540 -15.596812 -3.665464 -4.825869 -0.787171 \n806388401 -inf 4.691645 -19.088912 -19.441124 -16.306290 -5.701741 \n806388405 -inf 5.377382 -18.588659 -19.197216 -16.002477 -5.000992 \n\n root choice \ntrip_id \n137248721 11.435801 WALK \n137248725 11.480441 WALK \n211388201 3.456910 WALK_LOC \n211388205 3.643469 WALK_LOC \n806388401 5.193789 WALK_LOC \n806388405 5.134877 WALK_LOC ", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILrootchoice
trip_id
137248721-1.116115-2855.401829-0.085418-0.101729-2854.3874440.269616-2855.394889-5709.680604-inf0.82267916.250835-1367.83230411.946618-1970.527596-3968.527596-3968.527596-3968.527596-3968.527596-inf-1970.647702-3973.145591-3973.145591-3974.414085-3975.433221-inf-inf-24.319775-24.959142-24.065572-7.93764211.435801WALK
137248725-0.865187-2855.1509020.3502540.148855-2854.1368590.705169-2855.144442-5709.430157-inf1.41668716.732000-1367.38213912.573430-1970.304027-3968.304027-3968.304027-3968.304027-3968.304027-inf-1970.656867-3975.391993-3975.391993-3971.028330-3977.511255-inf-inf-23.947836-24.710809-23.747349-7.31386611.480441WALK
211388201-2855.281895-5709.567609-inf-17.128230-2871.413945-5.588125-20.987850-2875.273564-6.938992-3.529757-3.726114-1380.907142-2.3549479.913300-1987.688271-1987.688271-1987.688271-1987.6882715.276609-1988.046728-3990.654273-3990.654273-1993.837992-1996.844325-inf4.127014-15.616441-3.619423-4.823285-0.6159943.456910WALK_LOC
211388205-2856.635577-5710.921291-inf-17.918686-2872.204400-5.973433-21.553014-2875.838729-7.245448-3.882701-3.857039-1381.038067-2.53678610.345617-1987.654383-1987.654383-1987.654383-1987.6543835.407303-1987.882804-3992.966000-3992.966000-1990.428019-1997.126285-inf4.133540-15.596812-3.665464-4.825869-0.7871713.643469WALK_LOC
806388401-2855.821604-5710.107318-inf-9.031970-2863.317684-3.070264-10.680802-2864.966516-3.647355-1.8164226.302066-1378.3711564.61077612.685721-1984.132938-1983.673938-1984.048738-1984.0949386.414384-1985.366765-3989.217824-3989.217824-3990.767471-3993.606059-inf4.691645-19.088912-19.441124-16.306290-5.7017415.193789WALK_LOC
806388405-2857.992149-5712.277863-inf-10.161818-2864.447532-3.062176-11.394371-2865.680085-3.493570-1.4458356.594584-1377.9236395.14664813.052200-1983.897400-1983.438400-1983.813200-1983.8594006.915048-1985.037673-3990.311044-3990.311044-3986.801670-3993.797451-inf5.377382-18.588659-19.197216-16.002477-5.0009925.134877WALK_LOC
\n
" }, - "execution_count": 105, + "execution_count": 107, "metadata": {}, "output_type": "execute_result" } From 5dc1a44ed9303e3269207f128a08842954ae49e1 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 4 May 2022 22:11:12 +1000 Subject: [PATCH 005/298] baby steps towards validation --- activitysim/core/logit.py | 5 +- activitysim/core/simulate.py | 29 +- notebooks/frozen_rand_mode_choice_dev.ipynb | 6 +- notebooks/validate_frozen_impl.ipynb | 2632 +++++++++++++++++++ 4 files changed, 2661 insertions(+), 11 deletions(-) create mode 100644 notebooks/validate_frozen_impl.ipynb diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 18d933cbd6..e3f87d4248 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -222,8 +222,11 @@ def is_alternative(name): nest_utils_for_choice.loc[no_choices_made_yet, "choice"] = \ np.where(choice_this_level.apply(is_alternative), choice_this_level, None) - assert not nest_utils_for_choice["choice"].isnull.any(), "No choice for XXX - implement reporting" + assert not nest_utils_for_choice["choice"].isnull().any(), "No choice for XXX - implement reporting" choices = pd.Series(nest_utils_for_choice["choice"], index=nested_utilities.index) + + assert not choices.isnull().any(), "No choice for XXX - implement reporting" + return choices diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 6f659606c6..4f9b7d9a2a 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1041,7 +1041,6 @@ def eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, estima logsums = pd.Series(nested_utilities.root, index=choosers.index) chunk.log_df(trace_label, "logsums", logsums) - # TODO: add checks on utilities? # # note base_probabilities could all be zero since we allowed all probs for nests to be zero # # check here to print a clear message but make_choices will raise error if probs don't sum to 1 @@ -1223,6 +1222,7 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, want_logsums=False, estimator=None, trace_label=None, trace_choice_name=None, trace_column_names=None, + choose_individual_max_utility=False ): """ Run an MNL or NL simulation for when the model spec does not involve alternative @@ -1273,6 +1273,7 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, set_skim_wrapper_targets(choosers, skims) if nest_spec is None: + # TODO: add frozen individual ru for mnl choices = eval_mnl(choosers, spec, locals_d, custom_chooser, log_alt_losers=log_alt_losers, want_logsums=want_logsums, @@ -1280,12 +1281,20 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, trace_label=trace_label, trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) else: - choices = eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, - log_alt_losers=log_alt_losers, - want_logsums=want_logsums, - estimator=estimator, - trace_label=trace_label, - trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) + if choose_individual_max_utility: + choices = eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, + log_alt_losers=log_alt_losers, + want_logsums=want_logsums, + estimator=estimator, + trace_label=trace_label, + trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) + else: + choices = eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, + log_alt_losers=log_alt_losers, + want_logsums=want_logsums, + estimator=estimator, + trace_label=trace_label, + trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) return choices @@ -1308,7 +1317,8 @@ def simple_simulate(choosers, spec, nest_spec, log_alt_losers=False, want_logsums=False, estimator=None, - trace_label=None, trace_choice_name=None, trace_column_names=None): + trace_label=None, trace_choice_name=None, trace_column_names=None, + choose_individual_max_utility=False): """ Run an MNL or NL simulation for when the model spec does not involve alternative specific data, e.g. there are no interactions with alternative @@ -1334,7 +1344,8 @@ def simple_simulate(choosers, spec, nest_spec, estimator=estimator, trace_label=chunk_trace_label, trace_choice_name=trace_choice_name, - trace_column_names=trace_column_names) + trace_column_names=trace_column_names, + choose_individual_max_utility=choose_individual_max_utility) result_list.append(choices) diff --git a/notebooks/frozen_rand_mode_choice_dev.ipynb b/notebooks/frozen_rand_mode_choice_dev.ipynb index 4a85f62408..101953e516 100644 --- a/notebooks/frozen_rand_mode_choice_dev.ipynb +++ b/notebooks/frozen_rand_mode_choice_dev.ipynb @@ -1189,7 +1189,11 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [], + "source": [ + "nest_utils_for_choice = utils_df.copy()\n", + "\n", + "logit.make_choices_ru_frozen(nest_utils_for_choice, nest_spec, trace_label=\"blah\")" + ], "metadata": { "collapsed": false, "pycharm": { diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb new file mode 100644 index 0000000000..1fd45d784c --- /dev/null +++ b/notebooks/validate_frozen_impl.ipynb @@ -0,0 +1,2632 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", + "metadata": {}, + "source": [ + "# validate results" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.385153Z", + "iopub.status.busy": "2022-05-01T10:17:57.384881Z", + "iopub.status.idle": "2022-05-01T10:17:57.534433Z", + "shell.execute_reply": "2022-05-01T10:17:57.533096Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%load_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3dba451-1e10-403e-8614-35d57e6577f4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.536623Z", + "iopub.status.busy": "2022-05-01T10:17:57.536012Z", + "iopub.status.idle": "2022-05-01T10:17:57.542755Z", + "shell.execute_reply": "2022-05-01T10:17:57.541685Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:00.350944Z", + "iopub.status.busy": "2022-05-01T10:18:00.350730Z", + "iopub.status.idle": "2022-05-01T10:18:12.760977Z", + "shell.execute_reply": "2022-05-01T10:18:12.760013Z", + "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import argparse\n", + "from datetime import datetime\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from numpy.random import default_rng\n", + "\n", + "from activitysim.cli import run\n", + "from activitysim.core import inject\n", + "from activitysim.core import tracing\n", + "from activitysim.core import config\n", + "from activitysim.core import pipeline\n", + "from activitysim.core import chunk\n", + "from activitysim.core import simulate\n", + "from activitysim.core import logit\n", + "from activitysim.abm.models.util import estimation\n", + "from activitysim.core import expressions\n", + "from activitysim.core.util import assign_in_place" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:16.944537Z", + "iopub.status.busy": "2022-05-01T10:18:16.944291Z", + "iopub.status.idle": "2022-05-01T10:18:17.124764Z", + "shell.execute_reply": "2022-05-01T10:18:17.123725Z", + "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "pd.set_option(\"max_columns\", 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:17.839947Z", + "iopub.status.busy": "2022-05-01T10:18:17.839070Z", + "iopub.status.idle": "2022-05-01T10:18:18.019676Z", + "shell.execute_reply": "2022-05-01T10:18:18.018689Z", + "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_example_mtc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:18.894533Z", + "iopub.status.busy": "2022-05-01T10:18:18.894303Z", + "iopub.status.idle": "2022-05-01T10:18:19.078807Z", + "shell.execute_reply": "2022-05-01T10:18:19.077951Z", + "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "os.chdir(example_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:22.111723Z", + "iopub.status.busy": "2022-05-01T10:18:22.111490Z", + "iopub.status.idle": "2022-05-01T10:18:22.297437Z", + "shell.execute_reply": "2022-05-01T10:18:22.296501Z", + "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "run.add_run_args(parser)\n", + "# args = parser.parse_args()\n", + "# parser.parse_args(['--sum', '7', '-1', '42'])\n", + "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", + "#run.run(args) # 2mins full example run\n", + "\n", + "\n", + "if not inject.is_injectable('preload_injectables'):\n", + " from activitysim import abm # register abm steps and other abm-specific injectables\n", + "run.handle_standard_args(args) # possibly update injectables" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "outputs": [], + "source": [ + "def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=True):\n", + "\n", + " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", + " resume_after = \"trip_scheduling\"\n", + " model_name = \"trip_mode_choice\"\n", + " chunk_size = 0 # test_mtc means no chunking\n", + "\n", + " pipeline.open_pipeline(resume_after)\n", + " # preload any bulky injectables (e.g. skims) not in pipeline\n", + " inject.get_injectable('preload_injectables', None)\n", + " pipeline._PIPELINE.rng().begin_step(model_name)\n", + " step_name = model_name\n", + " args = {}\n", + " checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + " inject.set_step_args(args)\n", + "\n", + " trips = inject.get_table('trips')\n", + " tours_merged = inject.get_table('tours_merged')\n", + " network_los = inject.get_injectable('network_los')\n", + "\n", + " trace_label = 'trip_mode_choice'\n", + " model_settings_file_name = 'trip_mode_choice.yaml'\n", + " model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + " mode_column_name = 'trip_mode'\n", + "\n", + " trips_df = trips.to_frame()\n", + " print(\"Running with %d trips\", trips_df.shape[0])\n", + "\n", + " tours_merged = tours_merged.to_frame()\n", + " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "\n", + " # - trips_merged - merge trips and tours_merged\n", + " trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + " assert trips_merged.index.equals(trips.index)\n", + "\n", + " # setup skim keys\n", + " assert ('trip_period' not in trips_merged)\n", + " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + " orig_col = 'origin'\n", + " dest_col = 'destination'\n", + "\n", + " constants = {}\n", + " constants.update(config.get_model_constants(model_settings))\n", + " constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + " })\n", + "\n", + " skim_dict = network_los.get_default_skim_dict()\n", + "\n", + " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + " skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + " }\n", + "\n", + " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + " nest_specs = config.get_logit_model_settings(model_settings)\n", + "\n", + " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + "\n", + " if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", + " continue\n", + "\n", + " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " (primary_purpose, len(trips_segment.index), ))\n", + "\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " ################ Replace wrapper function\n", + " # choices = mode_choice_simulate(...)\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", + " choices = simulate.simple_simulate(\n", + " choosers=trips_segment,\n", + " spec=spec,\n", + " nest_spec=nest_spec,\n", + " skims=skims,\n", + " locals_d=locals_dict,\n", + " chunk_size=chunk_size,\n", + " want_logsums=logsum_column_name is not None,\n", + " trace_label=segment_trace_label,\n", + " trace_choice_name='trip_mode_choice',\n", + " estimator=estimator,\n", + " trace_column_names=None,\n", + " choose_individual_max_utility=choose_individual_max_utility)\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " if not choose_individual_max_utility:\n", + " alts = spec.columns\n", + " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", + " ################\n", + " choices_list.append(choices)\n", + " choices_df_asim = pd.concat(choices_list)\n", + "\n", + " # update trips table with choices (and potionally logssums)\n", + " trips_df = trips.to_frame()\n", + "\n", + " if (do_these_purposes is not None):\n", + " trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", + "\n", + " assign_in_place(trips_df, choices_df_asim)\n", + " assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + " finalise = True\n", + " if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + " print(\"Done\")\n", + "\n", + " return trips_df" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:28:53.643404Z", + "iopub.status.busy": "2021-09-01T02:28:53.643055Z", + "iopub.status.idle": "2021-09-01T02:29:01.223951Z", + "shell.execute_reply": "2021-09-01T02:29:01.222865Z", + "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" + }, + "tags": [], + "pycharm": { + "is_executing": true + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'atwork' (27 trips)\n", + "trip_mode_choice tour_type 'eatout' (33 trips)\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n", + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n" + ] + } + ], + "source": [ + "trips_df_ru = run_trip_mode_choice()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# OLD\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "id": "2f080150-c15a-4059-8a26-42a8c0072606", + "metadata": { + "execution": { + "iopub.execute_input": "2021-08-31T23:16:40.014024Z", + "iopub.status.busy": "2021-08-31T23:16:40.013770Z", + "iopub.status.idle": "2021-08-31T23:16:40.236326Z", + "shell.execute_reply": "2021-08-31T23:16:40.235553Z", + "shell.execute_reply.started": "2021-08-31T23:16:40.013973Z" + }, + "tags": [] + }, + "source": [ + "### make choice at each level" + ] + }, + { + "cell_type": "code", + "execution_count": 315, + "id": "2ed71b54-67e4-4087-957a-75f4fa184144", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:43.778081Z", + "iopub.status.busy": "2021-09-01T01:11:43.777757Z", + "iopub.status.idle": "2021-09-01T01:11:43.975332Z", + "shell.execute_reply": "2021-09-01T01:11:43.974588Z", + "shell.execute_reply.started": "2021-09-01T01:11:43.778055Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def get_alternatives(nests, name):\n", + " alts = list(filter(lambda x: x.name == name, nests))\n", + " assert len(alts) == 1, f\"{len(alts)} not one\"\n", + " alts = alts[0].alternatives\n", + " return alts\n", + "\n", + "def recursive_choice(row, columns, nest_levels, nests):\n", + " choices = row[columns].idxmax() #axis=1).values[0]\n", + " next_level_columns = get_alternatives(nests, choices)\n", + " #print(f\"{choices} leads to columns {next_level_columns}\")\n", + " if next_level_columns is None:\n", + " return choices \n", + " new_choice = recursive_choice(row, next_level_columns, nest_levels, nests)\n", + " return new_choice\n", + "\n", + "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", + "\n", + "def make_choice(utils_df, nests, nest_levels, seed=None):\n", + " rng = default_rng(seed=seed)\n", + " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", + " probs_arr = utils_df - np.log(-np.log(rands))\n", + " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", + " return choices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", + "seed = 9326543345\n", + "make_choice(utils_df, nests_, nest_levels, seed)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 316, + "id": "30424562-2627-446b-abd5-7c763c52060e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T01:11:49.444289Z", + "iopub.status.busy": "2021-09-01T01:11:49.444002Z", + "iopub.status.idle": "2021-09-01T01:11:49.664214Z", + "shell.execute_reply": "2021-09-01T01:11:49.663088Z", + "shell.execute_reply.started": "2021-09-01T01:11:49.444266Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n" + ] + } + ], + "source": [ + "# group nests by level:\n", + "depth = np.max([x.level for x in nests_])\n", + "print(depth)\n", + "nest_levels = {x: [] for x in range(1, depth+1)}\n", + "for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + "assert len(nest_levels[1]) == 1\n", + "assert nest_levels[1][0] == 'root'" + ] + }, + { + "cell_type": "code", + "execution_count": 453, + "id": "806ce752-0927-4d5b-a6cc-68d6c9b8a05e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:55:59.542669Z", + "iopub.status.busy": "2021-09-01T04:55:59.542360Z", + "iopub.status.idle": "2021-09-01T04:55:59.771722Z", + "shell.execute_reply": "2021-09-01T04:55:59.770801Z", + "shell.execute_reply.started": "2021-09-01T04:55:59.542615Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + " nested_probabilities = \\\n", + " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", + " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) \n", + " return base_probabilities\n", + "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 451, + "id": "054fd9b1-72fc-49cc-a790-48ef75bcbaed", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:55:08.260140Z", + "iopub.status.busy": "2021-09-01T04:55:08.259914Z", + "iopub.status.idle": "2021-09-01T04:55:09.140095Z", + "shell.execute_reply": "2021-09-01T04:55:09.139119Z", + "shell.execute_reply.started": "2021-09-01T04:55:08.260116Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "trip_id\n", + "86627409 WALK_LRF\n", + "86627413 WALK_LRF\n", + "86673657 WALK_LOC\n", + "86673658 WALK\n", + "86673659 WALK\n", + " ... \n", + "2464446025 WALK\n", + "2464446029 WALK\n", + "2464449633 WALK\n", + "2464449634 WALK\n", + "2464449637 WALK\n", + "Length: 168, dtype: object" + ] + }, + "execution_count": 451, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def simple_simulate_rum(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", + "\n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices\n", + "\n", + "simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" + ] + }, + { + "cell_type": "code", + "execution_count": 393, + "id": "f3c5c1ba-e9c3-4511-98bf-9ac98c987da7", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T02:39:52.539983Z", + "iopub.status.busy": "2021-09-01T02:39:52.539668Z", + "iopub.status.idle": "2021-09-01T02:39:52.796170Z", + "shell.execute_reply": "2021-09-01T02:39:52.795498Z", + "shell.execute_reply.started": "2021-09-01T02:39:52.539944Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " segment_trace_label, log_alt_losers=log_alt_losers, \n", + " trace_column_names=trace_column_names, seed=seed)\n", + "\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " choices_list.append(choices)\n", + "\n", + " choices_df = pd.concat(choices_list)\n", + " return choices_df" + ] + }, + { + "cell_type": "code", + "execution_count": 454, + "id": "74eeedd3-4ade-4729-8170-79fa9dcf11f8", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:58:52.825042Z", + "iopub.status.busy": "2021-09-01T04:58:52.824768Z", + "iopub.status.idle": "2021-09-01T04:58:53.042884Z", + "shell.execute_reply": "2021-09-01T04:58:53.042061Z", + "shell.execute_reply.started": "2021-09-01T04:58:52.825004Z" + } + }, + "outputs": [], + "source": [ + "## caculate probabilities with Asim methodology, should be correct\n", + "def gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None):\n", + " full_probs = [] # analytical probs\n", + "\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " #choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " # segment_trace_label, log_alt_losers=log_alt_losers, \n", + " # trace_column_names=trace_column_names, seed=seed)\n", + " probs = simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, \n", + " estimator, segment_trace_label, log_alt_losers, \n", + " trace_column_names)\n", + " full_probs.append(probs)\n", + " probs_df = pd.concat(full_probs)\n", + " return probs_df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 455, + "id": "295a4fd1-8e85-47e2-af82-9f1beac31d63", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T04:59:46.126555Z", + "iopub.status.busy": "2021-09-01T04:59:46.126262Z", + "iopub.status.idle": "2021-09-01T04:59:52.979908Z", + "shell.execute_reply": "2021-09-01T04:59:52.979043Z", + "shell.execute_reply.started": "2021-09-01T04:59:46.126522Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2021-09-01 14:59:46.353007 Start\n", + "2021-09-01 14:59:52.976736 End\n" + ] + } + ], + "source": [ + "print(f\"{datetime.now()} Start\")\n", + "pipeline.open_pipeline(resume_after)\n", + "inject.get_injectable('preload_injectables', None)\n", + "model_name = \"trip_mode_choice\"\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "step_name = model_name\n", + "args = {}\n", + "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "chunk_size = 0\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "trips_df = trips.to_frame()\n", + "#print(\"Running with %d trips\", trips_df.shape[0])\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + "orig_col = 'origin'\n", + "dest_col = 'destination'\n", + "\n", + "constants = {}\n", + "constants.update(config.get_model_constants(model_settings))\n", + "constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + "})\n", + "\n", + "skim_dict = network_los.get_default_skim_dict()\n", + "\n", + "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + "skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + "}\n", + "\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "\n", + "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + "\n", + "all_choices = []\n", + "for i in range(100):\n", + " if i % 10 == 0:\n", + " print(f\"{datetime.now()} iteration {i}\")\n", + " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=None)\n", + " all_choices.append(choices_df)\n", + "all_choices = pd.concat(all_choices, axis=1)\n", + "\n", + "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", + " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", + " log_alt_losers=None, trace_column_names=None)\n", + "\n", + "# update trips table with choices (and potionally logssums)\n", + "#trips_df = trips.to_frame()\n", + "#\n", + "#assign_in_place(trips_df, choices_df)\n", + "#assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + "print(f\"{datetime.now()} End\")" + ] + }, + { + "cell_type": "code", + "execution_count": 463, + "id": "dc87e904-16d1-44d4-81b7-cafbe4e223c3", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:01:40.901982Z", + "iopub.status.busy": "2021-09-01T05:01:40.901759Z", + "iopub.status.idle": "2021-09-01T05:01:41.337758Z", + "shell.execute_reply": "2021-09-01T05:01:41.336757Z", + "shell.execute_reply.started": "2021-09-01T05:01:40.901946Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#all_choices.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", + "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts = val_counts / all_choices.shape[1]\n", + "#val_counts = val_counts.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", + "#val_counts['prob_of_asim_choice'] = val_counts.apply(lambda x: x[x.asim], axis=1) # this is what our simulation says w.r.t. to asim choice\n", + "# for 100% and many samples should mostly agree" + ] + }, + { + "cell_type": "code", + "execution_count": 464, + "id": "09bf151c-a39a-4823-acfd-9c8ae747c338", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:01:46.119117Z", + "iopub.status.busy": "2021-09-01T05:01:46.118849Z", + "iopub.status.idle": "2021-09-01T05:01:46.354114Z", + "shell.execute_reply": "2021-09-01T05:01:46.353248Z", + "shell.execute_reply.started": "2021-09-01T05:01:46.119092Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "#val_counts['prob_of_asim_choice'].hist(bins=100);" + ] + }, + { + "cell_type": "code", + "execution_count": 490, + "id": "f3b6ade9-d875-4104-b438-b53ef42e342c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T05:10:08.653227Z", + "iopub.status.busy": "2021-09-01T05:10:08.652978Z", + "iopub.status.idle": "2021-09-01T05:10:08.915620Z", + "shell.execute_reply": "2021-09-01T05:10:08.914513Z", + "shell.execute_reply.started": "2021-09-01T05:10:08.653189Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", + "analytical 3.152 0.852 0.0 0.0 0.0 \n", + "experiment 3.243 0.880 0.0 0.0 0.0 \n", + "\n", + " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", + "analytical 0.0 0.0 0.0 0.685 0.0 \n", + "experiment 0.0 0.0 0.0 0.772 0.0 \n", + "\n", + " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK \\\n", + "analytical 0.129 0.0 0.182 0.260 1.334 63.708 \n", + "experiment 0.156 0.0 0.174 0.259 1.434 63.243 \n", + "\n", + " WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", + "analytical 0.0 0.0 0.455 18.355 10.887 \n", + "experiment 0.0 0.0 0.394 18.639 10.807 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mode_share_analytical = (probs_nl.sum(axis=0) / probs_nl.shape[0]).to_frame('analytical')\n", + "assert np.allclose(mode_share_analytical.sum(), 1)\n", + "mode_share_rum = (val_counts.sum(axis=0) / val_counts.shape[0]).to_frame('experiment')\n", + "assert np.allclose(mode_share_rum.sum(), 1)\n", + "full_share = mode_share_analytical.join(mode_share_rum, how='outer').fillna(0)\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * full_share).T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e48a56e-d613-4dfd-bef6-62bee524f12f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50914e1d-750d-440c-9617-f96bd2a46c56", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2d70e0e-520e-49a6-8024-ae31345f6ead", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "f9e8ef02-1d57-4b21-8aef-25a6e1095c02", + "metadata": {}, + "source": [ + "### try zenith normalisation of simple_simulate_rum" + ] + }, + { + "cell_type": "code", + "execution_count": 629, + "id": "db235e90-d2e5-4e90-b41c-6d90a670e41d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:24.045881Z", + "iopub.status.busy": "2021-09-01T11:37:24.045637Z", + "iopub.status.idle": "2021-09-01T11:37:24.276488Z", + "shell.execute_reply": "2021-09-01T11:37:24.274898Z", + "shell.execute_reply.started": "2021-09-01T11:37:24.045844Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation belowi s equivalent.\n", + "def compute_nested_utilities_zenith(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) # / nest.product_of_coefficients #coefficient\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", + " \n", + " # now go over all leaves and correct for scale\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] /= nest.coefficient\n", + " \n", + " return nested_utilities\n", + "\n", + "\n", + "def simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " utils_df = compute_nested_utilities_zenith(raw_utilities, nest_spec)\n", + "\n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices\n", + "\n", + "#simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" + ] + }, + { + "cell_type": "code", + "execution_count": 630, + "id": "f69bc3dc-84e4-4dd5-9a57-c053cadba4b5", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:24.828926Z", + "iopub.status.busy": "2021-09-01T11:37:24.828707Z", + "iopub.status.idle": "2021-09-01T11:37:25.046409Z", + "shell.execute_reply": "2021-09-01T11:37:25.045399Z", + "shell.execute_reply.started": "2021-09-01T11:37:24.828902Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", + " choices_list = []\n", + " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " # (primary_purpose, len(trips_segment.index), ))\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", + " choices = simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, \n", + " segment_trace_label, log_alt_losers=log_alt_losers, \n", + " trace_column_names=trace_column_names, seed=seed)\n", + "\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " choices_list.append(choices)\n", + "\n", + " choices_df = pd.concat(choices_list)\n", + " return choices_df" + ] + }, + { + "cell_type": "code", + "execution_count": 631, + "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:37:32.250808Z", + "iopub.status.busy": "2021-09-01T11:37:32.250548Z", + "iopub.status.idle": "2021-09-01T20:20:35.257463Z", + "shell.execute_reply": "2021-09-01T20:20:35.255964Z", + "shell.execute_reply.started": "2021-09-01T11:37:32.250782Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "num_samples = 10\n", + "\n", + "rng_test = default_rng(23423)\n", + "\n", + "print(f\"{datetime.now()} Start\")\n", + "pipeline.open_pipeline(resume_after)\n", + "inject.get_injectable('preload_injectables', None)\n", + "model_name = \"trip_mode_choice\"\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "\n", + "step_name = model_name\n", + "args = {}\n", + "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "chunk_size = 0\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "trips_df = trips.to_frame()\n", + "#print(\"Running with %d trips\", trips_df.shape[0])\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + "orig_col = 'origin'\n", + "dest_col = 'destination'\n", + "\n", + "constants = {}\n", + "constants.update(config.get_model_constants(model_settings))\n", + "constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + "})\n", + "\n", + "skim_dict = network_los.get_default_skim_dict()\n", + "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + "}\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + "all_choices_zenith = []\n", + "all_choices = []\n", + "for i in range(num_samples):\n", + " \n", + " seed = rng_test.integers(0, 100000) #int(9.3 * (i+1)**3) # why not\n", + " \n", + " if i % 50 == 0:\n", + " print(f\"{datetime.now()} iteration {i}\")\n", + " choices_df_zenith = stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", + " all_choices_zenith.append(choices_df_zenith)\n", + "\n", + " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", + " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", + " all_choices.append(choices_df)\n", + " \n", + " t_ = choices_df_zenith.merge(choices_df, left_index=True, right_index=True, suffixes=['_zenith', '_asim'])\n", + " diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", + " \n", + " #print(f\"seed {seed} leads to {diffs.shape[0]} differences. tripids {diffs.index}\")\n", + "\n", + "all_choices_zenith = pd.concat(all_choices_zenith, axis=1)\n", + "all_choices = pd.concat(all_choices, axis=1)\n", + "\n", + "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", + " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", + " log_alt_losers=None, trace_column_names=None)\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + "print(f\"{datetime.now()} End\")" + ] + }, + { + "cell_type": "code", + "execution_count": 643, + "id": "3abd7949-dc9c-469b-845e-26c17741f70e", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:33:21.533110Z", + "iopub.status.busy": "2021-09-01T20:33:21.531915Z", + "iopub.status.idle": "2021-09-01T20:33:21.740946Z", + "shell.execute_reply": "2021-09-01T20:33:21.739713Z", + "shell.execute_reply.started": "2021-09-01T20:33:21.533074Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Have 2038 samples\n" + ] + } + ], + "source": [ + "print(f\"Have {all_choices_zenith.shape[1]} samples\")" + ] + }, + { + "cell_type": "code", + "execution_count": 633, + "id": "8762bd6d-ac04-458c-869f-be86d8297351", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:21:27.778803Z", + "iopub.status.busy": "2021-09-01T20:21:27.778531Z", + "iopub.status.idle": "2021-09-01T20:21:28.675645Z", + "shell.execute_reply": "2021-09-01T20:21:28.674734Z", + "shell.execute_reply.started": "2021-09-01T20:21:27.778754Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "val_counts_zenith = all_choices_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts_zenith = val_counts_zenith / all_choices_zenith.shape[1]\n", + "\n", + "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "val_counts = val_counts / all_choices.shape[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 634, + "id": "0f566105-9424-457b-86f5-d60c1d63aac5", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:21:30.954894Z", + "iopub.status.busy": "2021-09-01T20:21:30.954654Z", + "iopub.status.idle": "2021-09-01T20:21:31.179689Z", + "shell.execute_reply": "2021-09-01T20:21:31.178304Z", + "shell.execute_reply.started": "2021-09-01T20:21:30.954867Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
experiment_zenith3.1500.8740.00.00.00.00.00.00.6800.00.1360.00.1540.2361.28563.7670.00.00.51518.82410.379
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", + "analytical 3.152 0.852 0.0 0.0 0.0 \n", + "experiment 3.243 0.880 0.0 0.0 0.0 \n", + "experiment_zenith 3.150 0.874 0.0 0.0 0.0 \n", + "\n", + " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", + "analytical 0.0 0.0 0.0 0.685 0.0 \n", + "experiment 0.0 0.0 0.0 0.772 0.0 \n", + "experiment_zenith 0.0 0.0 0.0 0.680 0.0 \n", + "\n", + " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE \\\n", + "analytical 0.129 0.0 0.182 0.260 1.334 \n", + "experiment 0.156 0.0 0.174 0.259 1.434 \n", + "experiment_zenith 0.136 0.0 0.154 0.236 1.285 \n", + "\n", + " WALK WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", + "analytical 63.708 0.0 0.0 0.455 18.355 10.887 \n", + "experiment 63.243 0.0 0.0 0.394 18.639 10.807 \n", + "experiment_zenith 63.767 0.0 0.0 0.515 18.824 10.379 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mode_share_rum_zenith = (val_counts_zenith.sum(axis=0) / val_counts_zenith.shape[0]).to_frame('experiment_zenith')\n", + "assert np.allclose(mode_share_rum_zenith.sum(), 1)\n", + "full_share_incl_zenith = full_share.merge(mode_share_rum_zenith, left_index=True, right_index=True, how='outer').fillna(0)\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * full_share_incl_zenith).T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c0d3081-b45b-4573-9246-e4a9d39591dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "612b2f04-45e2-4eb2-b52a-9f753427877f", + "metadata": {}, + "source": [ + "## investigate diverging seed and look at diff in formulation" + ] + }, + { + "cell_type": "code", + "execution_count": 619, + "id": "ac09dc75-a92f-43fb-a0cd-fe7de1f8b66d", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:12:02.988141Z", + "iopub.status.busy": "2021-09-01T11:12:02.987900Z", + "iopub.status.idle": "2021-09-01T11:12:03.212179Z", + "shell.execute_reply": "2021-09-01T11:12:03.210993Z", + "shell.execute_reply.started": "2021-09-01T11:12:02.988102Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# TODO: work out if our formulation and formulation based on asim probabilities is equivalent\n", + "def compute_nested_utilities_asim(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) / nest.product_of_coefficients\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", + " return nested_utilities\n", + "\n", + "def compute_nested_utilities_zenith_check(raw_utilities, nest_spec):\n", + " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] = \\\n", + " raw_utilities[name].astype(float) # scale correction is below\n", + " else:\n", + " with np.errstate(divide='ignore'):\n", + " nested_utilities[name] = \\\n", + " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", + " \n", + " # now go over all leaves and correct for scale\n", + " for nest in logit.each_nest(nest_spec):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " nested_utilities[name] /= nest.coefficient\n", + " \n", + " return nested_utilities" + ] + }, + { + "cell_type": "code", + "execution_count": 688, + "id": "4b7c6368-21ca-4442-969e-f2f8ff868842", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T20:49:56.958862Z", + "iopub.status.busy": "2021-09-01T20:49:56.958623Z", + "iopub.status.idle": "2021-09-01T20:49:57.191884Z", + "shell.execute_reply": "2021-09-01T20:49:57.190975Z", + "shell.execute_reply.started": "2021-09-01T20:49:56.958827Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None, use_zenith=True, raw_utilities=None):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " \n", + " if raw_utilities is None:\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + "\n", + " if use_zenith:\n", + " utils_df = compute_nested_utilities_zenith_check(raw_utilities, nest_spec)\n", + " else:\n", + " utils_df = compute_nested_utilities_asim(raw_utilities, nest_spec)\n", + "\n", + " # test\n", + " #return compute_nested_utilities_zenith_check(raw_utilities, nest_spec), compute_nested_utilities_asim(raw_utilities, nest_spec)\n", + " \n", + " nests_ = list(logit.each_nest(nest_spec))\n", + " # group nests by level:\n", + " depth = np.max([x.level for x in nests_])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in nests_:\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1\n", + " assert nest_levels[1][0] == 'root'\n", + " # make choices\n", + " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", + "\n", + " return choices" + ] + }, + { + "cell_type": "code", + "execution_count": 626, + "id": "bd6cadc3-79f5-4824-b55e-639c39d5ab18", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:13:12.077886Z", + "iopub.status.busy": "2021-09-01T11:13:12.077652Z", + "iopub.status.idle": "2021-09-01T11:13:12.290222Z", + "shell.execute_reply": "2021-09-01T11:13:12.288861Z", + "shell.execute_reply.started": "2021-09-01T11:13:12.077863Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# t_z, t_a = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed)\n", + "# tr_id = 86673661\n", + "# display(t_z.loc[t_z.index==tr_id])\n", + "# display(t_a.loc[t_a.index==tr_id])" + ] + }, + { + "cell_type": "code", + "execution_count": 625, + "id": "42e6da62-58e8-4b16-9169-9faa1fc8a162", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T11:13:06.847583Z", + "iopub.status.busy": "2021-09-01T11:13:06.847204Z", + "iopub.status.idle": "2021-09-01T11:13:08.401700Z", + "shell.execute_reply": "2021-09-01T11:13:08.400711Z", + "shell.execute_reply.started": "2021-09-01T11:13:06.847554Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "seed 51006 leads to 34 differences out of 168. tripids Int64Index([ 86673657, 86673661, 106741681, 106741682, 106741685,\n", + " 106741686, 106741687, 106741688, 211327433, 211327437,\n", + " 444793574, 484173905, 484173909, 535170694, 535620053,\n", + " 708171014, 943749470, 943749471, 1060575853, 1091770617,\n", + " 1146472489, 1146472493, 1276281769, 1276281773, 1658748793,\n", + " 1658748797, 1767013726, 1767186577, 1767186578, 1768237161,\n", + " 1768237165, 1768237166, 2463663417, 2463663421],\n", + " dtype='int64', name='trip_id')\n" + ] + } + ], + "source": [ + "seed = 51006\n", + "x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True)\n", + "y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False)\n", + "t_ = x_.to_frame('trip_mode_zenith').merge(y_.to_frame('trip_mode_asim'), left_index=True, right_index=True)\n", + "diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", + "print(f\"seed {seed} leads to {diffs.shape[0]} differences out of {t_.shape[0]}. tripids {diffs.index}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f3c0506-a79d-4a31-9d56-71b9966d0a92", + "metadata": {}, + "outputs": [], + "source": [ + "seed 51006 leads to 3 differences. tripids Int64Index([86673661, 535170689, 1060575849], dtype='int64', name='trip_id')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a5f332a-b1d7-4a80-bb21-d37a1611cf9f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24c5e4a1-6142-49d4-8da8-a1e266b3cb9b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 753, + "id": "2098907f-28af-4f53-af74-6aaee8a6c53c", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T23:04:23.861048Z", + "iopub.status.busy": "2021-09-01T23:04:23.860748Z", + "iopub.status.idle": "2021-09-01T23:04:24.069307Z", + "shell.execute_reply": "2021-09-01T23:04:24.068434Z", + "shell.execute_reply.started": "2021-09-01T23:04:23.861024Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "rng_ = default_rng(seed=100)\n", + "new_utils = pd.DataFrame(0.1 * -np.log(-np.log(rng_.uniform(0,1,raw_utilities.shape))), columns=raw_utilities.columns)\n", + "new_utils.index = raw_utilities.index" + ] + }, + { + "cell_type": "code", + "execution_count": 767, + "id": "da48a178-82b2-44a5-b45a-e9de3bb5e5c2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-01T23:17:20.369675Z", + "iopub.status.busy": "2021-09-01T23:17:20.369424Z", + "iopub.status.idle": "2021-09-01T23:17:20.666506Z", + "shell.execute_reply": "2021-09-01T23:17:20.665626Z", + "shell.execute_reply.started": "2021-09-01T23:17:20.369637Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
1372487210.0566420.0373050.0427800.0295760.0861540.0265860.0883130.1003310.0280210.0185160.0643910.0200090.0263990.0226220.0230070.0217540.0871260.0211710.0364750.0276500.135171
1372487250.1019020.0228760.0312270.0699900.0569130.0466850.0884110.0877850.0254140.0381620.0239880.0288000.0393180.0201600.0483640.0230030.0339590.0345970.0377080.0995230.041213
2113882010.0375620.0700330.0559510.0403890.0200410.0959740.0946780.0943680.0360140.0250510.0348600.0349530.0354990.0280400.0436500.0247130.0313880.0259310.0669990.0526780.051228
2113882050.0354800.0562560.0439440.0480850.0420780.0622210.1068190.0872650.0351170.0303990.0351540.0264690.0506460.0357270.0242840.0329050.0394270.0410920.0547140.0484590.063458
8063884010.0549180.0404880.0331490.0542940.0434770.0679820.1524650.0741520.0334040.0287350.0592690.0243390.0369680.0277600.0235220.0609320.0234450.0239310.0446430.0337730.058353
8063884050.0327640.0573010.0184370.0770990.0469630.0635210.0949510.0890720.0308260.0378960.0357520.0198780.0495360.0796100.0269220.0248330.0248400.0266950.0799360.0401460.043022
\n", + "
" + ], + "text/plain": [ + " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n", + "trip_id \n", + "137248721 0.056642 0.037305 0.042780 0.029576 \n", + "137248725 0.101902 0.022876 0.031227 0.069990 \n", + "211388201 0.037562 0.070033 0.055951 0.040389 \n", + "211388205 0.035480 0.056256 0.043944 0.048085 \n", + "806388401 0.054918 0.040488 0.033149 0.054294 \n", + "806388405 0.032764 0.057301 0.018437 0.077099 \n", + "\n", + " SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\n", + "trip_id \n", + "137248721 0.086154 0.026586 0.088313 0.100331 0.028021 0.018516 \n", + "137248725 0.056913 0.046685 0.088411 0.087785 0.025414 0.038162 \n", + "211388201 0.020041 0.095974 0.094678 0.094368 0.036014 0.025051 \n", + "211388205 0.042078 0.062221 0.106819 0.087265 0.035117 0.030399 \n", + "806388401 0.043477 0.067982 0.152465 0.074152 0.033404 0.028735 \n", + "806388405 0.046963 0.063521 0.094951 0.089072 0.030826 0.037896 \n", + "\n", + " WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\n", + "trip_id \n", + "137248721 0.064391 0.020009 0.026399 0.022622 0.023007 0.021754 \n", + "137248725 0.023988 0.028800 0.039318 0.020160 0.048364 0.023003 \n", + "211388201 0.034860 0.034953 0.035499 0.028040 0.043650 0.024713 \n", + "211388205 0.035154 0.026469 0.050646 0.035727 0.024284 0.032905 \n", + "806388401 0.059269 0.024339 0.036968 0.027760 0.023522 0.060932 \n", + "806388405 0.035752 0.019878 0.049536 0.079610 0.026922 0.024833 \n", + "\n", + " DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n", + "trip_id \n", + "137248721 0.087126 0.021171 0.036475 0.027650 0.135171 \n", + "137248725 0.033959 0.034597 0.037708 0.099523 0.041213 \n", + "211388201 0.031388 0.025931 0.066999 0.052678 0.051228 \n", + "211388205 0.039427 0.041092 0.054714 0.048459 0.063458 \n", + "806388401 0.023445 0.023931 0.044643 0.033773 0.058353 \n", + "806388405 0.024840 0.026695 0.079936 0.040146 0.043022 " + ] + }, + "execution_count": 767, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nested_exp_utilities_cf = simulate.compute_nested_exp_utilities(new_utils, nest_spec)\n", + "nested_probabilities_cf = simulate.compute_nested_probabilities(nested_exp_utilities_cf, nest_spec, trace_label=None)\n", + "base_probabilities_cf = simulate.compute_base_probabilities(nested_probabilities_cf, nest_spec, spec)\n", + "base_probabilities_cf" + ] + }, + { + "cell_type": "code", + "execution_count": 799, + "id": "50dfd1ae-10c3-475c-94be-de783c2fa5c2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:15:29.056902Z", + "iopub.status.busy": "2021-09-02T00:15:29.056683Z", + "iopub.status.idle": "2021-09-02T00:27:21.965206Z", + "shell.execute_reply": "2021-09-02T00:27:21.964340Z", + "shell.execute_reply.started": "2021-09-02T00:15:29.056877Z" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 11min 49s, sys: 766 ms, total: 11min 50s\n", + "Wall time: 11min 52s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "num_samples = 10000 # 7.5s per 100\n", + "\n", + "data_zenith = []\n", + "data_asim = []\n", + "for i in range(num_samples):\n", + " seed = rng_.integers(0, 100000)\n", + " x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True, raw_utilities=new_utils)\n", + " y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False, raw_utilities=new_utils)\n", + " data_zenith.append(x_)\n", + " data_asim.append(y_)\n", + " \n", + "data_asim = pd.concat(data_asim, axis=1)\n", + "data_zenith = pd.concat(data_zenith, axis=1)\n", + "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", + "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_asim = counts_asim / data_asim.shape[1]\n", + "\n", + "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", + "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", + "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", + "# assert np.allclose(mode_share_zenith.sum(), 1)\n", + "# assert np.allclose(mode_share_asim.sum(), 1)\n", + "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", + "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 796, + "id": "bb16f9d7-44f5-4316-b981-d65216a5e217", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:14:11.772592Z", + "iopub.status.busy": "2021-09-02T00:14:11.772347Z", + "iopub.status.idle": "2021-09-02T00:14:12.001077Z", + "shell.execute_reply": "2021-09-02T00:14:12.000166Z", + "shell.execute_reply.started": "2021-09-02T00:14:11.772556Z" + } + }, + "outputs": [], + "source": [ + "#temp_z = data_zenith.copy()\n", + "#temp_a = data_asim.copy()\n", + "#data_asim = data_asim.join(temp_a, lsuffix=\"_o\", rsuffix=\"_n\")\n", + "#data_zenith = data_zenith.join(temp_z, lsuffix=\"_o\", rsuffix=\"_n\")\n", + "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", + "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", + "# counts_asim = counts_asim / data_asim.shape[1]\n", + "\n", + "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", + "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", + "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", + "# assert np.allclose(mode_share_zenith.sum(), 1)\n", + "# assert np.allclose(mode_share_asim.sum(), 1)\n", + "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", + "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 805, + "id": "031d8340-0af0-4608-8a83-ed2af4aaa3b2", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:41:01.948669Z", + "iopub.status.busy": "2021-09-02T00:41:01.948422Z", + "iopub.status.idle": "2021-09-02T00:41:02.175972Z", + "shell.execute_reply": "2021-09-02T00:41:02.174759Z", + "shell.execute_reply.started": "2021-09-02T00:41:01.948633Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
zenith8.0555.1674.6313.5023.6064.2663.9633.4983.8955.1764.7995.8514.9996.1214.6689.4864.2284.1252.9773.6713.317
asim6.0966.6984.9773.2023.5224.3344.1293.1843.2245.1496.2838.2964.7936.5844.5327.4844.1524.2732.6293.4333.023
probs8.8835.3214.7382.8903.1364.0033.5653.1623.7585.3244.9276.0495.3416.5415.03710.4273.9734.2242.5743.1472.979
\n", + "
" + ], + "text/plain": [ + " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP DRIVE_HVY \\\n", + "zenith 8.055 5.167 4.631 3.502 3.606 4.266 \n", + "asim 6.096 6.698 4.977 3.202 3.522 4.334 \n", + "probs 8.883 5.321 4.738 2.890 3.136 4.003 \n", + "\n", + " DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY SHARED3FREE \\\n", + "zenith 3.963 3.498 3.895 5.176 4.799 \n", + "asim 4.129 3.184 3.224 5.149 6.283 \n", + "probs 3.565 3.162 3.758 5.324 4.927 \n", + "\n", + " SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK WALK_COM WALK_EXP \\\n", + "zenith 5.851 4.999 6.121 4.668 9.486 4.228 4.125 \n", + "asim 8.296 4.793 6.584 4.532 7.484 4.152 4.273 \n", + "probs 6.049 5.341 6.541 5.037 10.427 3.973 4.224 \n", + "\n", + " WALK_HVY WALK_LOC WALK_LRF \n", + "zenith 2.977 3.671 3.317 \n", + "asim 2.629 3.433 3.023 \n", + "probs 2.574 3.147 2.979 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# sample of 20000\n", + "with pd.option_context(\"precision\", 3):\n", + " display((100.0 * mode_share_comp).T)" + ] + }, + { + "cell_type": "code", + "execution_count": 806, + "id": "aece448f-4055-4d2a-b90d-8530caec9cb9", + "metadata": { + "execution": { + "iopub.execute_input": "2021-09-02T00:43:32.971972Z", + "iopub.status.busy": "2021-09-02T00:43:32.971684Z", + "iopub.status.idle": "2021-09-02T00:43:33.223345Z", + "shell.execute_reply": "2021-09-02T00:43:33.222488Z", + "shell.execute_reply.started": "2021-09-02T00:43:32.971932Z" + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
trip_id137248721_z137248721_a137248721
BIKE0.092700.054350.100331
DRIVEALONEFREE0.053650.050050.056642
DRIVEALONEPAY0.038800.031550.037305
DRIVE_COM0.028700.024700.021171
DRIVE_EXP0.028800.026050.021754
DRIVE_HVY0.078150.099700.087126
DRIVE_LOC0.028750.027000.022622
DRIVE_LRF0.027800.024450.023007
SHARED2FREE0.043250.023000.042780
SHARED2PAY0.033600.015850.029576
SHARED3FREE0.073650.119200.086154
SHARED3PAY0.032600.039600.026586
TAXI0.035100.050300.036475
TNC_SHARED0.126200.181150.135171
TNC_SINGLE0.026250.038450.027650
WALK0.079400.048250.088313
WALK_COM0.031850.024600.026399
WALK_EXP0.057050.058950.064391
WALK_HVY0.025200.018400.020009
WALK_LOC0.034550.027400.028021
WALK_LRF0.023950.017000.018516
\n", + "
" + ], + "text/plain": [ + "trip_id 137248721_z 137248721_a 137248721\n", + "BIKE 0.09270 0.05435 0.100331\n", + "DRIVEALONEFREE 0.05365 0.05005 0.056642\n", + "DRIVEALONEPAY 0.03880 0.03155 0.037305\n", + "DRIVE_COM 0.02870 0.02470 0.021171\n", + "DRIVE_EXP 0.02880 0.02605 0.021754\n", + "DRIVE_HVY 0.07815 0.09970 0.087126\n", + "DRIVE_LOC 0.02875 0.02700 0.022622\n", + "DRIVE_LRF 0.02780 0.02445 0.023007\n", + "SHARED2FREE 0.04325 0.02300 0.042780\n", + "SHARED2PAY 0.03360 0.01585 0.029576\n", + "SHARED3FREE 0.07365 0.11920 0.086154\n", + "SHARED3PAY 0.03260 0.03960 0.026586\n", + "TAXI 0.03510 0.05030 0.036475\n", + "TNC_SHARED 0.12620 0.18115 0.135171\n", + "TNC_SINGLE 0.02625 0.03845 0.027650\n", + "WALK 0.07940 0.04825 0.088313\n", + "WALK_COM 0.03185 0.02460 0.026399\n", + "WALK_EXP 0.05705 0.05895 0.064391\n", + "WALK_HVY 0.02520 0.01840 0.020009\n", + "WALK_LOC 0.03455 0.02740 0.028021\n", + "WALK_LRF 0.02395 0.01700 0.018516" + ] + }, + "execution_count": 806, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# TODO: look at probs per trip, i.e. do not sum across trips\n", + "trip_id = 137248721\n", + "\n", + "counts_zenith.loc[counts_zenith.index == trip_id].T.merge(\n", + " counts_asim.loc[counts_asim.index == trip_id].T, suffixes=['_z', '_a'], left_index=True, right_index=True, how='outer').merge(\n", + " base_probabilities_cf.loc[base_probabilities_cf.index == trip_id].T, suffixes=['', '_probs'], left_index=True, right_index=True, how='outer').fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ef84f10-d616-40e4-87e5-425499027ff3", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "431cee49-40a9-4dc7-93a5-75308fb302dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fcdc0ff-c510-4878-8672-8365e046442c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file From 83a86f5575f79922b9fafea6715fc0d342277846 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 4 May 2022 23:01:29 +1000 Subject: [PATCH 006/298] scale of leaf utility --- activitysim/core/logit.py | 5 +- activitysim/core/simulate.py | 9 +- notebooks/validate_frozen_impl.ipynb | 139 ++++++++++++++++++++++++--- 3 files changed, 133 insertions(+), 20 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index e3f87d4248..a2b732c52a 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -473,7 +473,10 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients nest.ancestors = parent_nest.ancestors + [name] nest.coefficient = parent_nest.coefficient - nest.parent_nest_scale = parent_nest.coefficient + + # this is used for frozen individual ru - see discussion in simulate.py l.740 + nest.parent_nest_scale = parent_nest.product_of_coefficients + #nest.parent_nest_scale = parent_nest.coefficient yield spec, nest diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 4f9b7d9a2a..27ed38e5fd 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -736,15 +736,18 @@ def compute_nested_utilities(raw_utilities, nest_spec): for nest in logit.each_nest(nest_spec, post_order=True): name = nest.name if nest.is_leaf: - nested_utilities[name] = \ - raw_utilities[name].astype(float) / nest.coefficient #nest.product_of_coefficients + nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients + #nested_utilities[name] = raw_utilities[name].astype(float) / nest.coefficient + # WHY does asim use product of coeffs here? the above gives us identical root logsums + # but then we need to dscale ev1 accordingly for alternatives, right? + # can be achieved by else: # the alternative nested_utilities will already have been computed due to post_order # this will RuntimeWarning: divide by zero encountered in log # if all nest alternative utilities are zero and produce -inf with np.errstate(divide='ignore'): nested_utilities[name] = \ - nest.coefficient * np.log((np.exp(nested_utilities[nest.alternatives])).sum(axis=1)) + nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) return nested_utilities diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 1fd45d784c..daeb7dab35 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -172,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 54, "outputs": [], "source": [ "def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=True):\n", @@ -339,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 55, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -349,10 +349,7 @@ "shell.execute_reply": "2021-09-01T02:29:01.222865Z", "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" }, - "tags": [], - "pycharm": { - "is_executing": true - } + "tags": [] }, "outputs": [ { @@ -371,19 +368,129 @@ "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", "trip_mode_choice tour_type 'escort' (6 trips)\n", - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n" + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", + "trip_mode_choice tour_type 'othmaint' (46 trips)\n", + "trip_mode_choice tour_type 'school' (37 trips)\n", + "trip_mode_choice tour_type 'shopping' (77 trips)\n", + "trip_mode_choice tour_type 'social' (19 trips)\n", + "trip_mode_choice tour_type 'univ' (26 trips)\n", + "trip_mode_choice tour_type 'work' (168 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'atwork' (27 trips)\n", + "trip_mode_choice tour_type 'eatout' (33 trips)\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n", + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", + "trip_mode_choice tour_type 'othmaint' (46 trips)\n", + "trip_mode_choice tour_type 'school' (37 trips)\n", + "trip_mode_choice tour_type 'shopping' (77 trips)\n", + "trip_mode_choice tour_type 'social' (19 trips)\n", + "trip_mode_choice tour_type 'univ' (26 trips)\n", + "trip_mode_choice tour_type 'work' (168 trips)\n", + "Done\n" ] } ], "source": [ - "trips_df_ru = run_trip_mode_choice()" + "trips_df_ru = run_trip_mode_choice()\n", + "trips_df = run_trip_mode_choice(choose_individual_max_utility=False)" ] }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 56, + "outputs": [ + { + "data": { + "text/plain": " trip_mode_fru trip_mode_asim\nWALK 299 304\nWALK_LOC 92 85\nWALK_LRF 61 53\nBIKE 17 17\nDRIVEALONEFREE 6 4\nSHARED2FREE 3 4\nTNC_SINGLE 2 4\nWALK_HVY 2 4", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK299304
WALK_LOC9285
WALK_LRF6153
BIKE1717
DRIVEALONEFREE64
SHARED2FREE34
TNC_SINGLE24
WALK_HVY24
\n
" + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "c_ = [\"trip_mode\", \"mode_choice_logsum\"]\n", + "trips = trips_df_ru[c_].merge(trips_df[c_], left_index=True, right_index=True, how=\"outer\", suffixes=[\"_fru\", \"_asim\"])\n", + "pd.merge(trips_df_ru.trip_mode.value_counts(), trips_df.trip_mode.value_counts(), left_index=True, right_index=True,\n", + " suffixes=[\"_fru\", \"_asim\"])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 57, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logsums agree\n" + ] + } + ], + "source": [ + "if np.allclose(trips.mode_choice_logsum_fru, trips.mode_choice_logsum_asim):\n", + " print(\"Logsums agree\")\n", + "else:\n", + " ((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru).hist(bins=50)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 58, + "outputs": [ + { + "data": { + "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n8684833 WALK -1.171760 WALK \n8684837 WALK -1.238719 WALK \n8685009 BIKE 6.198626 BIKE \n8685013 WALK 6.175681 BIKE \n8753057 WALK 4.457539 WALK \n... ... ... ... \n2472945113 WALK_LOC 12.537675 WALK_LOC \n2472945117 WALK 11.880804 WALK_LOC \n2472945118 WALK_LOC 13.710030 WALK \n2473024473 WALK_LOC -0.530696 WALK_LOC \n2473024477 WALK_LRF 0.624304 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n8684833 -1.171760 \n8684837 -1.238719 \n8685009 6.198626 \n8685013 6.175681 \n8753057 4.457539 \n... ... \n2472945113 12.537675 \n2472945117 11.880804 \n2472945118 13.710030 \n2473024473 -0.530696 \n2473024477 0.624304 \n\n[482 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
8684833WALK-1.171760WALK-1.171760
8684837WALK-1.238719WALK-1.238719
8685009BIKE6.198626BIKE6.198626
8685013WALK6.175681BIKE6.175681
8753057WALK4.457539WALK4.457539
...............
2472945113WALK_LOC12.537675WALK_LOC12.537675
2472945117WALK11.880804WALK_LOC11.880804
2472945118WALK_LOC13.710030WALK13.710030
2473024473WALK_LOC-0.530696WALK_LOC-0.530696
2473024477WALK_LRF0.624304WALK_LRF0.624304
\n

482 rows × 4 columns

\n
" + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trips" + ], "metadata": { "collapsed": false, "pycharm": { @@ -452,14 +559,14 @@ } }, { - "cell_type": "markdown", - "source": [ - "# OLD\n" - ], + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], "metadata": { "collapsed": false, "pycharm": { - "name": "#%% md\n" + "name": "#%%\n" } } }, From 5a7ad3c76604846057a897ff78c3b7983738776d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 4 May 2022 23:41:58 +1000 Subject: [PATCH 007/298] pull out cruft --- notebooks/fru_utils.py | 416 +++++++++++++++++++++ notebooks/validate_frozen_impl.ipynb | 524 +++++---------------------- 2 files changed, 509 insertions(+), 431 deletions(-) create mode 100644 notebooks/fru_utils.py diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py new file mode 100644 index 0000000000..4ead449bb9 --- /dev/null +++ b/notebooks/fru_utils.py @@ -0,0 +1,416 @@ +import os +import argparse +from datetime import datetime + +import numpy as np +import pandas as pd + +from activitysim.cli import run +from activitysim.core import inject +from activitysim.core import tracing +from activitysim.core import config +from activitysim.core import pipeline +from activitysim.core import chunk +from activitysim.core import simulate +from activitysim.core import logit +from activitysim.abm.models.util import estimation +from activitysim.core import expressions +from activitysim.core.util import assign_in_place + + +def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=True): + + """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" + resume_after = "trip_scheduling" + model_name = "trip_mode_choice" + chunk_size = 0 # test_mtc means no chunking + + pipeline.open_pipeline(resume_after) + # preload any bulky injectables (e.g. skims) not in pipeline + inject.get_injectable('preload_injectables', None) + pipeline._PIPELINE.rng().begin_step(model_name) + step_name = model_name + args = {} + checkpoint = pipeline.intermediate_checkpoint(model_name) + inject.set_step_args(args) + + trips = inject.get_table('trips') + tours_merged = inject.get_table('tours_merged') + network_los = inject.get_injectable('network_los') + + trace_label = 'trip_mode_choice' + model_settings_file_name = 'trip_mode_choice.yaml' + model_settings = config.read_model_settings(model_settings_file_name) + + logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME') + mode_column_name = 'trip_mode' + + trips_df = trips.to_frame() + print("Running with %d trips", trips_df.shape[0]) + + tours_merged = tours_merged.to_frame() + tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] + + # - trips_merged - merge trips and tours_merged + trips_merged = pd.merge( + trips_df, + tours_merged, + left_on='tour_id', + right_index=True, + how="left") + assert trips_merged.index.equals(trips.index) + + # setup skim keys + assert ('trip_period' not in trips_merged) + trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) + + orig_col = 'origin' + dest_col = 'destination' + + constants = {} + constants.update(config.get_model_constants(model_settings)) + constants.update({ + 'ORIGIN': orig_col, + 'DESTINATION': dest_col + }) + + skim_dict = network_los.get_default_skim_dict() + + odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col, + dim3_key='trip_period') + dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col, + dim3_key='trip_period') + od_skim_wrapper = skim_dict.wrap('origin', 'destination') + + skims = { + "odt_skims": odt_skim_stack_wrapper, + "dot_skims": dot_skim_stack_wrapper, + "od_skims": od_skim_wrapper, + } + + model_spec = simulate.read_model_spec(file_name=model_settings['SPEC']) + nest_specs = config.get_logit_model_settings(model_settings) + + estimator = estimation.manager.begin_estimation('trip_mode_choice') + + choices_list = [] + for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'): + + if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes): + continue + + print("trip_mode_choice tour_type '%s' (%s trips)" % + (primary_purpose, len(trips_segment.index), )) + + # name index so tracing knows how to slice + assert trips_segment.index.name == 'trip_id' + + coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose) + + locals_dict = {} + locals_dict.update(constants) + locals_dict.update(coefficients) + + segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose) + + expressions.annotate_preprocessors( + trips_segment, locals_dict, skims, + model_settings, segment_trace_label) + + locals_dict.update(skims) + + ################ Replace wrapper function + # choices = mode_choice_simulate(...) + spec=simulate.eval_coefficients(model_spec, coefficients, estimator) + nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label) + choices = simulate.simple_simulate( + choosers=trips_segment, + spec=spec, + nest_spec=nest_spec, + skims=skims, + locals_d=locals_dict, + chunk_size=chunk_size, + want_logsums=logsum_column_name is not None, + trace_label=segment_trace_label, + trace_choice_name='trip_mode_choice', + estimator=estimator, + trace_column_names=None, + choose_individual_max_utility=choose_individual_max_utility) + # for consistency, always return dataframe, whether or not logsums were requested + if isinstance(choices, pd.Series): + choices = choices.to_frame('choice') + choices.rename(columns={'logsum': logsum_column_name, + 'choice': mode_column_name}, + inplace=True) + if not choose_individual_max_utility: + alts = spec.columns + choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) + ################ + choices_list.append(choices) + choices_df_asim = pd.concat(choices_list) + + # update trips table with choices (and potionally logssums) + trips_df = trips.to_frame() + + if (do_these_purposes is not None): + trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)] + + assign_in_place(trips_df, choices_df_asim) + assert not trips_df[mode_column_name].isnull().any() + + finalise = True + if finalise: + inject.set_step_args(None) + # + pipeline._PIPELINE.rng().end_step(model_name) + pipeline.add_checkpoint(model_name) + if not pipeline.intermediate_checkpoint(): + pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME) + + pipeline.close_pipeline() + + print("Done") + + return trips_df + + + + +def eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, + log_alt_losers=False, + want_logsums=False, trace_label=None, + trace_choice_name=None, trace_column_names=None): + + trace_label = tracing.extend_trace_label(trace_label, 'eval_nl') + assert trace_label + have_trace_targets = tracing.has_trace_targets(choosers) + + logit.validate_nest_spec(nest_spec, trace_label) + raw_utilities = simulate.eval_utilities(spec, choosers, locals_d, + log_alt_losers=log_alt_losers, + trace_label=trace_label, have_trace_targets=have_trace_targets, + estimator=estimator, trace_column_names=trace_column_names) + # exponentiated utilities of leaves and nests + nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec) + nested_utils = simulate.compute_nested_utilities(raw_utilities, nest_spec) + # probabilities of alternatives relative to siblings sharing the same nest + nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, + trace_label=trace_label) + if want_logsums: + # logsum of nest root + logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) + # global (flattened) leaf probabilities based on relative nest coefficients (in spec order) + base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) + # note base_probabilities could all be zero since we allowed all probs for nests to be zero + # check here to print a clear message but make_choices will raise error if probs don't sum to 1 + BAD_PROB_THRESHOLD = 0.001 + no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD + if no_choices.any(): + print("BAD") + choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label) + if want_logsums: + choices = choices.to_frame('choice') + choices['logsum'] = logsums + return choices, raw_utilities, nested_exp_utilities, nested_utils, nested_probabilities, base_probabilities + + +def simple_simulate_dev(choosers, spec, nest_spec, + skims=None, locals_d=None, + chunk_size=0, custom_chooser=None, + log_alt_losers=False, + want_logsums=False, + estimator=None, + trace_label=None, trace_choice_name=None, trace_column_names=None): + trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate') + assert len(choosers) > 0 + result_list = [] + # segment by person type and pick the right spec for each person type + for i, chooser_chunk, chunk_trace_label in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label): + # the following replaces choices = _simple_simulate(...) + if skims is not None: + simulate.set_skim_wrapper_targets(choosers, skims) + + # only do this for nested, logit is straight forward + assert nest_spec is not None + choices, raw_utilities, nested_exp_utilities, nested_utils, \ + nested_probs, base_probs = eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, + log_alt_losers=log_alt_losers, + want_logsums=want_logsums, estimator=estimator, trace_label=trace_label, + trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) + + result_list.append(choices) + chunk.log_df(trace_label, f'result_list', result_list) + + if len(result_list) > 1: + choices = pd.concat(result_list) + assert len(choices.index == len(choosers.index)) + return choices, raw_utilities, nested_exp_utilities, nested_utils, nested_probs, base_probs + + +def get_stuff(do_these_purposes=None): + #do_these_purposes=['escort'] + """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" + resume_after = "trip_scheduling" + model_name = "trip_mode_choice" + chunk_size = 0 # test_mtc means no chunking + + pipeline.open_pipeline(resume_after) + # preload any bulky injectables (e.g. skims) not in pipeline + inject.get_injectable('preload_injectables', None) + pipeline._PIPELINE.rng().begin_step(model_name) + step_name = model_name + args = {} + checkpoint = pipeline.intermediate_checkpoint(model_name) + inject.set_step_args(args) + + trips = inject.get_table('trips') + tours_merged = inject.get_table('tours_merged') + network_los = inject.get_injectable('network_los') + + trace_label = 'trip_mode_choice' + model_settings_file_name = 'trip_mode_choice.yaml' + model_settings = config.read_model_settings(model_settings_file_name) + + logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME') + mode_column_name = 'trip_mode' + + trips_df = trips.to_frame() + print("Running with %d trips", trips_df.shape[0]) + + tours_merged = tours_merged.to_frame() + tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] + + # - trips_merged - merge trips and tours_merged + trips_merged = pd.merge( + trips_df, + tours_merged, + left_on='tour_id', + right_index=True, + how="left") + assert trips_merged.index.equals(trips.index) + + # setup skim keys + assert ('trip_period' not in trips_merged) + trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) + + orig_col = 'origin' + dest_col = 'destination' + + constants = {} + constants.update(config.get_model_constants(model_settings)) + constants.update({ + 'ORIGIN': orig_col, + 'DESTINATION': dest_col + }) + + skim_dict = network_los.get_default_skim_dict() + + odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col, + dim3_key='trip_period') + dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col, + dim3_key='trip_period') + od_skim_wrapper = skim_dict.wrap('origin', 'destination') + + skims = { + "odt_skims": odt_skim_stack_wrapper, + "dot_skims": dot_skim_stack_wrapper, + "od_skims": od_skim_wrapper, + } + + model_spec = simulate.read_model_spec(file_name=model_settings['SPEC']) + nest_specs = config.get_logit_model_settings(model_settings) + + estimator = estimation.manager.begin_estimation('trip_mode_choice') + + choices_list = [] + raw_util_list = [] + nest_list = [] + nu_list = [] + nest_spec_list = [] + nested_probs_list = [] + base_probs_list = [] + + for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'): + + if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes): + continue + + print("trip_mode_choice tour_type '%s' (%s trips)" % + (primary_purpose, len(trips_segment.index), )) + + # name index so tracing knows how to slice + assert trips_segment.index.name == 'trip_id' + + coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose) + + locals_dict = {} + locals_dict.update(constants) + locals_dict.update(coefficients) + + segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose) + + expressions.annotate_preprocessors( + trips_segment, locals_dict, skims, + model_settings, segment_trace_label) + + locals_dict.update(skims) + + ################ Replace wrapper function + # choices = mode_choice_simulate(...) + spec=simulate.eval_coefficients(model_spec, coefficients, estimator) + nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label) + choices, raw_utilities, nested_exp_utilities, nested_utils, nested_probs, base_probs = simple_simulate_dev( + choosers=trips_segment, + spec=spec, + nest_spec=nest_spec, + skims=skims, + locals_d=locals_dict, + chunk_size=chunk_size, + want_logsums=logsum_column_name is not None, + trace_label=segment_trace_label, + trace_choice_name='trip_mode_choice', + estimator=estimator, + trace_column_names=None) + # for consistency, always return dataframe, whether or not logsums were requested + if isinstance(choices, pd.Series): + choices = choices.to_frame('choice') + choices.rename(columns={'logsum': logsum_column_name, + 'choice': mode_column_name}, + inplace=True) + alts = spec.columns + choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) + ################ + choices_list.append(choices) + raw_util_list.append(raw_utilities) + nest_list.append(nested_exp_utilities) + nu_list.append(nested_utils) + nest_spec_list.append(nest_spec) + nested_probs_list.append(nested_probs) + base_probs_list.append(base_probs) + + + choices_df_asim = pd.concat(choices_list) + + # update trips table with choices (and potionally logssums) + trips_df = trips.to_frame() + + if (do_these_purposes is not None): + trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)] + + assign_in_place(trips_df, choices_df_asim) + assert not trips_df[mode_column_name].isnull().any() + + finalise = True + if finalise: + inject.set_step_args(None) + # + pipeline._PIPELINE.rng().end_step(model_name) + pipeline.add_checkpoint(model_name) + if not pipeline.intermediate_checkpoint(): + pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME) + + pipeline.close_pipeline() + + print("Done") + + return trips_df, raw_util_list, nest_list, nu_list, nest_spec_list, nested_probs_list, base_probs_list \ No newline at end of file diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index daeb7dab35..c4262ba02b 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -85,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -104,7 +104,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -124,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -143,7 +143,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -163,8 +163,6 @@ "# parser.parse_args(['--sum', '7', '-1', '42'])\n", "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", "#run.run(args) # 2mins full example run\n", - "\n", - "\n", "if not inject.is_injectable('preload_injectables'):\n", " from activitysim import abm # register abm steps and other abm-specific injectables\n", "run.handle_standard_args(args) # possibly update injectables" @@ -172,174 +170,7 @@ }, { "cell_type": "code", - "execution_count": 54, - "outputs": [], - "source": [ - "def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=True):\n", - "\n", - " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", - " resume_after = \"trip_scheduling\"\n", - " model_name = \"trip_mode_choice\"\n", - " chunk_size = 0 # test_mtc means no chunking\n", - "\n", - " pipeline.open_pipeline(resume_after)\n", - " # preload any bulky injectables (e.g. skims) not in pipeline\n", - " inject.get_injectable('preload_injectables', None)\n", - " pipeline._PIPELINE.rng().begin_step(model_name)\n", - " step_name = model_name\n", - " args = {}\n", - " checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - " inject.set_step_args(args)\n", - "\n", - " trips = inject.get_table('trips')\n", - " tours_merged = inject.get_table('tours_merged')\n", - " network_los = inject.get_injectable('network_los')\n", - "\n", - " trace_label = 'trip_mode_choice'\n", - " model_settings_file_name = 'trip_mode_choice.yaml'\n", - " model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - " mode_column_name = 'trip_mode'\n", - "\n", - " trips_df = trips.to_frame()\n", - " print(\"Running with %d trips\", trips_df.shape[0])\n", - "\n", - " tours_merged = tours_merged.to_frame()\n", - " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "\n", - " # - trips_merged - merge trips and tours_merged\n", - " trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - " assert trips_merged.index.equals(trips.index)\n", - "\n", - " # setup skim keys\n", - " assert ('trip_period' not in trips_merged)\n", - " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - " orig_col = 'origin'\n", - " dest_col = 'destination'\n", - "\n", - " constants = {}\n", - " constants.update(config.get_model_constants(model_settings))\n", - " constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - " })\n", - "\n", - " skim_dict = network_los.get_default_skim_dict()\n", - "\n", - " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "\n", - " skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - " }\n", - "\n", - " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - " nest_specs = config.get_logit_model_settings(model_settings)\n", - "\n", - " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - "\n", - " if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", - " continue\n", - "\n", - " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " (primary_purpose, len(trips_segment.index), ))\n", - "\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " ################ Replace wrapper function\n", - " # choices = mode_choice_simulate(...)\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", - " choices = simulate.simple_simulate(\n", - " choosers=trips_segment,\n", - " spec=spec,\n", - " nest_spec=nest_spec,\n", - " skims=skims,\n", - " locals_d=locals_dict,\n", - " chunk_size=chunk_size,\n", - " want_logsums=logsum_column_name is not None,\n", - " trace_label=segment_trace_label,\n", - " trace_choice_name='trip_mode_choice',\n", - " estimator=estimator,\n", - " trace_column_names=None,\n", - " choose_individual_max_utility=choose_individual_max_utility)\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " if not choose_individual_max_utility:\n", - " alts = spec.columns\n", - " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", - " ################\n", - " choices_list.append(choices)\n", - " choices_df_asim = pd.concat(choices_list)\n", - "\n", - " # update trips table with choices (and potionally logssums)\n", - " trips_df = trips.to_frame()\n", - "\n", - " if (do_these_purposes is not None):\n", - " trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", - "\n", - " assign_in_place(trips_df, choices_df_asim)\n", - " assert not trips_df[mode_column_name].isnull().any()\n", - "\n", - " finalise = True\n", - " if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - " print(\"Done\")\n", - "\n", - " return trips_df" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 55, + "execution_count": 8, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -374,20 +205,7 @@ "trip_mode_choice tour_type 'shopping' (77 trips)\n", "trip_mode_choice tour_type 'social' (19 trips)\n", "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n" ] }, @@ -395,6 +213,7 @@ "name": "stderr", "output_type": "stream", "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -418,20 +237,21 @@ } ], "source": [ + "from fru_utils import run_trip_mode_choice\n", "trips_df_ru = run_trip_mode_choice()\n", "trips_df = run_trip_mode_choice(choose_individual_max_utility=False)" ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 9, "outputs": [ { "data": { "text/plain": " trip_mode_fru trip_mode_asim\nWALK 299 304\nWALK_LOC 92 85\nWALK_LRF 61 53\nBIKE 17 17\nDRIVEALONEFREE 6 4\nSHARED2FREE 3 4\nTNC_SINGLE 2 4\nWALK_HVY 2 4", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK299304
WALK_LOC9285
WALK_LRF6153
BIKE1717
DRIVEALONEFREE64
SHARED2FREE34
TNC_SINGLE24
WALK_HVY24
\n
" }, - "execution_count": 56, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -451,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 62, "outputs": [ { "name": "stdout", @@ -475,26 +295,19 @@ } }, { - "cell_type": "code", - "execution_count": 58, - "outputs": [ - { - "data": { - "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n8684833 WALK -1.171760 WALK \n8684837 WALK -1.238719 WALK \n8685009 BIKE 6.198626 BIKE \n8685013 WALK 6.175681 BIKE \n8753057 WALK 4.457539 WALK \n... ... ... ... \n2472945113 WALK_LOC 12.537675 WALK_LOC \n2472945117 WALK 11.880804 WALK_LOC \n2472945118 WALK_LOC 13.710030 WALK \n2473024473 WALK_LOC -0.530696 WALK_LOC \n2473024477 WALK_LRF 0.624304 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n8684833 -1.171760 \n8684837 -1.238719 \n8685009 6.198626 \n8685013 6.175681 \n8753057 4.457539 \n... ... \n2472945113 12.537675 \n2472945117 11.880804 \n2472945118 13.710030 \n2473024473 -0.530696 \n2473024477 0.624304 \n\n[482 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
8684833WALK-1.171760WALK-1.171760
8684837WALK-1.238719WALK-1.238719
8685009BIKE6.198626BIKE6.198626
8685013WALK6.175681BIKE6.175681
8753057WALK4.457539WALK4.457539
...............
2472945113WALK_LOC12.537675WALK_LOC12.537675
2472945117WALK11.880804WALK_LOC11.880804
2472945118WALK_LOC13.710030WALK13.710030
2473024473WALK_LOC-0.530696WALK_LOC-0.530696
2473024477WALK_LRF0.624304WALK_LRF0.624304
\n

482 rows × 4 columns

\n
" - }, - "execution_count": 58, - "metadata": {}, - "output_type": "execute_result" - } - ], + "cell_type": "markdown", "source": [ - "trips" + "# Run MC validation\n", + "\n", + "see logit._each_nest parent_nest_scale for leaf node, and simulate.compute_nested_utilities for discussion of scales\n", + "of alternatives\n", + "\n", + "easiest way is to run a simulation" ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } } }, @@ -512,9 +325,30 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 14, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'social' (19 trips)\n", + "Done\n" + ] + } + ], + "source": [ + "from fru_utils import get_stuff\n", + "t, ru, neu, nu, ns, nested_probs, base_probs = get_stuff(do_these_purposes=[\"social\"])" + ], "metadata": { "collapsed": false, "pycharm": { @@ -524,9 +358,21 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 15, + "outputs": [ + { + "data": { + "text/plain": " AUTO NONMOTORIZED TRANSIT RIDEHAIL DRIVEALONE \\\ntrip_id \n8805121 0.000000e+00 1.000000 0.000000 1.418229e-07 0.0 \n8805125 0.000000e+00 1.000000 0.000000 1.333383e-07 0.0 \n8805126 0.000000e+00 1.000000 0.000000 1.666869e-07 0.0 \n468119921 0.000000e+00 1.000000 0.000000 5.175690e-08 0.0 \n468119922 0.000000e+00 0.999874 0.000000 1.255768e-04 0.0 \n468119925 0.000000e+00 0.999875 0.000000 1.254853e-04 0.0 \n468119926 0.000000e+00 1.000000 0.000000 5.207466e-08 0.0 \n468119927 0.000000e+00 1.000000 0.000000 7.363066e-08 0.0 \n468119928 0.000000e+00 1.000000 0.000000 2.191280e-08 0.0 \n642446345 4.882838e-03 0.044905 0.949699 5.136180e-04 0.0 \n642446349 3.622165e-03 0.043359 0.952506 5.131357e-04 0.0 \n1179608009 7.537428e-05 0.969538 0.000000 3.038711e-02 0.0 \n1179608013 1.855034e-05 0.969598 0.000000 3.038345e-02 0.0 \n1658807153 5.751672e-07 0.048044 0.951445 5.107302e-04 0.0 \n1658807157 6.945596e-07 0.065847 0.933430 7.226819e-04 0.0 \n1767182945 1.610090e-02 0.046835 0.931353 5.711349e-03 0.0 \n1767182949 1.540381e-02 0.041767 0.937130 5.699151e-03 0.0 \n2464104881 2.228876e-05 0.646947 0.353030 4.902454e-07 0.0 \n2464104885 2.163505e-05 0.627973 0.372005 4.758670e-07 0.0 \n\n SHAREDRIDE2 SHAREDRIDE3 DRIVEALONEFREE DRIVEALONEPAY \\\ntrip_id \n8805121 0.000000 0.000000e+00 0.0 0.0 \n8805125 0.000000 0.000000e+00 0.0 0.0 \n8805126 0.000000 0.000000e+00 0.0 0.0 \n468119921 0.000000 0.000000e+00 0.0 0.0 \n468119922 0.000000 0.000000e+00 0.0 0.0 \n468119925 0.000000 0.000000e+00 0.0 0.0 \n468119926 0.000000 0.000000e+00 0.0 0.0 \n468119927 0.000000 0.000000e+00 0.0 0.0 \n468119928 0.000000 0.000000e+00 0.0 0.0 \n642446345 1.000000 9.628424e-12 0.0 0.0 \n642446349 1.000000 1.090669e-11 0.0 0.0 \n1179608009 0.739343 2.606568e-01 0.0 0.0 \n1179608013 0.597359 4.026407e-01 0.0 0.0 \n1658807153 0.739335 2.606646e-01 0.0 0.0 \n1658807157 0.725355 2.746454e-01 0.0 0.0 \n1767182945 1.000000 9.618359e-12 0.0 0.0 \n1767182949 1.000000 9.798594e-12 0.0 0.0 \n2464104881 1.000000 9.634468e-12 0.0 0.0 \n2464104885 1.000000 9.634468e-12 0.0 0.0 \n\n SHARED2FREE SHARED2PAY SHARED3FREE SHARED3PAY WALK BIKE \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 1.0 0.0 \n8805125 0.0 0.0 0.0 0.0 1.0 0.0 \n8805126 0.0 0.0 0.0 0.0 1.0 0.0 \n468119921 0.0 0.0 0.0 0.0 1.0 0.0 \n468119922 0.0 0.0 0.0 0.0 1.0 0.0 \n468119925 0.0 0.0 0.0 0.0 1.0 0.0 \n468119926 0.0 0.0 0.0 0.0 1.0 0.0 \n468119927 0.0 0.0 0.0 0.0 1.0 0.0 \n468119928 0.0 0.0 0.0 0.0 1.0 0.0 \n642446345 1.0 0.0 1.0 0.0 1.0 0.0 \n642446349 1.0 0.0 1.0 0.0 1.0 0.0 \n1179608009 1.0 0.0 1.0 0.0 1.0 0.0 \n1179608013 1.0 0.0 1.0 0.0 1.0 0.0 \n1658807153 1.0 0.0 1.0 0.0 1.0 0.0 \n1658807157 1.0 0.0 1.0 0.0 1.0 0.0 \n1767182945 1.0 0.0 1.0 0.0 1.0 0.0 \n1767182949 1.0 0.0 1.0 0.0 1.0 0.0 \n2464104881 1.0 0.0 1.0 0.0 1.0 0.0 \n2464104885 1.0 0.0 1.0 0.0 1.0 0.0 \n\n WALKACCESS DRIVEACCESS WALK_LOC WALK_LRF WALK_EXP WALK_HVY \\\ntrip_id \n8805121 0.0 0.0 0.000000 0.000000 0.0 0.0 \n8805125 0.0 0.0 0.000000 0.000000 0.0 0.0 \n8805126 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119921 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119922 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119925 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119926 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119927 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119928 0.0 0.0 0.000000 0.000000 0.0 0.0 \n642446345 1.0 0.0 0.063138 0.936862 0.0 0.0 \n642446349 1.0 0.0 0.071770 0.928230 0.0 0.0 \n1179608009 0.0 0.0 0.000000 0.000000 0.0 0.0 \n1179608013 0.0 0.0 0.000000 0.000000 0.0 0.0 \n1658807153 1.0 0.0 1.000000 0.000000 0.0 0.0 \n1658807157 1.0 0.0 1.000000 0.000000 0.0 0.0 \n1767182945 1.0 0.0 0.154889 0.845111 0.0 0.0 \n1767182949 1.0 0.0 0.150707 0.849293 0.0 0.0 \n2464104881 1.0 0.0 1.000000 0.000000 0.0 0.0 \n2464104885 1.0 0.0 1.000000 0.000000 0.0 0.0 \n\n WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.300854 0.553048 0.146098 \n8805125 0.304288 0.549407 0.146304 \n8805126 0.291815 0.562518 0.145667 \n468119921 0.211883 0.005323 0.782795 \n468119922 0.211720 0.005255 0.783024 \n468119925 0.211271 0.005261 0.783468 \n468119926 0.301114 0.004624 0.694262 \n468119927 0.720964 0.001787 0.277249 \n468119928 0.108040 0.006816 0.885144 \n642446345 0.187119 0.254576 0.558305 \n642446349 0.181108 0.256588 0.562305 \n1179608009 0.000172 0.910435 0.089393 \n1179608013 0.000172 0.910454 0.089374 \n1658807153 0.000139 0.919273 0.080588 \n1658807157 0.000132 0.919585 0.080283 \n1767182945 0.262451 0.588087 0.149462 \n1767182949 0.259083 0.592884 0.148034 \n2464104881 0.278497 0.000018 0.721485 \n2464104885 0.278497 0.000018 0.721485 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
AUTONONMOTORIZEDTRANSITRIDEHAILDRIVEALONESHAREDRIDE2SHAREDRIDE3DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALKACCESSDRIVEACCESSWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.000000e+001.0000000.0000001.418229e-070.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.3008540.5530480.146098
88051250.000000e+001.0000000.0000001.333383e-070.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.3042880.5494070.146304
88051260.000000e+001.0000000.0000001.666869e-070.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2918150.5625180.145667
4681199210.000000e+001.0000000.0000005.175690e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2118830.0053230.782795
4681199220.000000e+000.9998740.0000001.255768e-040.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2117200.0052550.783024
4681199250.000000e+000.9998750.0000001.254853e-040.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2112710.0052610.783468
4681199260.000000e+001.0000000.0000005.207466e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.3011140.0046240.694262
4681199270.000000e+001.0000000.0000007.363066e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.7209640.0017870.277249
4681199280.000000e+001.0000000.0000002.191280e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.1080400.0068160.885144
6424463454.882838e-030.0449050.9496995.136180e-040.01.0000009.628424e-120.00.01.00.01.00.01.00.01.00.00.0631380.9368620.00.00.00.00.00.00.00.00.1871190.2545760.558305
6424463493.622165e-030.0433590.9525065.131357e-040.01.0000001.090669e-110.00.01.00.01.00.01.00.01.00.00.0717700.9282300.00.00.00.00.00.00.00.00.1811080.2565880.562305
11796080097.537428e-050.9695380.0000003.038711e-020.00.7393432.606568e-010.00.01.00.01.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.0001720.9104350.089393
11796080131.855034e-050.9695980.0000003.038345e-020.00.5973594.026407e-010.00.01.00.01.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.0001720.9104540.089374
16588071535.751672e-070.0480440.9514455.107302e-040.00.7393352.606646e-010.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.0001390.9192730.080588
16588071576.945596e-070.0658470.9334307.226819e-040.00.7253552.746454e-010.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.0001320.9195850.080283
17671829451.610090e-020.0468350.9313535.711349e-030.01.0000009.618359e-120.00.01.00.01.00.01.00.01.00.00.1548890.8451110.00.00.00.00.00.00.00.00.2624510.5880870.149462
17671829491.540381e-020.0417670.9371305.699151e-030.01.0000009.798594e-120.00.01.00.01.00.01.00.01.00.00.1507070.8492930.00.00.00.00.00.00.00.00.2590830.5928840.148034
24641048812.228876e-050.6469470.3530304.902454e-070.01.0000009.634468e-120.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.2784970.0000180.721485
24641048852.163505e-050.6279730.3720054.758670e-070.01.0000009.634468e-120.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.2784970.0000180.721485
\n
" + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nested_probs[0]" + ], "metadata": { "collapsed": false, "pycharm": { @@ -536,9 +382,21 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 16, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "base_probs[0]" + ], "metadata": { "collapsed": false, "pycharm": { @@ -550,7 +408,10 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [], + "source": [ + "# for later: this seems to be a non-driving segment - grab workers or something later?\n", + "# for now: implement a run with seeding so I can reproduce these counts" + ], "metadata": { "collapsed": false, "pycharm": { @@ -558,6 +419,13 @@ } } }, + { + "cell_type": "markdown", + "source": [], + "metadata": { + "collapsed": false + } + }, { "cell_type": "code", "execution_count": null, @@ -582,73 +450,11 @@ } } }, - { - "cell_type": "markdown", - "id": "2f080150-c15a-4059-8a26-42a8c0072606", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T23:16:40.014024Z", - "iopub.status.busy": "2021-08-31T23:16:40.013770Z", - "iopub.status.idle": "2021-08-31T23:16:40.236326Z", - "shell.execute_reply": "2021-08-31T23:16:40.235553Z", - "shell.execute_reply.started": "2021-08-31T23:16:40.013973Z" - }, - "tags": [] - }, - "source": [ - "### make choice at each level" - ] - }, - { - "cell_type": "code", - "execution_count": 315, - "id": "2ed71b54-67e4-4087-957a-75f4fa184144", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:43.778081Z", - "iopub.status.busy": "2021-09-01T01:11:43.777757Z", - "iopub.status.idle": "2021-09-01T01:11:43.975332Z", - "shell.execute_reply": "2021-09-01T01:11:43.974588Z", - "shell.execute_reply.started": "2021-09-01T01:11:43.778055Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def get_alternatives(nests, name):\n", - " alts = list(filter(lambda x: x.name == name, nests))\n", - " assert len(alts) == 1, f\"{len(alts)} not one\"\n", - " alts = alts[0].alternatives\n", - " return alts\n", - "\n", - "def recursive_choice(row, columns, nest_levels, nests):\n", - " choices = row[columns].idxmax() #axis=1).values[0]\n", - " next_level_columns = get_alternatives(nests, choices)\n", - " #print(f\"{choices} leads to columns {next_level_columns}\")\n", - " if next_level_columns is None:\n", - " return choices \n", - " new_choice = recursive_choice(row, next_level_columns, nest_levels, nests)\n", - " return new_choice\n", - "\n", - "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", - "\n", - "def make_choice(utils_df, nests, nest_levels, seed=None):\n", - " rng = default_rng(seed=seed)\n", - " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", - " probs_arr = utils_df - np.log(-np.log(rands))\n", - " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", - " return choices" - ] - }, { "cell_type": "code", "execution_count": null, "outputs": [], - "source": [ - "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", - "seed = 9326543345\n", - "make_choice(utils_df, nests_, nest_levels, seed)" - ], + "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -658,37 +464,15 @@ }, { "cell_type": "code", - "execution_count": 316, - "id": "30424562-2627-446b-abd5-7c763c52060e", + "execution_count": null, + "outputs": [], + "source": [], "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:49.444289Z", - "iopub.status.busy": "2021-09-01T01:11:49.444002Z", - "iopub.status.idle": "2021-09-01T01:11:49.664214Z", - "shell.execute_reply": "2021-09-01T01:11:49.663088Z", - "shell.execute_reply.started": "2021-09-01T01:11:49.444266Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "4\n" - ] + "collapsed": false, + "pycharm": { + "name": "#%%\n" } - ], - "source": [ - "# group nests by level:\n", - "depth = np.max([x.level for x in nests_])\n", - "print(depth)\n", - "nest_levels = {x: [] for x in range(1, depth+1)}\n", - "for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - "assert len(nest_levels[1]) == 1\n", - "assert nest_levels[1][0] == 'root'" - ] + } }, { "cell_type": "code", @@ -722,128 +506,6 @@ "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)" ] }, - { - "cell_type": "code", - "execution_count": 451, - "id": "054fd9b1-72fc-49cc-a790-48ef75bcbaed", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:55:08.260140Z", - "iopub.status.busy": "2021-09-01T04:55:08.259914Z", - "iopub.status.idle": "2021-09-01T04:55:09.140095Z", - "shell.execute_reply": "2021-09-01T04:55:09.139119Z", - "shell.execute_reply.started": "2021-09-01T04:55:08.260116Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "86627409 WALK_LRF\n", - "86627413 WALK_LRF\n", - "86673657 WALK_LOC\n", - "86673658 WALK\n", - "86673659 WALK\n", - " ... \n", - "2464446025 WALK\n", - "2464446029 WALK\n", - "2464449633 WALK\n", - "2464449634 WALK\n", - "2464449637 WALK\n", - "Length: 168, dtype: object" - ] - }, - "execution_count": 451, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def simple_simulate_rum(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", - "\n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices\n", - "\n", - "simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" - ] - }, - { - "cell_type": "code", - "execution_count": 393, - "id": "f3c5c1ba-e9c3-4511-98bf-9ac98c987da7", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:39:52.539983Z", - "iopub.status.busy": "2021-09-01T02:39:52.539668Z", - "iopub.status.idle": "2021-09-01T02:39:52.796170Z", - "shell.execute_reply": "2021-09-01T02:39:52.795498Z", - "shell.execute_reply.started": "2021-09-01T02:39:52.539944Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " segment_trace_label, log_alt_losers=log_alt_losers, \n", - " trace_column_names=trace_column_names, seed=seed)\n", - "\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " choices_list.append(choices)\n", - "\n", - " choices_df = pd.concat(choices_list)\n", - " return choices_df" - ] - }, { "cell_type": "code", "execution_count": 454, From 5e8bf1648342458fcb903c9f0192cec1855543f5 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 5 May 2022 12:56:34 +1000 Subject: [PATCH 008/298] towards validation of probs for single trip --- notebooks/validate_frozen_impl.ipynb | 1600 ++++++++------------------ 1 file changed, 450 insertions(+), 1150 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index c4262ba02b..dbc4ea7990 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -205,15 +205,28 @@ "trip_mode_choice tour_type 'shopping' (77 trips)\n", "trip_mode_choice tour_type 'social' (19 trips)\n", "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n" + "trip_mode_choice tour_type 'work' (168 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n", + "Running with %d trips 482\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -221,7 +234,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", "trip_mode_choice tour_type 'escort' (6 trips)\n", @@ -271,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 10, "outputs": [ { "name": "stdout", @@ -295,25 +307,20 @@ } }, { - "cell_type": "markdown", - "source": [ - "# Run MC validation\n", - "\n", - "see logit._each_nest parent_nest_scale for leaf node, and simulate.compute_nested_utilities for discussion of scales\n", - "of alternatives\n", - "\n", - "easiest way is to run a simulation" - ], + "cell_type": "code", + "execution_count": 10, + "outputs": [], + "source": [], "metadata": { "collapsed": false, "pycharm": { - "name": "#%% md\n" + "name": "#%%\n" } } }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "outputs": [], "source": [], "metadata": { @@ -323,9 +330,26 @@ } } }, + { + "cell_type": "markdown", + "source": [ + "# Run MC validation\n", + "\n", + "see logit._each_nest parent_nest_scale for leaf node, and simulate.compute_nested_utilities for discussion of scales\n", + "of alternatives\n", + "\n", + "easiest way is to run a simulation" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "outputs": [ { "name": "stderr", @@ -358,20 +382,41 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 12, "outputs": [ { "data": { - "text/plain": " AUTO NONMOTORIZED TRANSIT RIDEHAIL DRIVEALONE \\\ntrip_id \n8805121 0.000000e+00 1.000000 0.000000 1.418229e-07 0.0 \n8805125 0.000000e+00 1.000000 0.000000 1.333383e-07 0.0 \n8805126 0.000000e+00 1.000000 0.000000 1.666869e-07 0.0 \n468119921 0.000000e+00 1.000000 0.000000 5.175690e-08 0.0 \n468119922 0.000000e+00 0.999874 0.000000 1.255768e-04 0.0 \n468119925 0.000000e+00 0.999875 0.000000 1.254853e-04 0.0 \n468119926 0.000000e+00 1.000000 0.000000 5.207466e-08 0.0 \n468119927 0.000000e+00 1.000000 0.000000 7.363066e-08 0.0 \n468119928 0.000000e+00 1.000000 0.000000 2.191280e-08 0.0 \n642446345 4.882838e-03 0.044905 0.949699 5.136180e-04 0.0 \n642446349 3.622165e-03 0.043359 0.952506 5.131357e-04 0.0 \n1179608009 7.537428e-05 0.969538 0.000000 3.038711e-02 0.0 \n1179608013 1.855034e-05 0.969598 0.000000 3.038345e-02 0.0 \n1658807153 5.751672e-07 0.048044 0.951445 5.107302e-04 0.0 \n1658807157 6.945596e-07 0.065847 0.933430 7.226819e-04 0.0 \n1767182945 1.610090e-02 0.046835 0.931353 5.711349e-03 0.0 \n1767182949 1.540381e-02 0.041767 0.937130 5.699151e-03 0.0 \n2464104881 2.228876e-05 0.646947 0.353030 4.902454e-07 0.0 \n2464104885 2.163505e-05 0.627973 0.372005 4.758670e-07 0.0 \n\n SHAREDRIDE2 SHAREDRIDE3 DRIVEALONEFREE DRIVEALONEPAY \\\ntrip_id \n8805121 0.000000 0.000000e+00 0.0 0.0 \n8805125 0.000000 0.000000e+00 0.0 0.0 \n8805126 0.000000 0.000000e+00 0.0 0.0 \n468119921 0.000000 0.000000e+00 0.0 0.0 \n468119922 0.000000 0.000000e+00 0.0 0.0 \n468119925 0.000000 0.000000e+00 0.0 0.0 \n468119926 0.000000 0.000000e+00 0.0 0.0 \n468119927 0.000000 0.000000e+00 0.0 0.0 \n468119928 0.000000 0.000000e+00 0.0 0.0 \n642446345 1.000000 9.628424e-12 0.0 0.0 \n642446349 1.000000 1.090669e-11 0.0 0.0 \n1179608009 0.739343 2.606568e-01 0.0 0.0 \n1179608013 0.597359 4.026407e-01 0.0 0.0 \n1658807153 0.739335 2.606646e-01 0.0 0.0 \n1658807157 0.725355 2.746454e-01 0.0 0.0 \n1767182945 1.000000 9.618359e-12 0.0 0.0 \n1767182949 1.000000 9.798594e-12 0.0 0.0 \n2464104881 1.000000 9.634468e-12 0.0 0.0 \n2464104885 1.000000 9.634468e-12 0.0 0.0 \n\n SHARED2FREE SHARED2PAY SHARED3FREE SHARED3PAY WALK BIKE \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 1.0 0.0 \n8805125 0.0 0.0 0.0 0.0 1.0 0.0 \n8805126 0.0 0.0 0.0 0.0 1.0 0.0 \n468119921 0.0 0.0 0.0 0.0 1.0 0.0 \n468119922 0.0 0.0 0.0 0.0 1.0 0.0 \n468119925 0.0 0.0 0.0 0.0 1.0 0.0 \n468119926 0.0 0.0 0.0 0.0 1.0 0.0 \n468119927 0.0 0.0 0.0 0.0 1.0 0.0 \n468119928 0.0 0.0 0.0 0.0 1.0 0.0 \n642446345 1.0 0.0 1.0 0.0 1.0 0.0 \n642446349 1.0 0.0 1.0 0.0 1.0 0.0 \n1179608009 1.0 0.0 1.0 0.0 1.0 0.0 \n1179608013 1.0 0.0 1.0 0.0 1.0 0.0 \n1658807153 1.0 0.0 1.0 0.0 1.0 0.0 \n1658807157 1.0 0.0 1.0 0.0 1.0 0.0 \n1767182945 1.0 0.0 1.0 0.0 1.0 0.0 \n1767182949 1.0 0.0 1.0 0.0 1.0 0.0 \n2464104881 1.0 0.0 1.0 0.0 1.0 0.0 \n2464104885 1.0 0.0 1.0 0.0 1.0 0.0 \n\n WALKACCESS DRIVEACCESS WALK_LOC WALK_LRF WALK_EXP WALK_HVY \\\ntrip_id \n8805121 0.0 0.0 0.000000 0.000000 0.0 0.0 \n8805125 0.0 0.0 0.000000 0.000000 0.0 0.0 \n8805126 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119921 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119922 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119925 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119926 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119927 0.0 0.0 0.000000 0.000000 0.0 0.0 \n468119928 0.0 0.0 0.000000 0.000000 0.0 0.0 \n642446345 1.0 0.0 0.063138 0.936862 0.0 0.0 \n642446349 1.0 0.0 0.071770 0.928230 0.0 0.0 \n1179608009 0.0 0.0 0.000000 0.000000 0.0 0.0 \n1179608013 0.0 0.0 0.000000 0.000000 0.0 0.0 \n1658807153 1.0 0.0 1.000000 0.000000 0.0 0.0 \n1658807157 1.0 0.0 1.000000 0.000000 0.0 0.0 \n1767182945 1.0 0.0 0.154889 0.845111 0.0 0.0 \n1767182949 1.0 0.0 0.150707 0.849293 0.0 0.0 \n2464104881 1.0 0.0 1.000000 0.000000 0.0 0.0 \n2464104885 1.0 0.0 1.000000 0.000000 0.0 0.0 \n\n WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.300854 0.553048 0.146098 \n8805125 0.304288 0.549407 0.146304 \n8805126 0.291815 0.562518 0.145667 \n468119921 0.211883 0.005323 0.782795 \n468119922 0.211720 0.005255 0.783024 \n468119925 0.211271 0.005261 0.783468 \n468119926 0.301114 0.004624 0.694262 \n468119927 0.720964 0.001787 0.277249 \n468119928 0.108040 0.006816 0.885144 \n642446345 0.187119 0.254576 0.558305 \n642446349 0.181108 0.256588 0.562305 \n1179608009 0.000172 0.910435 0.089393 \n1179608013 0.000172 0.910454 0.089374 \n1658807153 0.000139 0.919273 0.080588 \n1658807157 0.000132 0.919585 0.080283 \n1767182945 0.262451 0.588087 0.149462 \n1767182949 0.259083 0.592884 0.148034 \n2464104881 0.278497 0.000018 0.721485 \n2464104885 0.278497 0.000018 0.721485 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
AUTONONMOTORIZEDTRANSITRIDEHAILDRIVEALONESHAREDRIDE2SHAREDRIDE3DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALKACCESSDRIVEACCESSWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.000000e+001.0000000.0000001.418229e-070.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.3008540.5530480.146098
88051250.000000e+001.0000000.0000001.333383e-070.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.3042880.5494070.146304
88051260.000000e+001.0000000.0000001.666869e-070.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2918150.5625180.145667
4681199210.000000e+001.0000000.0000005.175690e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2118830.0053230.782795
4681199220.000000e+000.9998740.0000001.255768e-040.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2117200.0052550.783024
4681199250.000000e+000.9998750.0000001.254853e-040.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.2112710.0052610.783468
4681199260.000000e+001.0000000.0000005.207466e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.3011140.0046240.694262
4681199270.000000e+001.0000000.0000007.363066e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.7209640.0017870.277249
4681199280.000000e+001.0000000.0000002.191280e-080.00.0000000.000000e+000.00.00.00.00.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.1080400.0068160.885144
6424463454.882838e-030.0449050.9496995.136180e-040.01.0000009.628424e-120.00.01.00.01.00.01.00.01.00.00.0631380.9368620.00.00.00.00.00.00.00.00.1871190.2545760.558305
6424463493.622165e-030.0433590.9525065.131357e-040.01.0000001.090669e-110.00.01.00.01.00.01.00.01.00.00.0717700.9282300.00.00.00.00.00.00.00.00.1811080.2565880.562305
11796080097.537428e-050.9695380.0000003.038711e-020.00.7393432.606568e-010.00.01.00.01.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.0001720.9104350.089393
11796080131.855034e-050.9695980.0000003.038345e-020.00.5973594.026407e-010.00.01.00.01.00.01.00.00.00.00.0000000.0000000.00.00.00.00.00.00.00.00.0001720.9104540.089374
16588071535.751672e-070.0480440.9514455.107302e-040.00.7393352.606646e-010.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.0001390.9192730.080588
16588071576.945596e-070.0658470.9334307.226819e-040.00.7253552.746454e-010.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.0001320.9195850.080283
17671829451.610090e-020.0468350.9313535.711349e-030.01.0000009.618359e-120.00.01.00.01.00.01.00.01.00.00.1548890.8451110.00.00.00.00.00.00.00.00.2624510.5880870.149462
17671829491.540381e-020.0417670.9371305.699151e-030.01.0000009.798594e-120.00.01.00.01.00.01.00.01.00.00.1507070.8492930.00.00.00.00.00.00.00.00.2590830.5928840.148034
24641048812.228876e-050.6469470.3530304.902454e-070.01.0000009.634468e-120.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.2784970.0000180.721485
24641048852.163505e-050.6279730.3720054.758670e-070.01.0000009.634468e-120.00.01.00.01.00.01.00.01.00.01.0000000.0000000.00.00.00.00.00.00.00.00.2784970.0000180.721485
\n
" + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" }, - "execution_count": 15, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "nested_probs[0]" + "# nested_probs[0] - these are relative in each nest, so the full probs are potentially just the product of these\n", + "base_probs[0]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 13, + "outputs": [], + "source": [ + "# for later: this seems to be a non-driving segment - grab workers or something later?\n", + "# for now: implement a run with seeding so I can reproduce these counts\n", + "\n", + "# actually, can I manipulate pipeline OR inject table dynamically? If I reproduce a single observation many times we\n", + "# can check mode shares\n", + "# 1) check tests\n", + "# 2) check code in mode choice, maybe duplicate here, see old code below" ], "metadata": { "collapsed": false, @@ -380,22 +425,28 @@ } } }, + { + "cell_type": "markdown", + "source": [], + "metadata": { + "collapsed": false + } + }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 70, "outputs": [ { "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" + "text/plain": "array([ 8684833, 8684837, 8685009, 8685013, 8753057,\n 8753061, 8753233, 8753237, 8805121, 8805125,\n 8805126, 9094329, 9094333, 9717265, 9717269,\n 35302033, 35302037, 36757193, 36757197, 36757257,\n 36757261, 86627409, 86627413, 86627625, 86627629,\n 86673657, 86673658, 86673659, 86673660, 86673661,\n 106170305, 106170309, 106289041, 106289045, 106289089,\n 106289090, 106289091, 106289092, 106289093, 106289369,\n 106289373, 106570377, 106570381, 106570382, 106570383,\n 106570465, 106570469, 106741681, 106741682, 106741685,\n 106741686, 106741687, 106741688, 106741961, 106741965,\n 106804593, 106804597, 106804598, 106804657, 106804661,\n 106804662, 106804663, 106804985, 106804989, 126610345,\n 126610349, 126610561, 126610565, 126610566, 126610673,\n 126610677, 126610873, 126610877, 126628233, 126628237,\n 126628585, 126628589, 137248721, 137248725, 137248873,\n 137248877, 137248961, 137248965, 137249225, 137249229,\n 195385257, 195385261, 211327433, 211327437, 211327673,\n 211327677, 211327761, 211327765, 211328025, 211328029,\n 211328030, 211388201, 211388205, 211388329, 211388333,\n 211388353, 211388357, 211388441, 211388445, 211388721,\n 211388725, 211389033, 211389037, 414508737, 414508741,\n 415214745, 415214749, 415556913, 415556917, 444793569,\n 444793573, 444793574, 444853217, 444853221, 444853265,\n 444853269, 444928617, 444928621, 467399281, 467399285,\n 468119569, 468119573, 468119857, 468119861, 468119921,\n 468119922, 468119925, 468119926, 468119927, 468119928,\n 484173817, 484173821, 484173905, 484173909, 484174185,\n 484174189, 515832417, 515832418, 515832419, 515832421,\n 515832422, 515832423, 515921265, 515921266, 515921269,\n 515921305, 515921309, 535170409, 535170413, 535170689,\n 535170693, 535170694, 535377657, 535377661, 535388201,\n 535388202, 535388205, 535388481, 535388485, 535620049,\n 535620053, 535672921, 535672925, 535694417, 535694421,\n 535694457, 535694461, 535694465, 535694466, 535694469,\n 581959169, 581959170, 581959171, 581959172, 581959173,\n 581959174, 581959561, 581959565, 615236801, 615236805,\n 615236865, 615236866, 615236869, 615350289, 615350293,\n 615350553, 615350557, 615350558, 615350617, 615350621,\n 642446345, 642446349, 642446633, 642446637, 708171009,\n 708171013, 708171014, 708171273, 708171277, 708171601,\n 708171605, 728159001, 728159005, 728159049, 728159053,\n 728159329, 728159333, 728159569, 728159573, 728159593,\n 728159597, 806388153, 806388157, 806388225, 806388229,\n 806388401, 806388405, 806388905, 806388906, 806388909,\n 806389233, 806389237, 841877257, 841877261, 841877849,\n 841877853, 841878177, 841878181, 841878505, 841878509,\n 841878510, 943749401, 943749405, 943749465, 943749469,\n 943749470, 943749471, 943750385, 943750389, 963188601,\n 963188605, 963188665, 963188669, 963285425, 963285426,\n 963285427, 963285429, 963285433, 963285437, 963285438,\n 963285439, 963286193, 963286197, 963286345, 963286346,\n 963286349, 969957321, 969957325, 1004301497, 1004301501,\n 1004301761, 1004301765, 1004301785, 1004301789, 1004301873,\n 1004301877, 1045822217, 1045822221, 1045822409, 1045822410,\n 1045822413, 1045822737, 1045822741, 1045823001, 1045823005,\n 1045823006, 1045823007, 1045823065, 1045823069, 1045823393,\n 1045823394, 1045823397, 1045823398, 1060575737, 1060575741,\n 1060575801, 1060575805, 1060575809, 1060575813, 1060575849,\n 1060575853, 1091733225, 1091733229, 1091770617, 1091770621,\n 1091770681, 1091770685, 1091770897, 1091770898, 1091770901,\n 1146269081, 1146269085, 1146269345, 1146269349, 1146269369,\n 1146269373, 1146269374, 1146269375, 1146269376, 1146386553,\n 1146386557, 1146386561, 1146386565, 1146386881, 1146386885,\n 1146472377, 1146472381, 1146472489, 1146472493, 1146472537,\n 1146472538, 1146472539, 1146472540, 1146472541, 1146472817,\n 1146472821, 1179607441, 1179607445, 1179607657, 1179607661,\n 1179607662, 1179607663, 1179608009, 1179608013, 1276281769,\n 1276281773, 1276282049, 1276282053, 1276282313, 1276282317,\n 1276282321, 1276282325, 1276282361, 1276282365, 1368289969,\n 1368289973, 1368290273, 1368290277, 1368290689, 1368290690,\n 1368290693, 1368291297, 1368291301, 1368291302, 1368291609,\n 1368291613, 1368292281, 1368292285, 1368292377, 1368292381,\n 1368292382, 1368292657, 1368292658, 1368292661, 1368292662,\n 1582205617, 1582205621, 1582205641, 1582205645, 1582205729,\n 1582205733, 1658748793, 1658748797, 1658806913, 1658806917,\n 1658807089, 1658807093, 1658807129, 1658807133, 1658807153,\n 1658807157, 1658807177, 1658807181, 1658947233, 1658947234,\n 1658947237, 1658947238, 1766872681, 1766872685, 1766872897,\n 1766872901, 1766872921, 1766872925, 1767013721, 1767013725,\n 1767013726, 1767013785, 1767013789, 1767182857, 1767182861,\n 1767182865, 1767182869, 1767182945, 1767182949, 1767183169,\n 1767183173, 1767183297, 1767183301, 1767186249, 1767186253,\n 1767186577, 1767186578, 1767186581, 1767186582, 1767666161,\n 1767666165, 1767666233, 1767666237, 1767666441, 1767666445,\n 1767666769, 1767666773, 1768236897, 1768236901, 1768237161,\n 1768237165, 1768237166, 1768237167, 1768237489, 1768237493,\n 2396217321, 2396217325, 2396217433, 2396217437, 2396217761,\n 2396217765, 2396217769, 2396217773, 2444719729, 2444719733,\n 2463663417, 2463663421, 2463971785, 2463971789, 2463971790,\n 2463972065, 2463972069, 2463972070, 2463972071, 2464104641,\n 2464104645, 2464104857, 2464104861, 2464104881, 2464104885,\n 2464131801, 2464131805, 2464406009, 2464406013, 2464443401,\n 2464443405, 2464446025, 2464446029, 2464449633, 2464449634,\n 2464449637, 2467713777, 2467713781, 2467756745, 2467756749,\n 2472945097, 2472945101, 2472945113, 2472945117, 2472945118,\n 2473024473, 2473024477])" }, - "execution_count": 16, + "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "base_probs[0]" + "pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values" ], "metadata": { "collapsed": false, @@ -409,8 +460,7 @@ "execution_count": null, "outputs": [], "source": [ - "# for later: this seems to be a non-driving segment - grab workers or something later?\n", - "# for now: implement a run with seeding so I can reproduce these counts" + "pipeline._PIPELINE.rng().add_channel(\"trips\", )" ], "metadata": { "collapsed": false, @@ -420,10 +470,344 @@ } }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 59, + "outputs": [ + { + "data": { + "text/plain": "" + }, + "execution_count": 59, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pipeline._PIPELINE.rng().Sim" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 65, + "outputs": [], + "source": [ + "def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples):\n", + " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", + " resume_after = \"trip_scheduling\"\n", + " model_name = \"trip_mode_choice\"\n", + " chunk_size = 0 # test_mtc means no chunking\n", + "\n", + " pipeline.open_pipeline(resume_after)\n", + " # preload any bulky injectables (e.g. skims) not in pipeline\n", + " inject.get_injectable('preload_injectables', None)\n", + " pipeline._PIPELINE.rng().begin_step(model_name)\n", + " #step_name = model_name\n", + " args = {}\n", + " #checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + " inject.set_step_args(args)\n", + "\n", + " trips = inject.get_table('trips')\n", + " tours_merged = inject.get_table('tours_merged')\n", + " network_los = inject.get_injectable('network_los')\n", + "\n", + " trace_label = 'trip_mode_choice'\n", + " model_settings_file_name = 'trip_mode_choice.yaml'\n", + " model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + " mode_column_name = 'trip_mode'\n", + "\n", + " trips_df = trips.to_frame()\n", + " print(\"Running with %d trips\", trips_df.shape[0])\n", + "\n", + " tours_merged = tours_merged.to_frame()\n", + " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "\n", + " # - trips_merged - merge trips and tours_merged\n", + " trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + " assert trips_merged.index.equals(trips.index)\n", + "\n", + " # setup skim keys\n", + " assert ('trip_period' not in trips_merged)\n", + " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "\n", + " orig_col = 'origin'\n", + " dest_col = 'destination'\n", + "\n", + " constants = {}\n", + " constants.update(config.get_model_constants(model_settings))\n", + " constants.update({\n", + " 'ORIGIN': orig_col,\n", + " 'DESTINATION': dest_col\n", + " })\n", + "\n", + " skim_dict = network_los.get_default_skim_dict()\n", + "\n", + " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", + " dim3_key='trip_period')\n", + " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", + " dim3_key='trip_period')\n", + " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", + "\n", + " skims = {\n", + " \"odt_skims\": odt_skim_stack_wrapper,\n", + " \"dot_skims\": dot_skim_stack_wrapper,\n", + " \"od_skims\": od_skim_wrapper,\n", + " }\n", + "\n", + " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + " nest_specs = config.get_logit_model_settings(model_settings)\n", + "\n", + " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", + "\n", + " choices_list = []\n", + "\n", + "\n", + " # grab one, duplicate num_samples times\n", + " trips_segment = trips_merged.loc[trips_merged.index == trip_id_to_check].copy()\n", + " primary_purpose = trips_segment['primary_purpose'].values[0]\n", + " trips_segment = trips_segment.loc[trips_segment.index.repeat(num_samples)]\n", + "\n", + " # need to add new row_states for rng here, need to ensure there are no collisions with existing keys\n", + " existing_indexes = pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values\n", + " num_new_indexes = trips_segment.shape[0]\n", + " now find indexes - random draw and then remove existing and do again? simple loop for each, or batch and do\n", + " recursively? simple loop is fast enough here\n", + "\n", + "\n", + " trips_segment.index += np.arange(num_samples)\n", + " # name index so tracing knows how to slice\n", + " assert trips_segment.index.name == 'trip_id'\n", + "\n", + "\n", + " # #logger.warning(\"Change seeding back when done with testing\")\n", + " # pipeline._PIPELINE.rng.row_states = pd.DataFrame(columns=['row_seed', 'offset'], index=trips_segment.index)\n", + " # pipeline._PIPELINE.rng.row_states[\"row_seed\"] = trips_segment.index.values\n", + " # pipeline._PIPELINE.rng.row_states[\"offset\"] = 0\n", + " pipeline._PIPELINE.rng().add_channel(\"trips\", trips_segment)\n", + "\n", + "\n", + " #for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", + " #if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", + " # continue\n", + "\n", + " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", + " (primary_purpose, len(trips_segment.index), ))\n", + "\n", + " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", + "\n", + " locals_dict = {}\n", + " locals_dict.update(constants)\n", + " locals_dict.update(coefficients)\n", + "\n", + " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", + "\n", + " expressions.annotate_preprocessors(\n", + " trips_segment, locals_dict, skims,\n", + " model_settings, segment_trace_label)\n", + "\n", + " locals_dict.update(skims)\n", + "\n", + " ################ Replace wrapper function\n", + " # choices = mode_choice_simulate(...)\n", + " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", + " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", + " choices = simulate.simple_simulate(\n", + " choosers=trips_segment,\n", + " spec=spec,\n", + " nest_spec=nest_spec,\n", + " skims=skims,\n", + " locals_d=locals_dict,\n", + " chunk_size=chunk_size,\n", + " want_logsums=logsum_column_name is not None,\n", + " trace_label=segment_trace_label,\n", + " trace_choice_name='trip_mode_choice',\n", + " estimator=estimator,\n", + " trace_column_names=None,\n", + " choose_individual_max_utility=choose_individual_max_utility)\n", + " # for consistency, always return dataframe, whether or not logsums were requested\n", + " if isinstance(choices, pd.Series):\n", + " choices = choices.to_frame('choice')\n", + " choices.rename(columns={'logsum': logsum_column_name,\n", + " 'choice': mode_column_name},\n", + " inplace=True)\n", + " if not choose_individual_max_utility:\n", + " alts = spec.columns\n", + " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", + " ################\n", + " choices_list.append(choices)\n", + " choices_df_asim = pd.concat(choices_list)\n", + "\n", + " # update trips table with choices (and potionally logssums)\n", + " trips_df = trips.to_frame()\n", + "\n", + " #if (do_these_purposes is not None):\n", + " # trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", + "\n", + " assign_in_place(trips_df, choices_df_asim)\n", + " assert not trips_df[mode_column_name].isnull().any()\n", + "\n", + " finalise = True\n", + " if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + " print(\"Done\")\n", + " return trips_df" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 66, + "outputs": [], + "source": [ + "#pipeline.close_pipeline()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 67, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n" + ] + }, + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mAssertionError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_30125/1730006119.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 2\u001B[0m \u001B[0mnum_samples\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;36m100\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0mtrip_id_to_check\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;36m642446345\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m----> 4\u001B[0;31m \u001B[0mt_\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mmode_choice_for_trip\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mchoose_individual_max_utility\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mchoose_individual_max_utility\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrip_id_to_check\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mtrip_id_to_check\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mnum_samples\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mnum_samples\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;32m/tmp/ipykernel_30125/3946923567.py\u001B[0m in \u001B[0;36mmode_choice_for_trip\u001B[0;34m(choose_individual_max_utility, trip_id_to_check, num_samples)\u001B[0m\n\u001B[1;32m 89\u001B[0m \u001B[0;31m# pipeline._PIPELINE.rng.row_states[\"row_seed\"] = trips_segment.index.values\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 90\u001B[0m \u001B[0;31m# pipeline._PIPELINE.rng.row_states[\"offset\"] = 0\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 91\u001B[0;31m \u001B[0mpipeline\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_PIPELINE\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrng\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_channel\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"trips\"\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrips_segment\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 92\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 93\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/random.py\u001B[0m in \u001B[0;36madd_channel\u001B[0;34m(self, channel_name, domain_df)\u001B[0m\n\u001B[1;32m 467\u001B[0m \u001B[0mchannel\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchannels\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mchannel_name\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 468\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 469\u001B[0;31m \u001B[0mchannel\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mextend_domain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdomain_df\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 470\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 471\u001B[0m \u001B[0;32melse\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/random.py\u001B[0m in \u001B[0;36mextend_domain\u001B[0;34m(self, domain_df)\u001B[0m\n\u001B[1;32m 142\u001B[0m \u001B[0;31m# row_states already exists, so we are extending\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 143\u001B[0m \u001B[0;31m# if extending, these should be new rows, no intersection with existing row_states\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 144\u001B[0;31m \u001B[0;32massert\u001B[0m \u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrow_states\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mindex\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mintersection\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdomain_df\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mindex\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 145\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrow_states\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mpd\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mconcat\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrow_states\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mrow_states\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 146\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mAssertionError\u001B[0m: " + ] + } + ], + "source": [ + "choose_individual_max_utility = False\n", + "num_samples = 100\n", + "trip_id_to_check = 642446345\n", + "t_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, trip_id_to_check=trip_id_to_check, num_samples=num_samples)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 18, + "outputs": [], + "source": [ + "#pipeline.registered_tables()\n", + "#list(map(lambda x: x['checkpoint_name'], pipeline._PIPELINE.checkpoints))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], "source": [], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 24, + "outputs": [], + "source": [ + "num_samples = 100\n", + "trip_id_to_check = 642446345\n", + "\n", + "trips_dup = trips_merged.loc[trips_merged.index == trip_id_to_check].copy()\n", + "trips_dup = trips_dup.loc[trips_dup.index.repeat(num_samples)]\n", + "trips_dup.index += np.arange(num_samples)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { @@ -476,7 +860,18 @@ }, { "cell_type": "code", - "execution_count": 453, + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", "id": "806ce752-0927-4d5b-a6cc-68d6c9b8a05e", "metadata": { "execution": { @@ -486,24 +881,13 @@ "shell.execute_reply": "2021-09-01T04:55:59.770801Z", "shell.execute_reply.started": "2021-09-01T04:55:59.542615Z" }, - "tags": [] + "tags": [], + "pycharm": { + "name": "#%% md\n" + } }, - "outputs": [], "source": [ - "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - " nested_probabilities = \\\n", - " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", - " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) \n", - " return base_probabilities\n", - "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)" + "# OLD" ] }, { @@ -521,6 +905,22 @@ }, "outputs": [], "source": [ + "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", + " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", + " logit.validate_nest_spec(nest_spec, trace_label)\n", + " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", + " log_alt_losers=log_alt_losers,\n", + " trace_label=trace_label, have_trace_targets=False,\n", + " estimator=estimator, trace_column_names=trace_column_names)\n", + " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", + " nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec,\n", + " trace_label=trace_label)\n", + " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", + " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", + " return base_probabilities\n", + "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)\n", + "\n", + "\n", "## caculate probabilities with Asim methodology, should be correct\n", "def gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", " trace_label=None, log_alt_losers=None, trace_column_names=None):\n", @@ -906,139 +1306,10 @@ "outputs": [], "source": [] }, - { - "cell_type": "markdown", - "id": "f9e8ef02-1d57-4b21-8aef-25a6e1095c02", - "metadata": {}, - "source": [ - "### try zenith normalisation of simple_simulate_rum" - ] - }, { "cell_type": "code", - "execution_count": 629, - "id": "db235e90-d2e5-4e90-b41c-6d90a670e41d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:24.045881Z", - "iopub.status.busy": "2021-09-01T11:37:24.045637Z", - "iopub.status.idle": "2021-09-01T11:37:24.276488Z", - "shell.execute_reply": "2021-09-01T11:37:24.274898Z", - "shell.execute_reply.started": "2021-09-01T11:37:24.045844Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation belowi s equivalent.\n", - "def compute_nested_utilities_zenith(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) # / nest.product_of_coefficients #coefficient\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", - " \n", - " # now go over all leaves and correct for scale\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] /= nest.coefficient\n", - " \n", - " return nested_utilities\n", - "\n", - "\n", - "def simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " utils_df = compute_nested_utilities_zenith(raw_utilities, nest_spec)\n", - "\n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices\n", - "\n", - "#simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" - ] - }, - { - "cell_type": "code", - "execution_count": 630, - "id": "f69bc3dc-84e4-4dd5-9a57-c053cadba4b5", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:24.828926Z", - "iopub.status.busy": "2021-09-01T11:37:24.828707Z", - "iopub.status.idle": "2021-09-01T11:37:25.046409Z", - "shell.execute_reply": "2021-09-01T11:37:25.045399Z", - "shell.execute_reply.started": "2021-09-01T11:37:24.828902Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " choices = simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " segment_trace_label, log_alt_losers=log_alt_losers, \n", - " trace_column_names=trace_column_names, seed=seed)\n", - "\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " choices_list.append(choices)\n", - "\n", - " choices_df = pd.concat(choices_list)\n", - " return choices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 631, - "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", + "execution_count": 631, + "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", "metadata": { "execution": { "iopub.execute_input": "2021-09-01T11:37:32.250808Z", @@ -1381,977 +1652,6 @@ " display((100.0 * full_share_incl_zenith).T)" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c0d3081-b45b-4573-9246-e4a9d39591dd", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "612b2f04-45e2-4eb2-b52a-9f753427877f", - "metadata": {}, - "source": [ - "## investigate diverging seed and look at diff in formulation" - ] - }, - { - "cell_type": "code", - "execution_count": 619, - "id": "ac09dc75-a92f-43fb-a0cd-fe7de1f8b66d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:12:02.988141Z", - "iopub.status.busy": "2021-09-01T11:12:02.987900Z", - "iopub.status.idle": "2021-09-01T11:12:03.212179Z", - "shell.execute_reply": "2021-09-01T11:12:03.210993Z", - "shell.execute_reply.started": "2021-09-01T11:12:02.988102Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation based on asim probabilities is equivalent\n", - "def compute_nested_utilities_asim(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) / nest.product_of_coefficients\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", - " return nested_utilities\n", - "\n", - "def compute_nested_utilities_zenith_check(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) # scale correction is below\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", - " \n", - " # now go over all leaves and correct for scale\n", - " for nest in logit.each_nest(nest_spec):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] /= nest.coefficient\n", - " \n", - " return nested_utilities" - ] - }, - { - "cell_type": "code", - "execution_count": 688, - "id": "4b7c6368-21ca-4442-969e-f2f8ff868842", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:49:56.958862Z", - "iopub.status.busy": "2021-09-01T20:49:56.958623Z", - "iopub.status.idle": "2021-09-01T20:49:57.191884Z", - "shell.execute_reply": "2021-09-01T20:49:57.190975Z", - "shell.execute_reply.started": "2021-09-01T20:49:56.958827Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None, use_zenith=True, raw_utilities=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " \n", - " if raw_utilities is None:\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " if use_zenith:\n", - " utils_df = compute_nested_utilities_zenith_check(raw_utilities, nest_spec)\n", - " else:\n", - " utils_df = compute_nested_utilities_asim(raw_utilities, nest_spec)\n", - "\n", - " # test\n", - " #return compute_nested_utilities_zenith_check(raw_utilities, nest_spec), compute_nested_utilities_asim(raw_utilities, nest_spec)\n", - " \n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices" - ] - }, - { - "cell_type": "code", - "execution_count": 626, - "id": "bd6cadc3-79f5-4824-b55e-639c39d5ab18", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:13:12.077886Z", - "iopub.status.busy": "2021-09-01T11:13:12.077652Z", - "iopub.status.idle": "2021-09-01T11:13:12.290222Z", - "shell.execute_reply": "2021-09-01T11:13:12.288861Z", - "shell.execute_reply.started": "2021-09-01T11:13:12.077863Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# t_z, t_a = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed)\n", - "# tr_id = 86673661\n", - "# display(t_z.loc[t_z.index==tr_id])\n", - "# display(t_a.loc[t_a.index==tr_id])" - ] - }, - { - "cell_type": "code", - "execution_count": 625, - "id": "42e6da62-58e8-4b16-9169-9faa1fc8a162", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:13:06.847583Z", - "iopub.status.busy": "2021-09-01T11:13:06.847204Z", - "iopub.status.idle": "2021-09-01T11:13:08.401700Z", - "shell.execute_reply": "2021-09-01T11:13:08.400711Z", - "shell.execute_reply.started": "2021-09-01T11:13:06.847554Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "seed 51006 leads to 34 differences out of 168. tripids Int64Index([ 86673657, 86673661, 106741681, 106741682, 106741685,\n", - " 106741686, 106741687, 106741688, 211327433, 211327437,\n", - " 444793574, 484173905, 484173909, 535170694, 535620053,\n", - " 708171014, 943749470, 943749471, 1060575853, 1091770617,\n", - " 1146472489, 1146472493, 1276281769, 1276281773, 1658748793,\n", - " 1658748797, 1767013726, 1767186577, 1767186578, 1768237161,\n", - " 1768237165, 1768237166, 2463663417, 2463663421],\n", - " dtype='int64', name='trip_id')\n" - ] - } - ], - "source": [ - "seed = 51006\n", - "x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True)\n", - "y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False)\n", - "t_ = x_.to_frame('trip_mode_zenith').merge(y_.to_frame('trip_mode_asim'), left_index=True, right_index=True)\n", - "diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", - "print(f\"seed {seed} leads to {diffs.shape[0]} differences out of {t_.shape[0]}. tripids {diffs.index}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2f3c0506-a79d-4a31-9d56-71b9966d0a92", - "metadata": {}, - "outputs": [], - "source": [ - "seed 51006 leads to 3 differences. tripids Int64Index([86673661, 535170689, 1060575849], dtype='int64', name='trip_id')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1a5f332a-b1d7-4a80-bb21-d37a1611cf9f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "24c5e4a1-6142-49d4-8da8-a1e266b3cb9b", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 753, - "id": "2098907f-28af-4f53-af74-6aaee8a6c53c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T23:04:23.861048Z", - "iopub.status.busy": "2021-09-01T23:04:23.860748Z", - "iopub.status.idle": "2021-09-01T23:04:24.069307Z", - "shell.execute_reply": "2021-09-01T23:04:24.068434Z", - "shell.execute_reply.started": "2021-09-01T23:04:23.861024Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "rng_ = default_rng(seed=100)\n", - "new_utils = pd.DataFrame(0.1 * -np.log(-np.log(rng_.uniform(0,1,raw_utilities.shape))), columns=raw_utilities.columns)\n", - "new_utils.index = raw_utilities.index" - ] - }, - { - "cell_type": "code", - "execution_count": 767, - "id": "da48a178-82b2-44a5-b45a-e9de3bb5e5c2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T23:17:20.369675Z", - "iopub.status.busy": "2021-09-01T23:17:20.369424Z", - "iopub.status.idle": "2021-09-01T23:17:20.666506Z", - "shell.execute_reply": "2021-09-01T23:17:20.665626Z", - "shell.execute_reply.started": "2021-09-01T23:17:20.369637Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
1372487210.0566420.0373050.0427800.0295760.0861540.0265860.0883130.1003310.0280210.0185160.0643910.0200090.0263990.0226220.0230070.0217540.0871260.0211710.0364750.0276500.135171
1372487250.1019020.0228760.0312270.0699900.0569130.0466850.0884110.0877850.0254140.0381620.0239880.0288000.0393180.0201600.0483640.0230030.0339590.0345970.0377080.0995230.041213
2113882010.0375620.0700330.0559510.0403890.0200410.0959740.0946780.0943680.0360140.0250510.0348600.0349530.0354990.0280400.0436500.0247130.0313880.0259310.0669990.0526780.051228
2113882050.0354800.0562560.0439440.0480850.0420780.0622210.1068190.0872650.0351170.0303990.0351540.0264690.0506460.0357270.0242840.0329050.0394270.0410920.0547140.0484590.063458
8063884010.0549180.0404880.0331490.0542940.0434770.0679820.1524650.0741520.0334040.0287350.0592690.0243390.0369680.0277600.0235220.0609320.0234450.0239310.0446430.0337730.058353
8063884050.0327640.0573010.0184370.0770990.0469630.0635210.0949510.0890720.0308260.0378960.0357520.0198780.0495360.0796100.0269220.0248330.0248400.0266950.0799360.0401460.043022
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n", - "trip_id \n", - "137248721 0.056642 0.037305 0.042780 0.029576 \n", - "137248725 0.101902 0.022876 0.031227 0.069990 \n", - "211388201 0.037562 0.070033 0.055951 0.040389 \n", - "211388205 0.035480 0.056256 0.043944 0.048085 \n", - "806388401 0.054918 0.040488 0.033149 0.054294 \n", - "806388405 0.032764 0.057301 0.018437 0.077099 \n", - "\n", - " SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\n", - "trip_id \n", - "137248721 0.086154 0.026586 0.088313 0.100331 0.028021 0.018516 \n", - "137248725 0.056913 0.046685 0.088411 0.087785 0.025414 0.038162 \n", - "211388201 0.020041 0.095974 0.094678 0.094368 0.036014 0.025051 \n", - "211388205 0.042078 0.062221 0.106819 0.087265 0.035117 0.030399 \n", - "806388401 0.043477 0.067982 0.152465 0.074152 0.033404 0.028735 \n", - "806388405 0.046963 0.063521 0.094951 0.089072 0.030826 0.037896 \n", - "\n", - " WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\n", - "trip_id \n", - "137248721 0.064391 0.020009 0.026399 0.022622 0.023007 0.021754 \n", - "137248725 0.023988 0.028800 0.039318 0.020160 0.048364 0.023003 \n", - "211388201 0.034860 0.034953 0.035499 0.028040 0.043650 0.024713 \n", - "211388205 0.035154 0.026469 0.050646 0.035727 0.024284 0.032905 \n", - "806388401 0.059269 0.024339 0.036968 0.027760 0.023522 0.060932 \n", - "806388405 0.035752 0.019878 0.049536 0.079610 0.026922 0.024833 \n", - "\n", - " DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n", - "trip_id \n", - "137248721 0.087126 0.021171 0.036475 0.027650 0.135171 \n", - "137248725 0.033959 0.034597 0.037708 0.099523 0.041213 \n", - "211388201 0.031388 0.025931 0.066999 0.052678 0.051228 \n", - "211388205 0.039427 0.041092 0.054714 0.048459 0.063458 \n", - "806388401 0.023445 0.023931 0.044643 0.033773 0.058353 \n", - "806388405 0.024840 0.026695 0.079936 0.040146 0.043022 " - ] - }, - "execution_count": 767, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nested_exp_utilities_cf = simulate.compute_nested_exp_utilities(new_utils, nest_spec)\n", - "nested_probabilities_cf = simulate.compute_nested_probabilities(nested_exp_utilities_cf, nest_spec, trace_label=None)\n", - "base_probabilities_cf = simulate.compute_base_probabilities(nested_probabilities_cf, nest_spec, spec)\n", - "base_probabilities_cf" - ] - }, - { - "cell_type": "code", - "execution_count": 799, - "id": "50dfd1ae-10c3-475c-94be-de783c2fa5c2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:15:29.056902Z", - "iopub.status.busy": "2021-09-02T00:15:29.056683Z", - "iopub.status.idle": "2021-09-02T00:27:21.965206Z", - "shell.execute_reply": "2021-09-02T00:27:21.964340Z", - "shell.execute_reply.started": "2021-09-02T00:15:29.056877Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 11min 49s, sys: 766 ms, total: 11min 50s\n", - "Wall time: 11min 52s\n" - ] - } - ], - "source": [ - "%%time\n", - "\n", - "num_samples = 10000 # 7.5s per 100\n", - "\n", - "data_zenith = []\n", - "data_asim = []\n", - "for i in range(num_samples):\n", - " seed = rng_.integers(0, 100000)\n", - " x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True, raw_utilities=new_utils)\n", - " y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False, raw_utilities=new_utils)\n", - " data_zenith.append(x_)\n", - " data_asim.append(y_)\n", - " \n", - "data_asim = pd.concat(data_asim, axis=1)\n", - "data_zenith = pd.concat(data_zenith, axis=1)\n", - "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", - "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_asim = counts_asim / data_asim.shape[1]\n", - "\n", - "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", - "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", - "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", - "# assert np.allclose(mode_share_zenith.sum(), 1)\n", - "# assert np.allclose(mode_share_asim.sum(), 1)\n", - "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", - "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 796, - "id": "bb16f9d7-44f5-4316-b981-d65216a5e217", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:14:11.772592Z", - "iopub.status.busy": "2021-09-02T00:14:11.772347Z", - "iopub.status.idle": "2021-09-02T00:14:12.001077Z", - "shell.execute_reply": "2021-09-02T00:14:12.000166Z", - "shell.execute_reply.started": "2021-09-02T00:14:11.772556Z" - } - }, - "outputs": [], - "source": [ - "#temp_z = data_zenith.copy()\n", - "#temp_a = data_asim.copy()\n", - "#data_asim = data_asim.join(temp_a, lsuffix=\"_o\", rsuffix=\"_n\")\n", - "#data_zenith = data_zenith.join(temp_z, lsuffix=\"_o\", rsuffix=\"_n\")\n", - "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", - "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_asim = counts_asim / data_asim.shape[1]\n", - "\n", - "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", - "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", - "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", - "# assert np.allclose(mode_share_zenith.sum(), 1)\n", - "# assert np.allclose(mode_share_asim.sum(), 1)\n", - "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", - "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 805, - "id": "031d8340-0af0-4608-8a83-ed2af4aaa3b2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:41:01.948669Z", - "iopub.status.busy": "2021-09-02T00:41:01.948422Z", - "iopub.status.idle": "2021-09-02T00:41:02.175972Z", - "shell.execute_reply": "2021-09-02T00:41:02.174759Z", - "shell.execute_reply.started": "2021-09-02T00:41:01.948633Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
zenith8.0555.1674.6313.5023.6064.2663.9633.4983.8955.1764.7995.8514.9996.1214.6689.4864.2284.1252.9773.6713.317
asim6.0966.6984.9773.2023.5224.3344.1293.1843.2245.1496.2838.2964.7936.5844.5327.4844.1524.2732.6293.4333.023
probs8.8835.3214.7382.8903.1364.0033.5653.1623.7585.3244.9276.0495.3416.5415.03710.4273.9734.2242.5743.1472.979
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP DRIVE_HVY \\\n", - "zenith 8.055 5.167 4.631 3.502 3.606 4.266 \n", - "asim 6.096 6.698 4.977 3.202 3.522 4.334 \n", - "probs 8.883 5.321 4.738 2.890 3.136 4.003 \n", - "\n", - " DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY SHARED3FREE \\\n", - "zenith 3.963 3.498 3.895 5.176 4.799 \n", - "asim 4.129 3.184 3.224 5.149 6.283 \n", - "probs 3.565 3.162 3.758 5.324 4.927 \n", - "\n", - " SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK WALK_COM WALK_EXP \\\n", - "zenith 5.851 4.999 6.121 4.668 9.486 4.228 4.125 \n", - "asim 8.296 4.793 6.584 4.532 7.484 4.152 4.273 \n", - "probs 6.049 5.341 6.541 5.037 10.427 3.973 4.224 \n", - "\n", - " WALK_HVY WALK_LOC WALK_LRF \n", - "zenith 2.977 3.671 3.317 \n", - "asim 2.629 3.433 3.023 \n", - "probs 2.574 3.147 2.979 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# sample of 20000\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * mode_share_comp).T)" - ] - }, - { - "cell_type": "code", - "execution_count": 806, - "id": "aece448f-4055-4d2a-b90d-8530caec9cb9", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:43:32.971972Z", - "iopub.status.busy": "2021-09-02T00:43:32.971684Z", - "iopub.status.idle": "2021-09-02T00:43:33.223345Z", - "shell.execute_reply": "2021-09-02T00:43:33.222488Z", - "shell.execute_reply.started": "2021-09-02T00:43:32.971932Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
trip_id137248721_z137248721_a137248721
BIKE0.092700.054350.100331
DRIVEALONEFREE0.053650.050050.056642
DRIVEALONEPAY0.038800.031550.037305
DRIVE_COM0.028700.024700.021171
DRIVE_EXP0.028800.026050.021754
DRIVE_HVY0.078150.099700.087126
DRIVE_LOC0.028750.027000.022622
DRIVE_LRF0.027800.024450.023007
SHARED2FREE0.043250.023000.042780
SHARED2PAY0.033600.015850.029576
SHARED3FREE0.073650.119200.086154
SHARED3PAY0.032600.039600.026586
TAXI0.035100.050300.036475
TNC_SHARED0.126200.181150.135171
TNC_SINGLE0.026250.038450.027650
WALK0.079400.048250.088313
WALK_COM0.031850.024600.026399
WALK_EXP0.057050.058950.064391
WALK_HVY0.025200.018400.020009
WALK_LOC0.034550.027400.028021
WALK_LRF0.023950.017000.018516
\n", - "
" - ], - "text/plain": [ - "trip_id 137248721_z 137248721_a 137248721\n", - "BIKE 0.09270 0.05435 0.100331\n", - "DRIVEALONEFREE 0.05365 0.05005 0.056642\n", - "DRIVEALONEPAY 0.03880 0.03155 0.037305\n", - "DRIVE_COM 0.02870 0.02470 0.021171\n", - "DRIVE_EXP 0.02880 0.02605 0.021754\n", - "DRIVE_HVY 0.07815 0.09970 0.087126\n", - "DRIVE_LOC 0.02875 0.02700 0.022622\n", - "DRIVE_LRF 0.02780 0.02445 0.023007\n", - "SHARED2FREE 0.04325 0.02300 0.042780\n", - "SHARED2PAY 0.03360 0.01585 0.029576\n", - "SHARED3FREE 0.07365 0.11920 0.086154\n", - "SHARED3PAY 0.03260 0.03960 0.026586\n", - "TAXI 0.03510 0.05030 0.036475\n", - "TNC_SHARED 0.12620 0.18115 0.135171\n", - "TNC_SINGLE 0.02625 0.03845 0.027650\n", - "WALK 0.07940 0.04825 0.088313\n", - "WALK_COM 0.03185 0.02460 0.026399\n", - "WALK_EXP 0.05705 0.05895 0.064391\n", - "WALK_HVY 0.02520 0.01840 0.020009\n", - "WALK_LOC 0.03455 0.02740 0.028021\n", - "WALK_LRF 0.02395 0.01700 0.018516" - ] - }, - "execution_count": 806, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# TODO: look at probs per trip, i.e. do not sum across trips\n", - "trip_id = 137248721\n", - "\n", - "counts_zenith.loc[counts_zenith.index == trip_id].T.merge(\n", - " counts_asim.loc[counts_asim.index == trip_id].T, suffixes=['_z', '_a'], left_index=True, right_index=True, how='outer').merge(\n", - " base_probabilities_cf.loc[base_probabilities_cf.index == trip_id].T, suffixes=['', '_probs'], left_index=True, right_index=True, how='outer').fillna(0)" - ] - }, { "cell_type": "code", "execution_count": null, From a31d97f94e9698a37a032b463884f77e3d5cceac Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 5 May 2022 14:08:14 +1000 Subject: [PATCH 009/298] probs trip set up --- notebooks/validate_frozen_impl.ipynb | 243 ++++----------------------- 1 file changed, 31 insertions(+), 212 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index dbc4ea7990..90240ad13f 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -434,67 +434,7 @@ }, { "cell_type": "code", - "execution_count": 70, - "outputs": [ - { - "data": { - "text/plain": "array([ 8684833, 8684837, 8685009, 8685013, 8753057,\n 8753061, 8753233, 8753237, 8805121, 8805125,\n 8805126, 9094329, 9094333, 9717265, 9717269,\n 35302033, 35302037, 36757193, 36757197, 36757257,\n 36757261, 86627409, 86627413, 86627625, 86627629,\n 86673657, 86673658, 86673659, 86673660, 86673661,\n 106170305, 106170309, 106289041, 106289045, 106289089,\n 106289090, 106289091, 106289092, 106289093, 106289369,\n 106289373, 106570377, 106570381, 106570382, 106570383,\n 106570465, 106570469, 106741681, 106741682, 106741685,\n 106741686, 106741687, 106741688, 106741961, 106741965,\n 106804593, 106804597, 106804598, 106804657, 106804661,\n 106804662, 106804663, 106804985, 106804989, 126610345,\n 126610349, 126610561, 126610565, 126610566, 126610673,\n 126610677, 126610873, 126610877, 126628233, 126628237,\n 126628585, 126628589, 137248721, 137248725, 137248873,\n 137248877, 137248961, 137248965, 137249225, 137249229,\n 195385257, 195385261, 211327433, 211327437, 211327673,\n 211327677, 211327761, 211327765, 211328025, 211328029,\n 211328030, 211388201, 211388205, 211388329, 211388333,\n 211388353, 211388357, 211388441, 211388445, 211388721,\n 211388725, 211389033, 211389037, 414508737, 414508741,\n 415214745, 415214749, 415556913, 415556917, 444793569,\n 444793573, 444793574, 444853217, 444853221, 444853265,\n 444853269, 444928617, 444928621, 467399281, 467399285,\n 468119569, 468119573, 468119857, 468119861, 468119921,\n 468119922, 468119925, 468119926, 468119927, 468119928,\n 484173817, 484173821, 484173905, 484173909, 484174185,\n 484174189, 515832417, 515832418, 515832419, 515832421,\n 515832422, 515832423, 515921265, 515921266, 515921269,\n 515921305, 515921309, 535170409, 535170413, 535170689,\n 535170693, 535170694, 535377657, 535377661, 535388201,\n 535388202, 535388205, 535388481, 535388485, 535620049,\n 535620053, 535672921, 535672925, 535694417, 535694421,\n 535694457, 535694461, 535694465, 535694466, 535694469,\n 581959169, 581959170, 581959171, 581959172, 581959173,\n 581959174, 581959561, 581959565, 615236801, 615236805,\n 615236865, 615236866, 615236869, 615350289, 615350293,\n 615350553, 615350557, 615350558, 615350617, 615350621,\n 642446345, 642446349, 642446633, 642446637, 708171009,\n 708171013, 708171014, 708171273, 708171277, 708171601,\n 708171605, 728159001, 728159005, 728159049, 728159053,\n 728159329, 728159333, 728159569, 728159573, 728159593,\n 728159597, 806388153, 806388157, 806388225, 806388229,\n 806388401, 806388405, 806388905, 806388906, 806388909,\n 806389233, 806389237, 841877257, 841877261, 841877849,\n 841877853, 841878177, 841878181, 841878505, 841878509,\n 841878510, 943749401, 943749405, 943749465, 943749469,\n 943749470, 943749471, 943750385, 943750389, 963188601,\n 963188605, 963188665, 963188669, 963285425, 963285426,\n 963285427, 963285429, 963285433, 963285437, 963285438,\n 963285439, 963286193, 963286197, 963286345, 963286346,\n 963286349, 969957321, 969957325, 1004301497, 1004301501,\n 1004301761, 1004301765, 1004301785, 1004301789, 1004301873,\n 1004301877, 1045822217, 1045822221, 1045822409, 1045822410,\n 1045822413, 1045822737, 1045822741, 1045823001, 1045823005,\n 1045823006, 1045823007, 1045823065, 1045823069, 1045823393,\n 1045823394, 1045823397, 1045823398, 1060575737, 1060575741,\n 1060575801, 1060575805, 1060575809, 1060575813, 1060575849,\n 1060575853, 1091733225, 1091733229, 1091770617, 1091770621,\n 1091770681, 1091770685, 1091770897, 1091770898, 1091770901,\n 1146269081, 1146269085, 1146269345, 1146269349, 1146269369,\n 1146269373, 1146269374, 1146269375, 1146269376, 1146386553,\n 1146386557, 1146386561, 1146386565, 1146386881, 1146386885,\n 1146472377, 1146472381, 1146472489, 1146472493, 1146472537,\n 1146472538, 1146472539, 1146472540, 1146472541, 1146472817,\n 1146472821, 1179607441, 1179607445, 1179607657, 1179607661,\n 1179607662, 1179607663, 1179608009, 1179608013, 1276281769,\n 1276281773, 1276282049, 1276282053, 1276282313, 1276282317,\n 1276282321, 1276282325, 1276282361, 1276282365, 1368289969,\n 1368289973, 1368290273, 1368290277, 1368290689, 1368290690,\n 1368290693, 1368291297, 1368291301, 1368291302, 1368291609,\n 1368291613, 1368292281, 1368292285, 1368292377, 1368292381,\n 1368292382, 1368292657, 1368292658, 1368292661, 1368292662,\n 1582205617, 1582205621, 1582205641, 1582205645, 1582205729,\n 1582205733, 1658748793, 1658748797, 1658806913, 1658806917,\n 1658807089, 1658807093, 1658807129, 1658807133, 1658807153,\n 1658807157, 1658807177, 1658807181, 1658947233, 1658947234,\n 1658947237, 1658947238, 1766872681, 1766872685, 1766872897,\n 1766872901, 1766872921, 1766872925, 1767013721, 1767013725,\n 1767013726, 1767013785, 1767013789, 1767182857, 1767182861,\n 1767182865, 1767182869, 1767182945, 1767182949, 1767183169,\n 1767183173, 1767183297, 1767183301, 1767186249, 1767186253,\n 1767186577, 1767186578, 1767186581, 1767186582, 1767666161,\n 1767666165, 1767666233, 1767666237, 1767666441, 1767666445,\n 1767666769, 1767666773, 1768236897, 1768236901, 1768237161,\n 1768237165, 1768237166, 1768237167, 1768237489, 1768237493,\n 2396217321, 2396217325, 2396217433, 2396217437, 2396217761,\n 2396217765, 2396217769, 2396217773, 2444719729, 2444719733,\n 2463663417, 2463663421, 2463971785, 2463971789, 2463971790,\n 2463972065, 2463972069, 2463972070, 2463972071, 2464104641,\n 2464104645, 2464104857, 2464104861, 2464104881, 2464104885,\n 2464131801, 2464131805, 2464406009, 2464406013, 2464443401,\n 2464443405, 2464446025, 2464446029, 2464449633, 2464449634,\n 2464449637, 2467713777, 2467713781, 2467756745, 2467756749,\n 2472945097, 2472945101, 2472945113, 2472945117, 2472945118,\n 2473024473, 2473024477])" - }, - "execution_count": 70, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "pipeline._PIPELINE.rng().add_channel(\"trips\", )" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 59, - "outputs": [ - { - "data": { - "text/plain": "" - }, - "execution_count": 59, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipeline._PIPELINE.rng().Sim" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 65, + "execution_count": 83, "outputs": [], "source": [ "def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples):\n", @@ -571,7 +511,7 @@ "\n", " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", "\n", - " choices_list = []\n", + " #choices_list = []\n", "\n", "\n", " # grab one, duplicate num_samples times\n", @@ -582,13 +522,11 @@ " # need to add new row_states for rng here, need to ensure there are no collisions with existing keys\n", " existing_indexes = pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values\n", " num_new_indexes = trips_segment.shape[0]\n", - " now find indexes - random draw and then remove existing and do again? simple loop for each, or batch and do\n", - " recursively? simple loop is fast enough here\n", - "\n", + " new_indexes = np.arange(existing_indexes.max()+1, existing_indexes.max() + num_new_indexes + 1)\n", "\n", - " trips_segment.index += np.arange(num_samples)\n", + " trips_segment.index = new_indexes #+= np.arange(num_samples)\n", " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", + " trips_segment.index.name = 'trip_id'\n", "\n", "\n", " # #logger.warning(\"Change seeding back when done with testing\")\n", @@ -646,17 +584,17 @@ " alts = spec.columns\n", " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", " ################\n", - " choices_list.append(choices)\n", - " choices_df_asim = pd.concat(choices_list)\n", + " #choices_list.append(choices)\n", + " #choices_df_asim = pd.concat(choices_list)\n", "\n", " # update trips table with choices (and potionally logssums)\n", - " trips_df = trips.to_frame()\n", + " #trips_df = trips_merged.copy() # trips.to_frame()\n", "\n", " #if (do_these_purposes is not None):\n", " # trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", "\n", - " assign_in_place(trips_df, choices_df_asim)\n", - " assert not trips_df[mode_column_name].isnull().any()\n", + " #assign_in_place(trips_df, choices)\n", + " #assert not trips_df[mode_column_name].isnull().any()\n", "\n", " finalise = True\n", " if finalise:\n", @@ -670,7 +608,7 @@ " pipeline.close_pipeline()\n", "\n", " print(\"Done\")\n", - " return trips_df" + " return trips_merged, choices" ], "metadata": { "collapsed": false, @@ -681,10 +619,10 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 84, "outputs": [], "source": [ - "#pipeline.close_pipeline()" + "pipeline.close_pipeline()" ], "metadata": { "collapsed": false, @@ -695,7 +633,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": null, "outputs": [ { "name": "stderr", @@ -709,152 +647,33 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running with %d trips 482\n" - ] - }, - { - "ename": "AssertionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mAssertionError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m/tmp/ipykernel_30125/1730006119.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 2\u001B[0m \u001B[0mnum_samples\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;36m100\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0mtrip_id_to_check\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;36m642446345\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m----> 4\u001B[0;31m \u001B[0mt_\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mmode_choice_for_trip\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mchoose_individual_max_utility\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mchoose_individual_max_utility\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrip_id_to_check\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mtrip_id_to_check\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mnum_samples\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mnum_samples\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", - "\u001B[0;32m/tmp/ipykernel_30125/3946923567.py\u001B[0m in \u001B[0;36mmode_choice_for_trip\u001B[0;34m(choose_individual_max_utility, trip_id_to_check, num_samples)\u001B[0m\n\u001B[1;32m 89\u001B[0m \u001B[0;31m# pipeline._PIPELINE.rng.row_states[\"row_seed\"] = trips_segment.index.values\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 90\u001B[0m \u001B[0;31m# pipeline._PIPELINE.rng.row_states[\"offset\"] = 0\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 91\u001B[0;31m \u001B[0mpipeline\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_PIPELINE\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrng\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_channel\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"trips\"\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrips_segment\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 92\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 93\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/random.py\u001B[0m in \u001B[0;36madd_channel\u001B[0;34m(self, channel_name, domain_df)\u001B[0m\n\u001B[1;32m 467\u001B[0m \u001B[0mchannel\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchannels\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mchannel_name\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 468\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 469\u001B[0;31m \u001B[0mchannel\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mextend_domain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdomain_df\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 470\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 471\u001B[0m \u001B[0;32melse\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/random.py\u001B[0m in \u001B[0;36mextend_domain\u001B[0;34m(self, domain_df)\u001B[0m\n\u001B[1;32m 142\u001B[0m \u001B[0;31m# row_states already exists, so we are extending\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 143\u001B[0m \u001B[0;31m# if extending, these should be new rows, no intersection with existing row_states\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 144\u001B[0;31m \u001B[0;32massert\u001B[0m \u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrow_states\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mindex\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mintersection\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdomain_df\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mindex\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 145\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrow_states\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mpd\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mconcat\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrow_states\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mrow_states\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 146\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;31mAssertionError\u001B[0m: " + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'social' (1000000 trips)\n" ] } ], "source": [ "choose_individual_max_utility = False\n", - "num_samples = 100\n", - "trip_id_to_check = 642446345\n", - "t_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, trip_id_to_check=trip_id_to_check, num_samples=num_samples)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 18, - "outputs": [], - "source": [ - "#pipeline.registered_tables()\n", - "#list(map(lambda x: x['checkpoint_name'], pipeline._PIPELINE.checkpoints))" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 24, - "outputs": [], - "source": [ - "num_samples = 100\n", + "num_samples = 1000000\n", "trip_id_to_check = 642446345\n", - "\n", - "trips_dup = trips_merged.loc[trips_merged.index == trip_id_to_check].copy()\n", - "trips_dup = trips_dup.loc[trips_dup.index.repeat(num_samples)]\n", - "trips_dup.index += np.arange(num_samples)" + "t_, c_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility,\n", + " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", + "\n", + "sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", + "#sim_mode_shares.columns = [\"mode_share_sim\"]\n", + "obs_probs = base_probs[0].loc[base_probs[0].index == trip_id_to_check].T\n", + "obs_probs.columns = [\"mode_share_obs\"]\n", + "ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how=\"outer\").fillna(0)\n", + "ms_comp[\"diff\"] = ms_comp[\"trip_mode\"] - ms_comp[\"mode_share_obs\"]\n", + "ms_comp[\"rel_diff\"] = ms_comp[\"diff\"] / ms_comp[\"mode_share_obs\"]\n", + "#ms_comp.style.format('{:.2}')\n", + "ms_comp.loc[(ms_comp.mode_share_obs != 0) & (ms_comp.trip_mode != 0), [\"mode_share_obs\", \"trip_mode\"]].T" ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" + "name": "#%%\n", + "is_executing": true } } }, From ab8a212977ba886a0a7041f425a3c35580b911b7 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 5 May 2022 14:11:42 +1000 Subject: [PATCH 010/298] probs validation clean up --- notebooks/validate_frozen_impl.ipynb | 840 ++------------------------- 1 file changed, 44 insertions(+), 796 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 90240ad13f..95127ec5c1 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -633,7 +633,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 138, "outputs": [ { "name": "stderr", @@ -650,6 +650,41 @@ "Running with %d trips 482\n", "trip_mode_choice tour_type 'social' (1000000 trips)\n" ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n" + ] + }, + { + "data": { + "text/plain": " SHARED2FREE TAXI TNC_SHARED TNC_SINGLE WALK \\\nmode_share_obs 0.004883 0.000096 0.000287 0.000131 0.044905 \ntrip_mode 0.004885 0.000095 0.000291 0.000125 0.044628 \n\n WALK_LOC WALK_LRF \nmode_share_obs 0.059962 0.889736 \ntrip_mode 0.060309 0.889667 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048830.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0048850.0000950.0002910.0001250.0446280.0603090.889667
\n
" + }, + "execution_count": 138, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -669,19 +704,6 @@ "#ms_comp.style.format('{:.2}')\n", "ms_comp.loc[(ms_comp.mode_share_obs != 0) & (ms_comp.trip_mode != 0), [\"mode_share_obs\", \"trip_mode\"]].T" ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n", - "is_executing": true - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -689,803 +711,29 @@ } } }, - { - "cell_type": "markdown", - "id": "806ce752-0927-4d5b-a6cc-68d6c9b8a05e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:55:59.542669Z", - "iopub.status.busy": "2021-09-01T04:55:59.542360Z", - "iopub.status.idle": "2021-09-01T04:55:59.771722Z", - "shell.execute_reply": "2021-09-01T04:55:59.770801Z", - "shell.execute_reply.started": "2021-09-01T04:55:59.542615Z" - }, - "tags": [], - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "# OLD" - ] - }, { "cell_type": "code", - "execution_count": 454, - "id": "74eeedd3-4ade-4729-8170-79fa9dcf11f8", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:58:52.825042Z", - "iopub.status.busy": "2021-09-01T04:58:52.824768Z", - "iopub.status.idle": "2021-09-01T04:58:53.042884Z", - "shell.execute_reply": "2021-09-01T04:58:53.042061Z", - "shell.execute_reply.started": "2021-09-01T04:58:52.825004Z" - } - }, - "outputs": [], - "source": [ - "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - " nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec,\n", - " trace_label=trace_label)\n", - " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", - " return base_probabilities\n", - "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)\n", - "\n", - "\n", - "## caculate probabilities with Asim methodology, should be correct\n", - "def gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None):\n", - " full_probs = [] # analytical probs\n", - "\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " #choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " # segment_trace_label, log_alt_losers=log_alt_losers, \n", - " # trace_column_names=trace_column_names, seed=seed)\n", - " probs = simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, \n", - " estimator, segment_trace_label, log_alt_losers, \n", - " trace_column_names)\n", - " full_probs.append(probs)\n", - " probs_df = pd.concat(full_probs)\n", - " return probs_df\n" - ] - }, - { - "cell_type": "code", - "execution_count": 455, - "id": "295a4fd1-8e85-47e2-af82-9f1beac31d63", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:59:46.126555Z", - "iopub.status.busy": "2021-09-01T04:59:46.126262Z", - "iopub.status.idle": "2021-09-01T04:59:52.979908Z", - "shell.execute_reply": "2021-09-01T04:59:52.979043Z", - "shell.execute_reply.started": "2021-09-01T04:59:46.126522Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2021-09-01 14:59:46.353007 Start\n", - "2021-09-01 14:59:52.976736 End\n" - ] - } - ], - "source": [ - "print(f\"{datetime.now()} Start\")\n", - "pipeline.open_pipeline(resume_after)\n", - "inject.get_injectable('preload_injectables', None)\n", - "model_name = \"trip_mode_choice\"\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "\n", - "step_name = model_name\n", - "args = {}\n", - "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "chunk_size = 0\n", - "\n", - "trace_label = 'trip_mode_choice'\n", - "model_settings_file_name = 'trip_mode_choice.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - "mode_column_name = 'trip_mode'\n", - "trips_df = trips.to_frame()\n", - "#print(\"Running with %d trips\", trips_df.shape[0])\n", - "tours_merged = tours_merged.to_frame()\n", - "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "# - trips_merged - merge trips and tours_merged\n", - "trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - "assert trips_merged.index.equals(trips.index)\n", - "\n", - "# setup skim keys\n", - "assert ('trip_period' not in trips_merged)\n", - "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - "orig_col = 'origin'\n", - "dest_col = 'destination'\n", - "\n", - "constants = {}\n", - "constants.update(config.get_model_constants(model_settings))\n", - "constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - "})\n", - "\n", - "skim_dict = network_los.get_default_skim_dict()\n", - "\n", - "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "\n", - "skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - "}\n", - "\n", - "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - "nest_spec = config.get_logit_model_settings(model_settings)\n", - "\n", - "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - "\n", - "all_choices = []\n", - "for i in range(100):\n", - " if i % 10 == 0:\n", - " print(f\"{datetime.now()} iteration {i}\")\n", - " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=None)\n", - " all_choices.append(choices_df)\n", - "all_choices = pd.concat(all_choices, axis=1)\n", - "\n", - "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", - " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", - " log_alt_losers=None, trace_column_names=None)\n", - "\n", - "# update trips table with choices (and potionally logssums)\n", - "#trips_df = trips.to_frame()\n", - "#\n", - "#assign_in_place(trips_df, choices_df)\n", - "#assert not trips_df[mode_column_name].isnull().any()\n", - "\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - "print(f\"{datetime.now()} End\")" - ] - }, - { - "cell_type": "code", - "execution_count": 463, - "id": "dc87e904-16d1-44d4-81b7-cafbe4e223c3", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:01:40.901982Z", - "iopub.status.busy": "2021-09-01T05:01:40.901759Z", - "iopub.status.idle": "2021-09-01T05:01:41.337758Z", - "shell.execute_reply": "2021-09-01T05:01:41.336757Z", - "shell.execute_reply.started": "2021-09-01T05:01:40.901946Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#all_choices.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", - "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts = val_counts / all_choices.shape[1]\n", - "#val_counts = val_counts.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", - "#val_counts['prob_of_asim_choice'] = val_counts.apply(lambda x: x[x.asim], axis=1) # this is what our simulation says w.r.t. to asim choice\n", - "# for 100% and many samples should mostly agree" - ] - }, - { - "cell_type": "code", - "execution_count": 464, - "id": "09bf151c-a39a-4823-acfd-9c8ae747c338", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:01:46.119117Z", - "iopub.status.busy": "2021-09-01T05:01:46.118849Z", - "iopub.status.idle": "2021-09-01T05:01:46.354114Z", - "shell.execute_reply": "2021-09-01T05:01:46.353248Z", - "shell.execute_reply.started": "2021-09-01T05:01:46.119092Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#val_counts['prob_of_asim_choice'].hist(bins=100);" - ] - }, - { - "cell_type": "code", - "execution_count": 490, - "id": "f3b6ade9-d875-4104-b438-b53ef42e342c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:10:08.653227Z", - "iopub.status.busy": "2021-09-01T05:10:08.652978Z", - "iopub.status.idle": "2021-09-01T05:10:08.915620Z", - "shell.execute_reply": "2021-09-01T05:10:08.914513Z", - "shell.execute_reply.started": "2021-09-01T05:10:08.653189Z" - }, - "tags": [] - }, + "execution_count": 140, "outputs": [ { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", - "analytical 3.152 0.852 0.0 0.0 0.0 \n", - "experiment 3.243 0.880 0.0 0.0 0.0 \n", - "\n", - " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", - "analytical 0.0 0.0 0.0 0.685 0.0 \n", - "experiment 0.0 0.0 0.0 0.772 0.0 \n", - "\n", - " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK \\\n", - "analytical 0.129 0.0 0.182 0.260 1.334 63.708 \n", - "experiment 0.156 0.0 0.174 0.259 1.434 63.243 \n", - "\n", - " WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", - "analytical 0.0 0.0 0.455 18.355 10.887 \n", - "experiment 0.0 0.0 0.394 18.639 10.807 " - ] + "text/plain": " mode_share_obs trip_mode diff rel_diff\nBIKE 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVEALONEFREE 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVEALONEPAY 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_COM 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_EXP 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_HVY 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_LOC 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_LRF 0.000e+00 0.000e+00 0.000e+00 NaN\nSHARED2FREE 4.883e-03 4.885e-03 2.162e-06 4.428e-04\nSHARED2PAY 0.000e+00 0.000e+00 0.000e+00 NaN\nSHARED3FREE 4.701e-14 0.000e+00 -4.701e-14 -1.000e+00\nSHARED3PAY 0.000e+00 0.000e+00 0.000e+00 NaN\nTAXI 9.611e-05 9.500e-05 -1.108e-06 -1.152e-02\nTNC_SHARED 2.868e-04 2.910e-04 4.245e-06 1.480e-02\nTNC_SINGLE 1.308e-04 1.250e-04 -5.755e-06 -4.401e-02\nWALK 4.490e-02 4.463e-02 -2.769e-04 -6.166e-03\nWALK_COM 0.000e+00 0.000e+00 0.000e+00 NaN\nWALK_EXP 0.000e+00 0.000e+00 0.000e+00 NaN\nWALK_HVY 0.000e+00 0.000e+00 0.000e+00 NaN\nWALK_LOC 5.996e-02 6.031e-02 3.467e-04 5.782e-03\nWALK_LRF 8.897e-01 8.897e-01 -6.933e-05 -7.792e-05", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
mode_share_obstrip_modediffrel_diff
BIKE0.000e+000.000e+000.000e+00NaN
DRIVEALONEFREE0.000e+000.000e+000.000e+00NaN
DRIVEALONEPAY0.000e+000.000e+000.000e+00NaN
DRIVE_COM0.000e+000.000e+000.000e+00NaN
DRIVE_EXP0.000e+000.000e+000.000e+00NaN
DRIVE_HVY0.000e+000.000e+000.000e+00NaN
DRIVE_LOC0.000e+000.000e+000.000e+00NaN
DRIVE_LRF0.000e+000.000e+000.000e+00NaN
SHARED2FREE4.883e-034.885e-032.162e-064.428e-04
SHARED2PAY0.000e+000.000e+000.000e+00NaN
SHARED3FREE4.701e-140.000e+00-4.701e-14-1.000e+00
SHARED3PAY0.000e+000.000e+000.000e+00NaN
TAXI9.611e-059.500e-05-1.108e-06-1.152e-02
TNC_SHARED2.868e-042.910e-044.245e-061.480e-02
TNC_SINGLE1.308e-041.250e-04-5.755e-06-4.401e-02
WALK4.490e-024.463e-02-2.769e-04-6.166e-03
WALK_COM0.000e+000.000e+000.000e+00NaN
WALK_EXP0.000e+000.000e+000.000e+00NaN
WALK_HVY0.000e+000.000e+000.000e+00NaN
WALK_LOC5.996e-026.031e-023.467e-045.782e-03
WALK_LRF8.897e-018.897e-01-6.933e-05-7.792e-05
\n
" }, "metadata": {}, "output_type": "display_data" } ], "source": [ - "mode_share_analytical = (probs_nl.sum(axis=0) / probs_nl.shape[0]).to_frame('analytical')\n", - "assert np.allclose(mode_share_analytical.sum(), 1)\n", - "mode_share_rum = (val_counts.sum(axis=0) / val_counts.shape[0]).to_frame('experiment')\n", - "assert np.allclose(mode_share_rum.sum(), 1)\n", - "full_share = mode_share_analytical.join(mode_share_rum, how='outer').fillna(0)\n", "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * full_share).T)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e48a56e-d613-4dfd-bef6-62bee524f12f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50914e1d-750d-440c-9617-f96bd2a46c56", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f2d70e0e-520e-49a6-8024-ae31345f6ead", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 631, - "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:32.250808Z", - "iopub.status.busy": "2021-09-01T11:37:32.250548Z", - "iopub.status.idle": "2021-09-01T20:20:35.257463Z", - "shell.execute_reply": "2021-09-01T20:20:35.255964Z", - "shell.execute_reply.started": "2021-09-01T11:37:32.250782Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "num_samples = 10\n", - "\n", - "rng_test = default_rng(23423)\n", - "\n", - "print(f\"{datetime.now()} Start\")\n", - "pipeline.open_pipeline(resume_after)\n", - "inject.get_injectable('preload_injectables', None)\n", - "model_name = \"trip_mode_choice\"\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "\n", - "step_name = model_name\n", - "args = {}\n", - "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "chunk_size = 0\n", - "\n", - "trace_label = 'trip_mode_choice'\n", - "model_settings_file_name = 'trip_mode_choice.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - "mode_column_name = 'trip_mode'\n", - "trips_df = trips.to_frame()\n", - "#print(\"Running with %d trips\", trips_df.shape[0])\n", - "tours_merged = tours_merged.to_frame()\n", - "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "# - trips_merged - merge trips and tours_merged\n", - "trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - "assert trips_merged.index.equals(trips.index)\n", - "\n", - "# setup skim keys\n", - "assert ('trip_period' not in trips_merged)\n", - "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - "orig_col = 'origin'\n", - "dest_col = 'destination'\n", - "\n", - "constants = {}\n", - "constants.update(config.get_model_constants(model_settings))\n", - "constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - "})\n", - "\n", - "skim_dict = network_los.get_default_skim_dict()\n", - "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - "}\n", - "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - "nest_spec = config.get_logit_model_settings(model_settings)\n", - "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - "all_choices_zenith = []\n", - "all_choices = []\n", - "for i in range(num_samples):\n", - " \n", - " seed = rng_test.integers(0, 100000) #int(9.3 * (i+1)**3) # why not\n", - " \n", - " if i % 50 == 0:\n", - " print(f\"{datetime.now()} iteration {i}\")\n", - " choices_df_zenith = stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", - " all_choices_zenith.append(choices_df_zenith)\n", - "\n", - " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", - " all_choices.append(choices_df)\n", - " \n", - " t_ = choices_df_zenith.merge(choices_df, left_index=True, right_index=True, suffixes=['_zenith', '_asim'])\n", - " diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", - " \n", - " #print(f\"seed {seed} leads to {diffs.shape[0]} differences. tripids {diffs.index}\")\n", - "\n", - "all_choices_zenith = pd.concat(all_choices_zenith, axis=1)\n", - "all_choices = pd.concat(all_choices, axis=1)\n", - "\n", - "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", - " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", - " log_alt_losers=None, trace_column_names=None)\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - "print(f\"{datetime.now()} End\")" - ] - }, - { - "cell_type": "code", - "execution_count": 643, - "id": "3abd7949-dc9c-469b-845e-26c17741f70e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:33:21.533110Z", - "iopub.status.busy": "2021-09-01T20:33:21.531915Z", - "iopub.status.idle": "2021-09-01T20:33:21.740946Z", - "shell.execute_reply": "2021-09-01T20:33:21.739713Z", - "shell.execute_reply.started": "2021-09-01T20:33:21.533074Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Have 2038 samples\n" - ] - } + " display(ms_comp)" ], - "source": [ - "print(f\"Have {all_choices_zenith.shape[1]} samples\")" - ] - }, - { - "cell_type": "code", - "execution_count": 633, - "id": "8762bd6d-ac04-458c-869f-be86d8297351", "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:21:27.778803Z", - "iopub.status.busy": "2021-09-01T20:21:27.778531Z", - "iopub.status.idle": "2021-09-01T20:21:28.675645Z", - "shell.execute_reply": "2021-09-01T20:21:28.674734Z", - "shell.execute_reply.started": "2021-09-01T20:21:27.778754Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "val_counts_zenith = all_choices_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts_zenith = val_counts_zenith / all_choices_zenith.shape[1]\n", - "\n", - "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts = val_counts / all_choices.shape[1]" - ] - }, - { - "cell_type": "code", - "execution_count": 634, - "id": "0f566105-9424-457b-86f5-d60c1d63aac5", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:21:30.954894Z", - "iopub.status.busy": "2021-09-01T20:21:30.954654Z", - "iopub.status.idle": "2021-09-01T20:21:31.179689Z", - "shell.execute_reply": "2021-09-01T20:21:31.178304Z", - "shell.execute_reply.started": "2021-09-01T20:21:30.954867Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
experiment_zenith3.1500.8740.00.00.00.00.00.00.6800.00.1360.00.1540.2361.28563.7670.00.00.51518.82410.379
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", - "analytical 3.152 0.852 0.0 0.0 0.0 \n", - "experiment 3.243 0.880 0.0 0.0 0.0 \n", - "experiment_zenith 3.150 0.874 0.0 0.0 0.0 \n", - "\n", - " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", - "analytical 0.0 0.0 0.0 0.685 0.0 \n", - "experiment 0.0 0.0 0.0 0.772 0.0 \n", - "experiment_zenith 0.0 0.0 0.0 0.680 0.0 \n", - "\n", - " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE \\\n", - "analytical 0.129 0.0 0.182 0.260 1.334 \n", - "experiment 0.156 0.0 0.174 0.259 1.434 \n", - "experiment_zenith 0.136 0.0 0.154 0.236 1.285 \n", - "\n", - " WALK WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", - "analytical 63.708 0.0 0.0 0.455 18.355 10.887 \n", - "experiment 63.243 0.0 0.0 0.394 18.639 10.807 \n", - "experiment_zenith 63.767 0.0 0.0 0.515 18.824 10.379 " - ] - }, - "metadata": {}, - "output_type": "display_data" + "collapsed": false, + "pycharm": { + "name": "#%%\n" } - ], - "source": [ - "mode_share_rum_zenith = (val_counts_zenith.sum(axis=0) / val_counts_zenith.shape[0]).to_frame('experiment_zenith')\n", - "assert np.allclose(mode_share_rum_zenith.sum(), 1)\n", - "full_share_incl_zenith = full_share.merge(mode_share_rum_zenith, left_index=True, right_index=True, how='outer').fillna(0)\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * full_share_incl_zenith).T)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ef84f10-d616-40e4-87e5-425499027ff3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "431cee49-40a9-4dc7-93a5-75308fb302dd", - "metadata": {}, - "outputs": [], - "source": [] + } }, { "cell_type": "code", From fd5ca14771973a4929d4116be0c752f98e7b2214 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 5 May 2022 21:18:50 +1000 Subject: [PATCH 011/298] fixes scale of nest utilities --- activitysim/core/logit.py | 4 +- activitysim/core/simulate.py | 18 +- notebooks/validate_frozen_impl.ipynb | 625 +++++++++++++++++++++++---- 3 files changed, 552 insertions(+), 95 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index a2b732c52a..8f432e5c1e 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -475,8 +475,8 @@ def _each_nest(spec, parent_nest, post_order): nest.coefficient = parent_nest.coefficient # this is used for frozen individual ru - see discussion in simulate.py l.740 - nest.parent_nest_scale = parent_nest.product_of_coefficients - #nest.parent_nest_scale = parent_nest.coefficient + #nest.parent_nest_scale = parent_nest.product_of_coefficients + nest.parent_nest_scale = parent_nest.coefficient yield spec, nest diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 27ed38e5fd..289ba90d92 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -736,18 +736,20 @@ def compute_nested_utilities(raw_utilities, nest_spec): for nest in logit.each_nest(nest_spec, post_order=True): name = nest.name if nest.is_leaf: - nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients - #nested_utilities[name] = raw_utilities[name].astype(float) / nest.coefficient - # WHY does asim use product of coeffs here? the above gives us identical root logsums - # but then we need to dscale ev1 accordingly for alternatives, right? - # can be achieved by + #nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients + nested_utilities[name] = raw_utilities[name].astype(float) / nest.coefficient + # WHY does asim use product of coeffs here? using that gives us identical root logsums + # but then we need to scale ev1 accordingly for alternatives, right? else: # the alternative nested_utilities will already have been computed due to post_order - # this will RuntimeWarning: divide by zero encountered in log - # if all nest alternative utilities are zero and produce -inf + if nest.level == 1: # FIXME: work out the parent scale mess + scale = 1.0 + else: + scale = nest.parent_nest_scale + with np.errstate(divide='ignore'): nested_utilities[name] = \ - nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) + nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / scale).sum(axis=1)) return nested_utilities diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 95127ec5c1..f68019d396 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -170,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 188, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -219,8 +219,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Done\n", - "Running with %d trips 482\n" + "Done\n" ] }, { @@ -234,16 +233,306 @@ "name": "stdout", "output_type": "stream", "text": [ - "trip_mode_choice tour_type 'atwork' (27 trips)\n", - "trip_mode_choice tour_type 'eatout' (33 trips)\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n", - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", - "trip_mode_choice tour_type 'othmaint' (46 trips)\n", - "trip_mode_choice tour_type 'school' (37 trips)\n", - "trip_mode_choice tour_type 'shopping' (77 trips)\n", - "trip_mode_choice tour_type 'social' (19 trips)\n", - "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n", + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'atwork' (27 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'eatout' (33 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'escort' (6 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'othmaint' (46 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'school' (37 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'shopping' (77 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'social' (19 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'univ' (26 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'work' (168 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "Done\n" ] } @@ -256,14 +545,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 189, "outputs": [ { "data": { - "text/plain": " trip_mode_fru trip_mode_asim\nWALK 299 304\nWALK_LOC 92 85\nWALK_LRF 61 53\nBIKE 17 17\nDRIVEALONEFREE 6 4\nSHARED2FREE 3 4\nTNC_SINGLE 2 4\nWALK_HVY 2 4", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK299304
WALK_LOC9285
WALK_LRF6153
BIKE1717
DRIVEALONEFREE64
SHARED2FREE34
TNC_SINGLE24
WALK_HVY24
\n
" + "text/plain": " trip_mode_fru trip_mode_asim\nWALK 299 304\nWALK_LOC 94 85\nWALK_LRF 57 53\nBIKE 17 17\nDRIVEALONEFREE 6 4\nWALK_HVY 4 4\nSHARED2FREE 3 4\nTNC_SINGLE 2 4", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK299304
WALK_LOC9485
WALK_LRF5753
BIKE1717
DRIVEALONEFREE64
WALK_HVY44
SHARED2FREE34
TNC_SINGLE24
\n
" }, - "execution_count": 9, + "execution_count": 189, "metadata": {}, "output_type": "execute_result" } @@ -283,7 +572,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 190, "outputs": [ { "name": "stdout", @@ -308,21 +597,21 @@ }, { "cell_type": "code", - "execution_count": 10, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" + "execution_count": 187, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n8805121 -5709.149390 -8563.435105 -inf -2856.962423 \n8805125 -5710.236852 -8564.522566 -inf -2857.805670 \n8805126 -5709.506304 -8563.792018 -inf -2857.185402 \n468119921 -2856.413143 -5710.698857 -inf -2855.880864 \n468119922 -2856.976497 -5711.262211 -inf -2856.444428 \n468119925 -2870.186517 -5724.472231 -inf -2863.993439 \n468119926 -2857.935513 -5712.221227 -inf -2856.918910 \n468119927 -2859.140866 -5713.426580 -inf -2857.537407 \n468119928 -2869.283122 -5723.568836 -inf -2863.287255 \n642446345 -5709.613741 -8563.899455 -inf -10.654154 \n642446349 -5711.108042 -8565.393757 -inf -11.507365 \n1179608009 -8563.419303 -11417.705017 -inf -17.616599 \n1179608013 -8571.196804 -11425.482519 -inf -22.061100 \n1658807153 -5709.137752 -8563.423466 -inf -19.719413 \n1658807157 -5710.005638 -8564.291352 -inf -20.220421 \n1767182945 -5709.423410 -8563.709124 -inf -10.469202 \n1767182949 -5709.668094 -8563.953808 -inf -10.618407 \n2464104881 -5709.489682 -8563.775396 -inf -12.625725 \n2464104885 -5709.489682 -8563.775396 -inf -12.625725 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n8805121 -5711.248137 -inf -2856.962379 -5711.248093 -inf \n8805125 -5712.091384 -inf -2857.707940 -5711.993654 -inf \n8805126 -5711.471116 -inf -2857.131784 -5711.417498 -inf \n468119921 -5710.166578 -inf -2855.667952 -5709.953666 -inf \n468119922 -5710.730142 -inf -2856.231600 -5710.517314 -inf \n468119925 -5718.279154 -inf -2861.516208 -5715.801922 -inf \n468119926 -5711.204625 -inf -2856.512269 -5710.797984 -inf \n468119927 -5711.823121 -inf -2856.896023 -5711.181738 -inf \n468119928 -5717.572969 -inf -2860.888909 -5715.174623 -inf \n642446345 -2864.939868 -5.179103 -62.836260 -2917.121974 -30.545404 \n642446349 -2865.793079 -5.593858 -63.433034 -2917.718749 -30.835503 \n1179608009 -2871.902313 -8.563624 -19.761289 -2874.047003 -9.606182 \n1179608013 -2876.346814 -10.724146 -22.872589 -2877.158304 -11.118620 \n1658807153 -2874.005127 -9.585826 -21.864019 -2876.149734 -10.628343 \n1658807157 -2874.506135 -9.829371 -22.218276 -2876.503991 -10.800551 \n1767182945 -2864.754916 -5.089195 -62.653459 -2916.939174 -30.456543 \n1767182949 -2864.904122 -5.161726 -62.764474 -2917.050188 -30.510508 \n2464104881 -2866.911439 -6.137505 -64.806540 -2919.092254 -31.503179 \n2464104885 -2866.911439 -6.137505 -64.806540 -2919.092254 -31.503179 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n8805121 -inf 12.460532 -1375.039468 8.971583 -1980.409272 \n8805125 -inf 12.557754 -1374.990857 9.041583 -3978.191234 \n8805126 -inf 12.207754 -1375.165857 8.789583 -1980.441864 \n468119921 -inf 10.647096 -1376.595265 7.665909 -1984.028282 \n468119922 -inf -0.158141 -1387.385919 -0.113862 -2003.585155 \n468119925 -inf -0.158141 -1387.385919 -0.113862 -2003.585155 \n468119926 -inf 10.734596 -1376.551515 7.728909 -1987.731834 \n468119927 -inf 10.765788 -1376.855740 7.751367 -1987.818125 \n468119928 -inf 11.707060 -1375.408912 8.429083 -1985.936364 \n642446345 -3.728954 -2.097408 -1387.343797 -1.510134 1.093980 \n642446349 -4.027578 -2.146019 -1387.353519 -1.545134 1.192207 \n1179608009 -5.948375 4.880186 -1378.113147 3.513734 -1984.050332 \n1179608013 -7.350415 4.880186 -1378.113147 3.513734 -1984.050332 \n1658807153 -6.684352 6.456402 -1376.357071 4.648609 15.268960 \n1658807157 -6.845959 6.407790 -1376.381376 4.613609 14.530278 \n1767182945 -3.664221 -3.606195 -1388.969251 -2.596460 -0.555751 \n1767182949 -3.716443 -3.776334 -1389.003278 -2.718960 -0.579012 \n2464104881 -4.419004 8.134630 -1378.288147 5.856934 10.502444 \n2464104885 -4.419004 8.134630 -1378.288147 5.856934 10.666689 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n8805121 -3978.409272 -3978.409272 -3978.409272 -3978.409272 -inf \n8805125 -3978.191234 -3978.191234 -3978.191234 -3978.191234 -inf \n8805126 -3978.441864 -3978.791864 -3978.441864 -3978.441864 -inf \n468119921 -3982.028282 -3982.028282 -3982.028282 -3982.028282 -inf \n468119922 -4001.585155 -4001.935155 -4001.585155 -4001.585155 -inf \n468119925 -4001.585155 -4001.585155 -4001.585155 -4001.585155 -inf \n468119926 -3985.731834 -3986.081834 -3985.731834 -3985.731834 -inf \n468119927 -3985.818125 -3986.168125 -3985.818125 -3985.818125 -inf \n468119928 -3983.936364 -3984.286364 -3983.936364 -3983.936364 -inf \n642446345 3.035970 -1995.576420 -1995.597276 -1995.880220 2.140923 \n642446349 3.035270 -1994.736869 -1995.597276 -1995.040669 2.145065 \n1179608009 -1984.050332 -1984.050332 -1984.050332 -1984.050332 -inf \n1179608013 -1984.050332 -1984.050332 -1984.050332 -1984.050332 -inf \n1658807153 -1982.731040 -1982.731040 -1982.731040 -1982.731040 10.603444 \n1658807157 -1983.469722 -1983.469722 -1983.469722 -1983.469722 10.090471 \n1767182945 0.665917 -1997.106222 -1997.397020 -1997.410022 0.546586 \n1767182949 0.665917 -1997.106222 -1997.408920 -1997.410022 0.544118 \n2464104881 -1986.419156 -1986.167956 -1986.416956 -1986.471756 7.293364 \n2464104885 -1986.254911 -1986.003711 -1986.252711 -1986.307511 7.407423 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n8805121 -5976.701052 -5979.008301 -5979.008301 -5979.622112 -5980.000155 \n8805125 -5976.250034 -5976.250034 -5976.250034 -5976.250034 -5976.250034 \n8805126 -5976.561059 -5980.066360 -5980.066360 -5977.489912 -5981.139291 \n468119921 -1987.917275 -3986.634667 -3986.634667 -3988.159653 -3989.540253 \n468119922 -1999.781073 -4002.350822 -4002.350822 -4003.314328 -4005.292790 \n468119925 -1999.856900 -4003.785211 -4003.785211 -4001.364128 -4006.015621 \n468119926 -1987.922393 -3991.085771 -3991.085771 -3985.679774 -3991.883814 \n468119927 -1984.330401 -3988.035744 -3988.035744 -3985.888572 -3992.028453 \n468119928 -1986.084720 -3989.542402 -3989.542402 -3984.015305 -3990.339045 \n642446345 -5993.059741 -5996.236057 -5996.236057 -5996.847988 -5998.040211 \n642446349 -5993.114899 -5996.745065 -5996.745065 -5994.750788 -5998.886171 \n1179608009 -7978.109132 -7978.109132 -7978.109132 -7978.109132 -7978.109132 \n1179608013 -7978.109132 -7978.109132 -7978.109132 -7978.109132 -7978.109132 \n1658807153 -5978.797090 -5981.645926 -5981.645926 -5982.436880 -5983.088374 \n1658807157 -5979.569178 -5984.301635 -5984.301635 -5980.452579 -5984.874558 \n1767182945 -5994.656309 -5997.269370 -5997.269370 -5997.769890 -5998.629581 \n1767182949 -5994.664265 -5997.497589 -5997.497589 -5995.576790 -5998.540328 \n2464104881 -5983.483693 -5989.023319 -5989.023319 -5991.399546 -5994.064540 \n2464104885 -5983.469343 -5991.354620 -5991.354620 -5989.123846 -5997.222704 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED \\\ntrip_id \n8805121 -inf -inf -20.081973 -19.473153 -20.804324 \n8805125 -inf -inf -20.047538 -19.456673 -20.779823 \n8805126 -inf -inf -20.169318 -19.513014 -20.864116 \n468119921 -inf -inf -26.859498 -30.543501 -25.552660 \n468119922 -inf -inf -26.820071 -30.516140 -25.512174 \n468119925 -inf -inf -26.824219 -30.517112 -25.513632 \n468119926 -inf -inf -26.316039 -30.492180 -25.480679 \n468119927 -inf -inf -24.418379 -30.418607 -25.374051 \n468119928 -inf -inf -27.800562 -30.563790 -25.697316 \n642446345 -inf 1.541464 -18.289778 -17.981922 -17.196617 \n642446349 -inf 1.544447 -18.324954 -17.976575 -17.192002 \n1179608009 -inf -inf -8.526813 0.047647 -2.273228 \n1179608013 -inf -inf -8.527679 0.047161 -2.273957 \n1658807153 -inf 7.634480 -8.591632 0.206340 -2.227894 \n1658807157 -inf 7.265139 -8.647127 0.198076 -2.240290 \n1767182945 -inf 0.393542 -14.395026 -13.588217 -14.958046 \n1767182949 -inf 0.391765 -14.435998 -13.608147 -14.995707 \n2464104881 -inf 5.251222 -24.155944 -33.809528 -23.204042 \n2464104885 -inf 5.333344 -24.155944 -33.809528 -23.204042 \n\n RIDEHAIL root \ntrip_id \n8805121 -6.797103 8.971583 \n8805125 -6.788793 9.041583 \n8805126 -6.817566 8.789583 \n468119921 -9.110799 7.665909 \n468119922 -9.096330 -0.113736 \n468119925 -9.097058 -0.113736 \n468119926 -9.041678 7.728909 \n468119927 -8.672837 7.751368 \n468119928 -9.207112 8.429083 \n642446345 -5.980956 1.593075 \n642446349 -5.981865 1.593106 \n1179608009 0.050933 3.544670 \n1179608013 0.050750 3.544608 \n1658807153 0.104584 7.684253 \n1658807157 0.101487 7.334029 \n1767182945 -4.700641 0.464659 \n1767182949 -4.710741 0.456698 \n2464104881 -8.235935 6.292424 \n2464104885 -8.235935 6.322192 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
8805121-5709.149390-8563.435105-inf-2856.962423-5711.248137-inf-2856.962379-5711.248093-inf-inf12.460532-1375.0394688.971583-1980.409272-3978.409272-3978.409272-3978.409272-3978.409272-inf-5976.701052-5979.008301-5979.008301-5979.622112-5980.000155-inf-inf-20.081973-19.473153-20.804324-6.7971038.971583
8805125-5710.236852-8564.522566-inf-2857.805670-5712.091384-inf-2857.707940-5711.993654-inf-inf12.557754-1374.9908579.041583-3978.191234-3978.191234-3978.191234-3978.191234-3978.191234-inf-5976.250034-5976.250034-5976.250034-5976.250034-5976.250034-inf-inf-20.047538-19.456673-20.779823-6.7887939.041583
8805126-5709.506304-8563.792018-inf-2857.185402-5711.471116-inf-2857.131784-5711.417498-inf-inf12.207754-1375.1658578.789583-1980.441864-3978.441864-3978.791864-3978.441864-3978.441864-inf-5976.561059-5980.066360-5980.066360-5977.489912-5981.139291-inf-inf-20.169318-19.513014-20.864116-6.8175668.789583
468119921-2856.413143-5710.698857-inf-2855.880864-5710.166578-inf-2855.667952-5709.953666-inf-inf10.647096-1376.5952657.665909-1984.028282-3982.028282-3982.028282-3982.028282-3982.028282-inf-1987.917275-3986.634667-3986.634667-3988.159653-3989.540253-inf-inf-26.859498-30.543501-25.552660-9.1107997.665909
468119922-2856.976497-5711.262211-inf-2856.444428-5710.730142-inf-2856.231600-5710.517314-inf-inf-0.158141-1387.385919-0.113862-2003.585155-4001.585155-4001.935155-4001.585155-4001.585155-inf-1999.781073-4002.350822-4002.350822-4003.314328-4005.292790-inf-inf-26.820071-30.516140-25.512174-9.096330-0.113736
468119925-2870.186517-5724.472231-inf-2863.993439-5718.279154-inf-2861.516208-5715.801922-inf-inf-0.158141-1387.385919-0.113862-2003.585155-4001.585155-4001.585155-4001.585155-4001.585155-inf-1999.856900-4003.785211-4003.785211-4001.364128-4006.015621-inf-inf-26.824219-30.517112-25.513632-9.097058-0.113736
468119926-2857.935513-5712.221227-inf-2856.918910-5711.204625-inf-2856.512269-5710.797984-inf-inf10.734596-1376.5515157.728909-1987.731834-3985.731834-3986.081834-3985.731834-3985.731834-inf-1987.922393-3991.085771-3991.085771-3985.679774-3991.883814-inf-inf-26.316039-30.492180-25.480679-9.0416787.728909
468119927-2859.140866-5713.426580-inf-2857.537407-5711.823121-inf-2856.896023-5711.181738-inf-inf10.765788-1376.8557407.751367-1987.818125-3985.818125-3986.168125-3985.818125-3985.818125-inf-1984.330401-3988.035744-3988.035744-3985.888572-3992.028453-inf-inf-24.418379-30.418607-25.374051-8.6728377.751368
468119928-2869.283122-5723.568836-inf-2863.287255-5717.572969-inf-2860.888909-5715.174623-inf-inf11.707060-1375.4089128.429083-1985.936364-3983.936364-3984.286364-3983.936364-3983.936364-inf-1986.084720-3989.542402-3989.542402-3984.015305-3990.339045-inf-inf-27.800562-30.563790-25.697316-9.2071128.429083
642446345-5709.613741-8563.899455-inf-10.654154-2864.939868-5.179103-62.836260-2917.121974-30.545404-3.728954-2.097408-1387.343797-1.5101341.0939803.035970-1995.576420-1995.597276-1995.8802202.140923-5993.059741-5996.236057-5996.236057-5996.847988-5998.040211-inf1.541464-18.289778-17.981922-17.196617-5.9809561.593075
642446349-5711.108042-8565.393757-inf-11.507365-2865.793079-5.593858-63.433034-2917.718749-30.835503-4.027578-2.146019-1387.353519-1.5451341.1922073.035270-1994.736869-1995.597276-1995.0406692.145065-5993.114899-5996.745065-5996.745065-5994.750788-5998.886171-inf1.544447-18.324954-17.976575-17.192002-5.9818651.593106
1179608009-8563.419303-11417.705017-inf-17.616599-2871.902313-8.563624-19.761289-2874.047003-9.606182-5.9483754.880186-1378.1131473.513734-1984.050332-1984.050332-1984.050332-1984.050332-1984.050332-inf-7978.109132-7978.109132-7978.109132-7978.109132-7978.109132-inf-inf-8.5268130.047647-2.2732280.0509333.544670
1179608013-8571.196804-11425.482519-inf-22.061100-2876.346814-10.724146-22.872589-2877.158304-11.118620-7.3504154.880186-1378.1131473.513734-1984.050332-1984.050332-1984.050332-1984.050332-1984.050332-inf-7978.109132-7978.109132-7978.109132-7978.109132-7978.109132-inf-inf-8.5276790.047161-2.2739570.0507503.544608
1658807153-5709.137752-8563.423466-inf-19.719413-2874.005127-9.585826-21.864019-2876.149734-10.628343-6.6843526.456402-1376.3570714.64860915.268960-1982.731040-1982.731040-1982.731040-1982.73104010.603444-5978.797090-5981.645926-5981.645926-5982.436880-5983.088374-inf7.634480-8.5916320.206340-2.2278940.1045847.684253
1658807157-5710.005638-8564.291352-inf-20.220421-2874.506135-9.829371-22.218276-2876.503991-10.800551-6.8459596.407790-1376.3813764.61360914.530278-1983.469722-1983.469722-1983.469722-1983.46972210.090471-5979.569178-5984.301635-5984.301635-5980.452579-5984.874558-inf7.265139-8.6471270.198076-2.2402900.1014877.334029
1767182945-5709.423410-8563.709124-inf-10.469202-2864.754916-5.089195-62.653459-2916.939174-30.456543-3.664221-3.606195-1388.969251-2.596460-0.5557510.665917-1997.106222-1997.397020-1997.4100220.546586-5994.656309-5997.269370-5997.269370-5997.769890-5998.629581-inf0.393542-14.395026-13.588217-14.958046-4.7006410.464659
1767182949-5709.668094-8563.953808-inf-10.618407-2864.904122-5.161726-62.764474-2917.050188-30.510508-3.716443-3.776334-1389.003278-2.718960-0.5790120.665917-1997.106222-1997.408920-1997.4100220.544118-5994.664265-5997.497589-5997.497589-5995.576790-5998.540328-inf0.391765-14.435998-13.608147-14.995707-4.7107410.456698
2464104881-5709.489682-8563.775396-inf-12.625725-2866.911439-6.137505-64.806540-2919.092254-31.503179-4.4190048.134630-1378.2881475.85693410.502444-1986.419156-1986.167956-1986.416956-1986.4717567.293364-5983.483693-5989.023319-5989.023319-5991.399546-5994.064540-inf5.251222-24.155944-33.809528-23.204042-8.2359356.292424
2464104885-5709.489682-8563.775396-inf-12.625725-2866.911439-6.137505-64.806540-2919.092254-31.503179-4.4190048.134630-1378.2881475.85693410.666689-1986.254911-1986.003711-1986.252711-1986.3075117.407423-5983.469343-5991.354620-5991.354620-5989.123846-5997.222704-inf5.333344-24.155944-33.809528-23.204042-8.2359356.322192
\n
" + }, + "execution_count": 187, + "metadata": {}, + "output_type": "execute_result" } - } - }, - { - "cell_type": "code", - "execution_count": 10, - "outputs": [], - "source": [], + ], + "source": [ + "simulate.compute_nested_utilities(ru[0], ns[0])" + ], "metadata": { "collapsed": false, "pycharm": { @@ -349,7 +638,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 192, "outputs": [ { "name": "stderr", @@ -364,7 +653,36 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (19 trips)\n", + "trip_mode_choice tour_type 'social' (19 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", + "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "Done\n" ] } @@ -382,14 +700,140 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 194, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root'] parent nest scale: 0\n", + "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] parent nest scale: 1.0\n", + "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] parent nest scale: 0.72\n", + "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2'] parent nest scale: 0.72\n", + "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3'] parent nest scale: 0.72\n", + "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED'] parent nest scale: 1.0\n", + "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT'] parent nest scale: 1.0\n", + "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS'] parent nest scale: 0.72\n", + "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS'] parent nest scale: 0.72\n", + "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL'] parent nest scale: 1.0\n" + ] + } + ], + "source": [ + "nest_spec = ns[0]\n", + "for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", + " nest.print()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 195, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "leaf DRIVEALONEFREE with 0.252, 0.35\n", + "leaf DRIVEALONEPAY with 0.252, 0.35\n", + "node DRIVEALONE with 0.35, 0.72\n", + "leaf SHARED2FREE with 0.252, 0.35\n", + "leaf SHARED2PAY with 0.252, 0.35\n", + "node SHAREDRIDE2 with 0.35, 0.72\n", + "leaf SHARED3FREE with 0.252, 0.35\n", + "leaf SHARED3PAY with 0.252, 0.35\n", + "node SHAREDRIDE3 with 0.35, 0.72\n", + "node AUTO with 0.72, 1.0\n", + "leaf WALK with 0.72, 0.72\n", + "leaf BIKE with 0.72, 0.72\n", + "node NONMOTORIZED with 0.72, 1.0\n", + "leaf WALK_LOC with 0.36, 0.5\n", + "leaf WALK_LRF with 0.36, 0.5\n", + "leaf WALK_EXP with 0.36, 0.5\n", + "leaf WALK_HVY with 0.36, 0.5\n", + "leaf WALK_COM with 0.36, 0.5\n", + "node WALKACCESS with 0.5, 0.72\n", + "leaf DRIVE_LOC with 0.36, 0.5\n", + "leaf DRIVE_LRF with 0.36, 0.5\n", + "leaf DRIVE_EXP with 0.36, 0.5\n", + "leaf DRIVE_HVY with 0.36, 0.5\n", + "leaf DRIVE_COM with 0.36, 0.5\n", + "node DRIVEACCESS with 0.5, 0.72\n", + "node TRANSIT with 0.72, 1.0\n", + "leaf TAXI with 0.36, 0.36\n", + "leaf TNC_SINGLE with 0.36, 0.36\n", + "leaf TNC_SHARED with 0.36, 0.36\n", + "node RIDEHAIL with 0.36, 1.0\n", + "node root with 1.0, 0\n" + ] + } + ], + "source": [ + "for nest in logit.each_nest(nest_spec, post_order=True):\n", + " name = nest.name\n", + " if nest.is_leaf:\n", + " print(f\"leaf {name} with {nest.product_of_coefficients}, {nest.parent_nest_scale}\")\n", + " else:\n", + " print(f\"node {name} with {nest.coefficient}, {nest.parent_nest_scale}\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 196, "outputs": [ { "data": { "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" }, - "execution_count": 12, + "execution_count": 196, "metadata": {}, "output_type": "execute_result" } @@ -434,7 +878,7 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 197, "outputs": [], "source": [ "def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples):\n", @@ -608,21 +1052,25 @@ " pipeline.close_pipeline()\n", "\n", " print(\"Done\")\n", - " return trips_merged, choices" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 84, - "outputs": [], - "source": [ - "pipeline.close_pipeline()" + " return trips_merged, choices\n", + "\n", + "\n", + "def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check):\n", + " t_, c_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility,\n", + " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", + "\n", + " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", + " #sim_mode_shares.columns = [\"mode_share_sim\"]\n", + " obs_probs = base_probs[0].loc[base_probs[0].index == trip_id_to_check].T\n", + " obs_probs.columns = [\"mode_share_obs\"]\n", + " ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how=\"outer\").fillna(0)\n", + " ms_comp[\"diff\"] = ms_comp[\"trip_mode\"] - ms_comp[\"mode_share_obs\"]\n", + " ms_comp[\"rel_diff\"] = ms_comp[\"diff\"] / ms_comp[\"mode_share_obs\"]\n", + " #ms_comp.style.format('{:.2}')\n", + " #with pd.option_context(\"precision\", 3):\n", + " # display(ms_comp)\n", + " display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)\n", + " return ms_comp" ], "metadata": { "collapsed": false, @@ -633,7 +1081,7 @@ }, { "cell_type": "code", - "execution_count": 138, + "execution_count": 198, "outputs": [ { "name": "stderr", @@ -648,7 +1096,7 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (1000000 trips)\n" + "trip_mode_choice tour_type 'social' (100000 trips)\n" ] }, { @@ -679,30 +1127,18 @@ }, { "data": { - "text/plain": " SHARED2FREE TAXI TNC_SHARED TNC_SINGLE WALK \\\nmode_share_obs 0.004883 0.000096 0.000287 0.000131 0.044905 \ntrip_mode 0.004885 0.000095 0.000291 0.000125 0.044628 \n\n WALK_LOC WALK_LRF \nmode_share_obs 0.059962 0.889736 \ntrip_mode 0.060309 0.889667 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048830.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0048850.0000950.0002910.0001250.0446280.0603090.889667
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.005210 0.000000e+00 0.000130 0.000280 0.000100 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.045410 0.061630 0.887240 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0052100.000000e+000.0001300.0002800.0001000.0454100.0616300.887240
\n
" }, - "execution_count": 138, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ "choose_individual_max_utility = False\n", - "num_samples = 1000000\n", + "num_samples = 100000\n", "trip_id_to_check = 642446345\n", - "t_, c_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility,\n", - " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", - "\n", - "sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", - "#sim_mode_shares.columns = [\"mode_share_sim\"]\n", - "obs_probs = base_probs[0].loc[base_probs[0].index == trip_id_to_check].T\n", - "obs_probs.columns = [\"mode_share_obs\"]\n", - "ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how=\"outer\").fillna(0)\n", - "ms_comp[\"diff\"] = ms_comp[\"trip_mode\"] - ms_comp[\"mode_share_obs\"]\n", - "ms_comp[\"rel_diff\"] = ms_comp[\"diff\"] / ms_comp[\"mode_share_obs\"]\n", - "#ms_comp.style.format('{:.2}')\n", - "ms_comp.loc[(ms_comp.mode_share_obs != 0) & (ms_comp.trip_mode != 0), [\"mode_share_obs\", \"trip_mode\"]].T" + "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" ], "metadata": { "collapsed": false, @@ -713,35 +1149,54 @@ }, { "cell_type": "code", - "execution_count": 140, + "execution_count": 199, + "id": "1fcdc0ff-c510-4878-8672-8365e046442c", + "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running with %d trips 482\n", + "trip_mode_choice tour_type 'social' (100000 trips)\n", + "Done\n" + ] + }, { "data": { - "text/plain": " mode_share_obs trip_mode diff rel_diff\nBIKE 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVEALONEFREE 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVEALONEPAY 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_COM 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_EXP 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_HVY 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_LOC 0.000e+00 0.000e+00 0.000e+00 NaN\nDRIVE_LRF 0.000e+00 0.000e+00 0.000e+00 NaN\nSHARED2FREE 4.883e-03 4.885e-03 2.162e-06 4.428e-04\nSHARED2PAY 0.000e+00 0.000e+00 0.000e+00 NaN\nSHARED3FREE 4.701e-14 0.000e+00 -4.701e-14 -1.000e+00\nSHARED3PAY 0.000e+00 0.000e+00 0.000e+00 NaN\nTAXI 9.611e-05 9.500e-05 -1.108e-06 -1.152e-02\nTNC_SHARED 2.868e-04 2.910e-04 4.245e-06 1.480e-02\nTNC_SINGLE 1.308e-04 1.250e-04 -5.755e-06 -4.401e-02\nWALK 4.490e-02 4.463e-02 -2.769e-04 -6.166e-03\nWALK_COM 0.000e+00 0.000e+00 0.000e+00 NaN\nWALK_EXP 0.000e+00 0.000e+00 0.000e+00 NaN\nWALK_HVY 0.000e+00 0.000e+00 0.000e+00 NaN\nWALK_LOC 5.996e-02 6.031e-02 3.467e-04 5.782e-03\nWALK_LRF 8.897e-01 8.897e-01 -6.933e-05 -7.792e-05", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
mode_share_obstrip_modediffrel_diff
BIKE0.000e+000.000e+000.000e+00NaN
DRIVEALONEFREE0.000e+000.000e+000.000e+00NaN
DRIVEALONEPAY0.000e+000.000e+000.000e+00NaN
DRIVE_COM0.000e+000.000e+000.000e+00NaN
DRIVE_EXP0.000e+000.000e+000.000e+00NaN
DRIVE_HVY0.000e+000.000e+000.000e+00NaN
DRIVE_LOC0.000e+000.000e+000.000e+00NaN
DRIVE_LRF0.000e+000.000e+000.000e+00NaN
SHARED2FREE4.883e-034.885e-032.162e-064.428e-04
SHARED2PAY0.000e+000.000e+000.000e+00NaN
SHARED3FREE4.701e-140.000e+00-4.701e-14-1.000e+00
SHARED3PAY0.000e+000.000e+000.000e+00NaN
TAXI9.611e-059.500e-05-1.108e-06-1.152e-02
TNC_SHARED2.868e-042.910e-044.245e-061.480e-02
TNC_SINGLE1.308e-041.250e-04-5.755e-06-4.401e-02
WALK4.490e-024.463e-02-2.769e-04-6.166e-03
WALK_COM0.000e+000.000e+000.000e+00NaN
WALK_EXP0.000e+000.000e+000.000e+00NaN
WALK_HVY0.000e+000.000e+000.000e+00NaN
WALK_LOC5.996e-026.031e-023.467e-045.782e-03
WALK_LRF8.897e-018.897e-01-6.933e-05-7.792e-05
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.000000 0.000000e+00 0.000000 0.000000 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.002600 0.020660 0.976740 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0000000.000000e+000.0000000.0000000.0000000.0026000.0206600.976740
\n
" }, "metadata": {}, "output_type": "display_data" } ], "source": [ - "with pd.option_context(\"precision\", 3):\n", - " display(ms_comp)" - ], + "choose_individual_max_utility = True\n", + "num_samples = 100000 # 1e5 takes about 25s\n", + "trip_id_to_check = 642446345\n", + "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n" } } - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1fcdc0ff-c510-4878-8672-8365e046442c", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From f2cea72263cb23473544ebeedeb51c7be1060f2a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 6 May 2022 11:59:01 +1000 Subject: [PATCH 012/298] two level still off --- activitysim/core/logit.py | 11 +- activitysim/core/simulate.py | 11 +- ...ested_logit_frozen_rand_individ_util.ipynb | 314 +++++++++--- notebooks/validate_frozen_impl.ipynb | 473 +++--------------- 4 files changed, 320 insertions(+), 489 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 8f432e5c1e..dd3c401eb2 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -198,9 +198,13 @@ def add_ev1_random(df, nest_spec): for n in each_nest(nest_spec): if n.level == 1: continue # skip the root level - # TODO: check parent nest level scale is what we want this is right + # TODO: check parent nest level scale is what we want uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice) - rands = inverse_ev1_cdf(uniform_rands, scale=n.parent_nest_scale) + #if n.is_leaf: + # scale = n.coefficient # product_of_coefficients parent_nest_scale coefficient + #else: + scale = 1.0 # n.coefficient + rands = inverse_ev1_cdf(uniform_rands, scale=scale) nest_utils_for_choice.loc[:, n.name] += rands[:, 0] # inverse_ev1_cdf of single-row df adds dimension return nest_utils_for_choice @@ -224,9 +228,6 @@ def is_alternative(name): assert not nest_utils_for_choice["choice"].isnull().any(), "No choice for XXX - implement reporting" choices = pd.Series(nest_utils_for_choice["choice"], index=nested_utilities.index) - - assert not choices.isnull().any(), "No choice for XXX - implement reporting" - return choices diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 289ba90d92..f3843bd0c7 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -737,15 +737,16 @@ def compute_nested_utilities(raw_utilities, nest_spec): name = nest.name if nest.is_leaf: #nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients - nested_utilities[name] = raw_utilities[name].astype(float) / nest.coefficient + nested_utilities[name] = raw_utilities[name].astype(float) / nest.coefficient # WHY does asim use product of coeffs here? using that gives us identical root logsums # but then we need to scale ev1 accordingly for alternatives, right? else: # the alternative nested_utilities will already have been computed due to post_order - if nest.level == 1: # FIXME: work out the parent scale mess - scale = 1.0 - else: - scale = nest.parent_nest_scale + #if nest.level == 1: # FIXME: work out the parent scale mess + # scale = 1.0 + #else: + # scale = nest.parent_nest_scale + scale = 1.0 # IT'S RECURSIVE for lowest level but check for higher level with np.errstate(divide='ignore'): nested_utilities[name] = \ diff --git a/notebooks/nested_logit_frozen_rand_individ_util.ipynb b/notebooks/nested_logit_frozen_rand_individ_util.ipynb index dede399ca8..4f06fca9ef 100644 --- a/notebooks/nested_logit_frozen_rand_individ_util.ipynb +++ b/notebooks/nested_logit_frozen_rand_individ_util.ipynb @@ -72,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 27, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:14.957470Z", @@ -101,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:15.467608Z", @@ -116,14 +116,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.4452265282367507 0.5330453677531714 0.02172810401007798\n", + "0.4402110749048506 0.5234199037442896 0.036369021350859815\n", "1.0\n" ] } ], "source": [ "cost = 3.0\n", - "nest_scale = 0.5\n", + "nest_scale = 0.6\n", "\n", "util_3 = utility(cost, utility_spec, 3)\n", "util_2 = utility(cost, utility_spec, 2)\n", @@ -144,17 +144,9 @@ "print(sum([prob_1, prob_2, prob_3]))" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The ActivitySim way\n", - "Kind of pointless here but this is how we choose a single value - draw from U and pick whichever interval it falls into" - ] - }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 32, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:20.523480Z", @@ -164,37 +156,25 @@ "shell.execute_reply.started": "2022-05-01T09:55:20.523453Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "closed form: [0.4452265282367507, 0.5330453677531714, 0.02172810401007798],\n", - "simulated: [0.4450967 0.5331544 0.0217489]\n", - "CPU times: user 750 ms, sys: 78.1 ms, total: 828 ms\n", - "Wall time: 811 ms\n" - ] - } - ], + "outputs": [], "source": [ - "%%time\n", - "\n", - "probs = [prob_1, prob_2, prob_3]\n", - "cum_probs = [0] + list(np.cumsum(probs))\n", - "\n", - "num_draws = 10000000\n", - "\n", - "# now draw from U and put into arrays, then value count?\n", - "rng = default_rng(999)\n", - "rands = rng.uniform(size=num_draws)\n", - "\n", - "hits, bins = np.histogram(rands, bins=cum_probs)\n", - "print(f\"closed form: {probs},\\nsimulated: {hits / num_draws}\")" + "# Asim does this:\n", + "# probs = [prob_1, prob_2, prob_3]\n", + "# cum_probs = [0] + list(np.cumsum(probs))\n", + "#\n", + "# num_draws = 10000000\n", + "#\n", + "# # now draw from U and put into arrays, then value count?\n", + "# rng = default_rng(999)\n", + "# rands = rng.uniform(size=num_draws)\n", + "#\n", + "# hits, bins = np.histogram(rands, bins=cum_probs)\n", + "# print(f\"closed form: {probs},\\nsimulated: {hits / num_draws}\")" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T09:55:24.027745Z", @@ -237,57 +217,177 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 52, "metadata": { "execution": { - "iopub.execute_input": "2022-05-01T10:02:14.035272Z", - "iopub.status.busy": "2022-05-01T10:02:14.035014Z", - "iopub.status.idle": "2022-05-01T10:02:14.040376Z", - "shell.execute_reply": "2022-05-01T10:02:14.039207Z", - "shell.execute_reply.started": "2022-05-01T10:02:14.035235Z" + "iopub.execute_input": "2022-05-01T10:03:23.999857Z", + "iopub.status.busy": "2022-05-01T10:03:23.999571Z", + "iopub.status.idle": "2022-05-01T10:03:30.033848Z", + "shell.execute_reply": "2022-05-01T10:03:30.032689Z", + "shell.execute_reply.started": "2022-05-01T10:03:23.999816Z" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closed form probs: 0.440211, 0.523420, 0.036369\n", + " simulated probs: 0.4403354, 0.5233336, 0.036331\n", + "CPU times: user 3.88 s, sys: 2.52 s, total: 6.39 s\n", + "Wall time: 6.73 s\n" + ] + } + ], "source": [ + "%%time\n", + "\n", + "# conditional error term are given by logit with scale given by nest scale\n", + "num_draws_dec = 10000000\n", + "#mu = 1.0 / nest_scale\n", + "\n", "util_3 = utility(cost, utility_spec, 3)\n", "util_2 = utility(cost, utility_spec, 2)\n", "logsum_bus = logsum(np.array([util_2, util_3]), nest_scale=nest_scale)\n", - "util_1 = utility(cost, utility_spec, 1)" + "util_1 = utility(cost, utility_spec, 1)\n", + "\n", + "\n", + "rng_dec = default_rng(9)\n", + "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", + "ev1_lower = inverse_ev1_cdf(rands_dec)\n", + "\n", + "lower_utils_2 = util_2 / nest_scale + ev1_lower[num_draws_dec:]\n", + "lower_utils_3 = util_3 / nest_scale + ev1_lower[:num_draws_dec]\n", + "\n", + "ev1_upper = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec))\n", + "nest_util = nest_scale * logsum_bus + ev1_upper\n", + "\n", + "ev1_upper = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec))\n", + "full_util_1 = util_1 + ev1_upper\n", + "\n", + "choices = np.array([full_util_1, nest_util]).argmax(axis=0)\n", + "nest_indexes = np.nonzero(choices == 1)[0]\n", + "nest_choices = np.array([lower_utils_2[nest_indexes], lower_utils_3[nest_indexes]]).argmax(axis=0)\n", + "nest_choices += 1\n", + "choices = np.append(choices[choices == 0], nest_choices)\n", + "\n", + "vals, counts = np.unique(choices, return_counts=True)\n", + "probs_dec = {i+1: counts[i] / num_draws_dec for i in vals}\n", + "\n", + "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", + "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, + "outputs": [], + "source": [], "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:03:23.999857Z", - "iopub.status.busy": "2022-05-01T10:03:23.999571Z", - "iopub.status.idle": "2022-05-01T10:03:30.033848Z", - "shell.execute_reply": "2022-05-01T10:03:30.032689Z", - "shell.execute_reply.started": "2022-05-01T10:03:23.999816Z" + "collapsed": false, + "pycharm": { + "name": "#%%\n" } - }, + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# This is the Zenith way as it was designed originally\n", + "\n", + "w/o scaling utilities in logsums, everything else is identical" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 47, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "closed form probs: 0.445227, 0.533045, 0.021728\n", - " simulated probs: 0.4452013, 0.5330709, 0.0217278\n" + "0.19304564457406473 0.6714008596782736 0.1355534957476616\n" + ] + } + ], + "source": [ + "cost = 3.0\n", + "nest_scale = 0.6\n", + "\n", + "util_3 = utility(cost, utility_spec, 3)\n", + "util_2 = utility(cost, utility_spec, 2)\n", + "logsum_bus = logsum(np.array([util_2, util_3])) #, nest_scale=nest_scale) # DIFF\n", + "nest_util = nest_scale * logsum_bus\n", + "\n", + "util_1 = utility(cost, utility_spec, 1)\n", + "prob_1 = np.exp(util_1) / (np.exp(util_1) + np.exp(nest_util))\n", + "\n", + "nest_cond_prob = np.exp(nest_util) / (np.exp(util_1) + np.exp(nest_util))\n", + "nest_marg_prob_2 = np.exp(util_2) / (np.exp(util_2) + np.exp(util_3)) # DIFF\n", + "nest_marg_prob_3 = np.exp(util_3) / (np.exp(util_2) + np.exp(util_3)) # DIFF\n", + "\n", + "prob_2 = nest_cond_prob * nest_marg_prob_2\n", + "prob_3 = nest_cond_prob * nest_marg_prob_3\n", + "\n", + "print(prob_1, prob_2, prob_3)\n", + "assert np.isclose(1.0, sum([prob_1, prob_2, prob_3]))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 48, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "closed form probs: 0.193046, 0.671401, 0.135553\n", + " simulated probs: 0.1930163, 0.6715585, 0.1354252\n" ] } ], "source": [ + "# test w/o scaling in utility? is this what previous zenith version does?\n", + "\n", "# conditional error term are given by logit with scale given by nest scale\n", "num_draws_dec = 10000000\n", - "#mu = 1.0 / nest_scale\n", + "\n", + "util_3 = utility(cost, utility_spec, 3)\n", + "util_2 = utility(cost, utility_spec, 2)\n", + "logsum_bus = logsum(np.array([util_2, util_3])) # , nest_scale=nest_scale) # THAT's the only diff right #\n", + "util_1 = utility(cost, utility_spec, 1)\n", + "\n", "\n", "rng_dec = default_rng(9)\n", "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", - "ev1_lower = inverse_ev1_cdf(rands_dec, scale=nest_scale)\n", + "ev1_lower = inverse_ev1_cdf(rands_dec) #, scale=nest_scale)\n", "\n", - "lower_utils_2 = util_2 + ev1_lower[num_draws_dec:] \n", - "lower_utils_3 = util_3 + ev1_lower[:num_draws_dec] \n", + "lower_utils_2 = util_2 + ev1_lower[num_draws_dec:]\n", + "lower_utils_3 = util_3 + ev1_lower[:num_draws_dec]\n", "\n", "#logsum_bus = logsum(np.array([lower_utils_2, lower_utils_3]), nest_scale=nest_scale)\n", "ev1_upper = inverse_ev1_cdf(rng_dec.uniform(size=2*num_draws_dec))\n", @@ -306,7 +406,97 @@ "\n", "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")" - ] + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } }, { "cell_type": "code", diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index f68019d396..bfb9b39faf 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -170,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 188, + "execution_count": 8, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -234,305 +234,16 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'atwork' (27 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'eatout' (33 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'escort' (6 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'othmaint' (46 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'school' (37 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'shopping' (77 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'social' (19 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'univ' (26 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'work' (168 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "trip_mode_choice tour_type 'atwork' (27 trips)\n", + "trip_mode_choice tour_type 'eatout' (33 trips)\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n", + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", + "trip_mode_choice tour_type 'othmaint' (46 trips)\n", + "trip_mode_choice tour_type 'school' (37 trips)\n", + "trip_mode_choice tour_type 'shopping' (77 trips)\n", + "trip_mode_choice tour_type 'social' (19 trips)\n", + "trip_mode_choice tour_type 'univ' (26 trips)\n", + "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n" ] } @@ -545,14 +256,14 @@ }, { "cell_type": "code", - "execution_count": 189, + "execution_count": 9, "outputs": [ { "data": { "text/plain": " trip_mode_fru trip_mode_asim\nWALK 299 304\nWALK_LOC 94 85\nWALK_LRF 57 53\nBIKE 17 17\nDRIVEALONEFREE 6 4\nWALK_HVY 4 4\nSHARED2FREE 3 4\nTNC_SINGLE 2 4", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK299304
WALK_LOC9485
WALK_LRF5753
BIKE1717
DRIVEALONEFREE64
WALK_HVY44
SHARED2FREE34
TNC_SINGLE24
\n
" }, - "execution_count": 189, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -572,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 190, + "execution_count": 10, "outputs": [ { "name": "stdout", @@ -597,20 +308,10 @@ }, { "cell_type": "code", - "execution_count": 187, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n8805121 -5709.149390 -8563.435105 -inf -2856.962423 \n8805125 -5710.236852 -8564.522566 -inf -2857.805670 \n8805126 -5709.506304 -8563.792018 -inf -2857.185402 \n468119921 -2856.413143 -5710.698857 -inf -2855.880864 \n468119922 -2856.976497 -5711.262211 -inf -2856.444428 \n468119925 -2870.186517 -5724.472231 -inf -2863.993439 \n468119926 -2857.935513 -5712.221227 -inf -2856.918910 \n468119927 -2859.140866 -5713.426580 -inf -2857.537407 \n468119928 -2869.283122 -5723.568836 -inf -2863.287255 \n642446345 -5709.613741 -8563.899455 -inf -10.654154 \n642446349 -5711.108042 -8565.393757 -inf -11.507365 \n1179608009 -8563.419303 -11417.705017 -inf -17.616599 \n1179608013 -8571.196804 -11425.482519 -inf -22.061100 \n1658807153 -5709.137752 -8563.423466 -inf -19.719413 \n1658807157 -5710.005638 -8564.291352 -inf -20.220421 \n1767182945 -5709.423410 -8563.709124 -inf -10.469202 \n1767182949 -5709.668094 -8563.953808 -inf -10.618407 \n2464104881 -5709.489682 -8563.775396 -inf -12.625725 \n2464104885 -5709.489682 -8563.775396 -inf -12.625725 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n8805121 -5711.248137 -inf -2856.962379 -5711.248093 -inf \n8805125 -5712.091384 -inf -2857.707940 -5711.993654 -inf \n8805126 -5711.471116 -inf -2857.131784 -5711.417498 -inf \n468119921 -5710.166578 -inf -2855.667952 -5709.953666 -inf \n468119922 -5710.730142 -inf -2856.231600 -5710.517314 -inf \n468119925 -5718.279154 -inf -2861.516208 -5715.801922 -inf \n468119926 -5711.204625 -inf -2856.512269 -5710.797984 -inf \n468119927 -5711.823121 -inf -2856.896023 -5711.181738 -inf \n468119928 -5717.572969 -inf -2860.888909 -5715.174623 -inf \n642446345 -2864.939868 -5.179103 -62.836260 -2917.121974 -30.545404 \n642446349 -2865.793079 -5.593858 -63.433034 -2917.718749 -30.835503 \n1179608009 -2871.902313 -8.563624 -19.761289 -2874.047003 -9.606182 \n1179608013 -2876.346814 -10.724146 -22.872589 -2877.158304 -11.118620 \n1658807153 -2874.005127 -9.585826 -21.864019 -2876.149734 -10.628343 \n1658807157 -2874.506135 -9.829371 -22.218276 -2876.503991 -10.800551 \n1767182945 -2864.754916 -5.089195 -62.653459 -2916.939174 -30.456543 \n1767182949 -2864.904122 -5.161726 -62.764474 -2917.050188 -30.510508 \n2464104881 -2866.911439 -6.137505 -64.806540 -2919.092254 -31.503179 \n2464104885 -2866.911439 -6.137505 -64.806540 -2919.092254 -31.503179 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n8805121 -inf 12.460532 -1375.039468 8.971583 -1980.409272 \n8805125 -inf 12.557754 -1374.990857 9.041583 -3978.191234 \n8805126 -inf 12.207754 -1375.165857 8.789583 -1980.441864 \n468119921 -inf 10.647096 -1376.595265 7.665909 -1984.028282 \n468119922 -inf -0.158141 -1387.385919 -0.113862 -2003.585155 \n468119925 -inf -0.158141 -1387.385919 -0.113862 -2003.585155 \n468119926 -inf 10.734596 -1376.551515 7.728909 -1987.731834 \n468119927 -inf 10.765788 -1376.855740 7.751367 -1987.818125 \n468119928 -inf 11.707060 -1375.408912 8.429083 -1985.936364 \n642446345 -3.728954 -2.097408 -1387.343797 -1.510134 1.093980 \n642446349 -4.027578 -2.146019 -1387.353519 -1.545134 1.192207 \n1179608009 -5.948375 4.880186 -1378.113147 3.513734 -1984.050332 \n1179608013 -7.350415 4.880186 -1378.113147 3.513734 -1984.050332 \n1658807153 -6.684352 6.456402 -1376.357071 4.648609 15.268960 \n1658807157 -6.845959 6.407790 -1376.381376 4.613609 14.530278 \n1767182945 -3.664221 -3.606195 -1388.969251 -2.596460 -0.555751 \n1767182949 -3.716443 -3.776334 -1389.003278 -2.718960 -0.579012 \n2464104881 -4.419004 8.134630 -1378.288147 5.856934 10.502444 \n2464104885 -4.419004 8.134630 -1378.288147 5.856934 10.666689 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n8805121 -3978.409272 -3978.409272 -3978.409272 -3978.409272 -inf \n8805125 -3978.191234 -3978.191234 -3978.191234 -3978.191234 -inf \n8805126 -3978.441864 -3978.791864 -3978.441864 -3978.441864 -inf \n468119921 -3982.028282 -3982.028282 -3982.028282 -3982.028282 -inf \n468119922 -4001.585155 -4001.935155 -4001.585155 -4001.585155 -inf \n468119925 -4001.585155 -4001.585155 -4001.585155 -4001.585155 -inf \n468119926 -3985.731834 -3986.081834 -3985.731834 -3985.731834 -inf \n468119927 -3985.818125 -3986.168125 -3985.818125 -3985.818125 -inf \n468119928 -3983.936364 -3984.286364 -3983.936364 -3983.936364 -inf \n642446345 3.035970 -1995.576420 -1995.597276 -1995.880220 2.140923 \n642446349 3.035270 -1994.736869 -1995.597276 -1995.040669 2.145065 \n1179608009 -1984.050332 -1984.050332 -1984.050332 -1984.050332 -inf \n1179608013 -1984.050332 -1984.050332 -1984.050332 -1984.050332 -inf \n1658807153 -1982.731040 -1982.731040 -1982.731040 -1982.731040 10.603444 \n1658807157 -1983.469722 -1983.469722 -1983.469722 -1983.469722 10.090471 \n1767182945 0.665917 -1997.106222 -1997.397020 -1997.410022 0.546586 \n1767182949 0.665917 -1997.106222 -1997.408920 -1997.410022 0.544118 \n2464104881 -1986.419156 -1986.167956 -1986.416956 -1986.471756 7.293364 \n2464104885 -1986.254911 -1986.003711 -1986.252711 -1986.307511 7.407423 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n8805121 -5976.701052 -5979.008301 -5979.008301 -5979.622112 -5980.000155 \n8805125 -5976.250034 -5976.250034 -5976.250034 -5976.250034 -5976.250034 \n8805126 -5976.561059 -5980.066360 -5980.066360 -5977.489912 -5981.139291 \n468119921 -1987.917275 -3986.634667 -3986.634667 -3988.159653 -3989.540253 \n468119922 -1999.781073 -4002.350822 -4002.350822 -4003.314328 -4005.292790 \n468119925 -1999.856900 -4003.785211 -4003.785211 -4001.364128 -4006.015621 \n468119926 -1987.922393 -3991.085771 -3991.085771 -3985.679774 -3991.883814 \n468119927 -1984.330401 -3988.035744 -3988.035744 -3985.888572 -3992.028453 \n468119928 -1986.084720 -3989.542402 -3989.542402 -3984.015305 -3990.339045 \n642446345 -5993.059741 -5996.236057 -5996.236057 -5996.847988 -5998.040211 \n642446349 -5993.114899 -5996.745065 -5996.745065 -5994.750788 -5998.886171 \n1179608009 -7978.109132 -7978.109132 -7978.109132 -7978.109132 -7978.109132 \n1179608013 -7978.109132 -7978.109132 -7978.109132 -7978.109132 -7978.109132 \n1658807153 -5978.797090 -5981.645926 -5981.645926 -5982.436880 -5983.088374 \n1658807157 -5979.569178 -5984.301635 -5984.301635 -5980.452579 -5984.874558 \n1767182945 -5994.656309 -5997.269370 -5997.269370 -5997.769890 -5998.629581 \n1767182949 -5994.664265 -5997.497589 -5997.497589 -5995.576790 -5998.540328 \n2464104881 -5983.483693 -5989.023319 -5989.023319 -5991.399546 -5994.064540 \n2464104885 -5983.469343 -5991.354620 -5991.354620 -5989.123846 -5997.222704 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED \\\ntrip_id \n8805121 -inf -inf -20.081973 -19.473153 -20.804324 \n8805125 -inf -inf -20.047538 -19.456673 -20.779823 \n8805126 -inf -inf -20.169318 -19.513014 -20.864116 \n468119921 -inf -inf -26.859498 -30.543501 -25.552660 \n468119922 -inf -inf -26.820071 -30.516140 -25.512174 \n468119925 -inf -inf -26.824219 -30.517112 -25.513632 \n468119926 -inf -inf -26.316039 -30.492180 -25.480679 \n468119927 -inf -inf -24.418379 -30.418607 -25.374051 \n468119928 -inf -inf -27.800562 -30.563790 -25.697316 \n642446345 -inf 1.541464 -18.289778 -17.981922 -17.196617 \n642446349 -inf 1.544447 -18.324954 -17.976575 -17.192002 \n1179608009 -inf -inf -8.526813 0.047647 -2.273228 \n1179608013 -inf -inf -8.527679 0.047161 -2.273957 \n1658807153 -inf 7.634480 -8.591632 0.206340 -2.227894 \n1658807157 -inf 7.265139 -8.647127 0.198076 -2.240290 \n1767182945 -inf 0.393542 -14.395026 -13.588217 -14.958046 \n1767182949 -inf 0.391765 -14.435998 -13.608147 -14.995707 \n2464104881 -inf 5.251222 -24.155944 -33.809528 -23.204042 \n2464104885 -inf 5.333344 -24.155944 -33.809528 -23.204042 \n\n RIDEHAIL root \ntrip_id \n8805121 -6.797103 8.971583 \n8805125 -6.788793 9.041583 \n8805126 -6.817566 8.789583 \n468119921 -9.110799 7.665909 \n468119922 -9.096330 -0.113736 \n468119925 -9.097058 -0.113736 \n468119926 -9.041678 7.728909 \n468119927 -8.672837 7.751368 \n468119928 -9.207112 8.429083 \n642446345 -5.980956 1.593075 \n642446349 -5.981865 1.593106 \n1179608009 0.050933 3.544670 \n1179608013 0.050750 3.544608 \n1658807153 0.104584 7.684253 \n1658807157 0.101487 7.334029 \n1767182945 -4.700641 0.464659 \n1767182949 -4.710741 0.456698 \n2464104881 -8.235935 6.292424 \n2464104885 -8.235935 6.322192 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
8805121-5709.149390-8563.435105-inf-2856.962423-5711.248137-inf-2856.962379-5711.248093-inf-inf12.460532-1375.0394688.971583-1980.409272-3978.409272-3978.409272-3978.409272-3978.409272-inf-5976.701052-5979.008301-5979.008301-5979.622112-5980.000155-inf-inf-20.081973-19.473153-20.804324-6.7971038.971583
8805125-5710.236852-8564.522566-inf-2857.805670-5712.091384-inf-2857.707940-5711.993654-inf-inf12.557754-1374.9908579.041583-3978.191234-3978.191234-3978.191234-3978.191234-3978.191234-inf-5976.250034-5976.250034-5976.250034-5976.250034-5976.250034-inf-inf-20.047538-19.456673-20.779823-6.7887939.041583
8805126-5709.506304-8563.792018-inf-2857.185402-5711.471116-inf-2857.131784-5711.417498-inf-inf12.207754-1375.1658578.789583-1980.441864-3978.441864-3978.791864-3978.441864-3978.441864-inf-5976.561059-5980.066360-5980.066360-5977.489912-5981.139291-inf-inf-20.169318-19.513014-20.864116-6.8175668.789583
468119921-2856.413143-5710.698857-inf-2855.880864-5710.166578-inf-2855.667952-5709.953666-inf-inf10.647096-1376.5952657.665909-1984.028282-3982.028282-3982.028282-3982.028282-3982.028282-inf-1987.917275-3986.634667-3986.634667-3988.159653-3989.540253-inf-inf-26.859498-30.543501-25.552660-9.1107997.665909
468119922-2856.976497-5711.262211-inf-2856.444428-5710.730142-inf-2856.231600-5710.517314-inf-inf-0.158141-1387.385919-0.113862-2003.585155-4001.585155-4001.935155-4001.585155-4001.585155-inf-1999.781073-4002.350822-4002.350822-4003.314328-4005.292790-inf-inf-26.820071-30.516140-25.512174-9.096330-0.113736
468119925-2870.186517-5724.472231-inf-2863.993439-5718.279154-inf-2861.516208-5715.801922-inf-inf-0.158141-1387.385919-0.113862-2003.585155-4001.585155-4001.585155-4001.585155-4001.585155-inf-1999.856900-4003.785211-4003.785211-4001.364128-4006.015621-inf-inf-26.824219-30.517112-25.513632-9.097058-0.113736
468119926-2857.935513-5712.221227-inf-2856.918910-5711.204625-inf-2856.512269-5710.797984-inf-inf10.734596-1376.5515157.728909-1987.731834-3985.731834-3986.081834-3985.731834-3985.731834-inf-1987.922393-3991.085771-3991.085771-3985.679774-3991.883814-inf-inf-26.316039-30.492180-25.480679-9.0416787.728909
468119927-2859.140866-5713.426580-inf-2857.537407-5711.823121-inf-2856.896023-5711.181738-inf-inf10.765788-1376.8557407.751367-1987.818125-3985.818125-3986.168125-3985.818125-3985.818125-inf-1984.330401-3988.035744-3988.035744-3985.888572-3992.028453-inf-inf-24.418379-30.418607-25.374051-8.6728377.751368
468119928-2869.283122-5723.568836-inf-2863.287255-5717.572969-inf-2860.888909-5715.174623-inf-inf11.707060-1375.4089128.429083-1985.936364-3983.936364-3984.286364-3983.936364-3983.936364-inf-1986.084720-3989.542402-3989.542402-3984.015305-3990.339045-inf-inf-27.800562-30.563790-25.697316-9.2071128.429083
642446345-5709.613741-8563.899455-inf-10.654154-2864.939868-5.179103-62.836260-2917.121974-30.545404-3.728954-2.097408-1387.343797-1.5101341.0939803.035970-1995.576420-1995.597276-1995.8802202.140923-5993.059741-5996.236057-5996.236057-5996.847988-5998.040211-inf1.541464-18.289778-17.981922-17.196617-5.9809561.593075
642446349-5711.108042-8565.393757-inf-11.507365-2865.793079-5.593858-63.433034-2917.718749-30.835503-4.027578-2.146019-1387.353519-1.5451341.1922073.035270-1994.736869-1995.597276-1995.0406692.145065-5993.114899-5996.745065-5996.745065-5994.750788-5998.886171-inf1.544447-18.324954-17.976575-17.192002-5.9818651.593106
1179608009-8563.419303-11417.705017-inf-17.616599-2871.902313-8.563624-19.761289-2874.047003-9.606182-5.9483754.880186-1378.1131473.513734-1984.050332-1984.050332-1984.050332-1984.050332-1984.050332-inf-7978.109132-7978.109132-7978.109132-7978.109132-7978.109132-inf-inf-8.5268130.047647-2.2732280.0509333.544670
1179608013-8571.196804-11425.482519-inf-22.061100-2876.346814-10.724146-22.872589-2877.158304-11.118620-7.3504154.880186-1378.1131473.513734-1984.050332-1984.050332-1984.050332-1984.050332-1984.050332-inf-7978.109132-7978.109132-7978.109132-7978.109132-7978.109132-inf-inf-8.5276790.047161-2.2739570.0507503.544608
1658807153-5709.137752-8563.423466-inf-19.719413-2874.005127-9.585826-21.864019-2876.149734-10.628343-6.6843526.456402-1376.3570714.64860915.268960-1982.731040-1982.731040-1982.731040-1982.73104010.603444-5978.797090-5981.645926-5981.645926-5982.436880-5983.088374-inf7.634480-8.5916320.206340-2.2278940.1045847.684253
1658807157-5710.005638-8564.291352-inf-20.220421-2874.506135-9.829371-22.218276-2876.503991-10.800551-6.8459596.407790-1376.3813764.61360914.530278-1983.469722-1983.469722-1983.469722-1983.46972210.090471-5979.569178-5984.301635-5984.301635-5980.452579-5984.874558-inf7.265139-8.6471270.198076-2.2402900.1014877.334029
1767182945-5709.423410-8563.709124-inf-10.469202-2864.754916-5.089195-62.653459-2916.939174-30.456543-3.664221-3.606195-1388.969251-2.596460-0.5557510.665917-1997.106222-1997.397020-1997.4100220.546586-5994.656309-5997.269370-5997.269370-5997.769890-5998.629581-inf0.393542-14.395026-13.588217-14.958046-4.7006410.464659
1767182949-5709.668094-8563.953808-inf-10.618407-2864.904122-5.161726-62.764474-2917.050188-30.510508-3.716443-3.776334-1389.003278-2.718960-0.5790120.665917-1997.106222-1997.408920-1997.4100220.544118-5994.664265-5997.497589-5997.497589-5995.576790-5998.540328-inf0.391765-14.435998-13.608147-14.995707-4.7107410.456698
2464104881-5709.489682-8563.775396-inf-12.625725-2866.911439-6.137505-64.806540-2919.092254-31.503179-4.4190048.134630-1378.2881475.85693410.502444-1986.419156-1986.167956-1986.416956-1986.4717567.293364-5983.483693-5989.023319-5989.023319-5991.399546-5994.064540-inf5.251222-24.155944-33.809528-23.204042-8.2359356.292424
2464104885-5709.489682-8563.775396-inf-12.625725-2866.911439-6.137505-64.806540-2919.092254-31.503179-4.4190048.134630-1378.2881475.85693410.666689-1986.254911-1986.003711-1986.252711-1986.3075117.407423-5983.469343-5991.354620-5991.354620-5989.123846-5997.222704-inf5.333344-24.155944-33.809528-23.204042-8.2359356.322192
\n
" - }, - "execution_count": 187, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 12, + "outputs": [], "source": [ - "simulate.compute_nested_utilities(ru[0], ns[0])" + "#simulate.compute_nested_utilities(ru[0], ns[0])" ], "metadata": { "collapsed": false, @@ -638,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 192, + "execution_count": 26, "outputs": [ { "name": "stderr", @@ -653,36 +354,7 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (19 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "trip_mode_choice tour_type 'social' (19 trips)\n", "Done\n" ] } @@ -700,7 +372,7 @@ }, { "cell_type": "code", - "execution_count": 194, + "execution_count": 27, "outputs": [ { "name": "stdout", @@ -708,13 +380,8 @@ "text": [ "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root'] parent nest scale: 0\n", "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] parent nest scale: 1.0\n", - "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] parent nest scale: 0.72\n", - "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2'] parent nest scale: 0.72\n", - "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3'] parent nest scale: 0.72\n", "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED'] parent nest scale: 1.0\n", "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT'] parent nest scale: 1.0\n", - "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS'] parent nest scale: 0.72\n", - "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS'] parent nest scale: 0.72\n", "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL'] parent nest scale: 1.0\n" ] } @@ -733,37 +400,32 @@ }, { "cell_type": "code", - "execution_count": 195, + "execution_count": 28, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "leaf DRIVEALONEFREE with 0.252, 0.35\n", - "leaf DRIVEALONEPAY with 0.252, 0.35\n", - "node DRIVEALONE with 0.35, 0.72\n", - "leaf SHARED2FREE with 0.252, 0.35\n", - "leaf SHARED2PAY with 0.252, 0.35\n", - "node SHAREDRIDE2 with 0.35, 0.72\n", - "leaf SHARED3FREE with 0.252, 0.35\n", - "leaf SHARED3PAY with 0.252, 0.35\n", - "node SHAREDRIDE3 with 0.35, 0.72\n", + "leaf DRIVEALONEFREE with 0.72, 0.72\n", + "leaf DRIVEALONEPAY with 0.72, 0.72\n", + "leaf SHARED2FREE with 0.72, 0.72\n", + "leaf SHARED2PAY with 0.72, 0.72\n", + "leaf SHARED3FREE with 0.72, 0.72\n", + "leaf SHARED3PAY with 0.72, 0.72\n", "node AUTO with 0.72, 1.0\n", "leaf WALK with 0.72, 0.72\n", "leaf BIKE with 0.72, 0.72\n", "node NONMOTORIZED with 0.72, 1.0\n", - "leaf WALK_LOC with 0.36, 0.5\n", - "leaf WALK_LRF with 0.36, 0.5\n", - "leaf WALK_EXP with 0.36, 0.5\n", - "leaf WALK_HVY with 0.36, 0.5\n", - "leaf WALK_COM with 0.36, 0.5\n", - "node WALKACCESS with 0.5, 0.72\n", - "leaf DRIVE_LOC with 0.36, 0.5\n", - "leaf DRIVE_LRF with 0.36, 0.5\n", - "leaf DRIVE_EXP with 0.36, 0.5\n", - "leaf DRIVE_HVY with 0.36, 0.5\n", - "leaf DRIVE_COM with 0.36, 0.5\n", - "node DRIVEACCESS with 0.5, 0.72\n", + "leaf WALK_LOC with 0.72, 0.72\n", + "leaf WALK_LRF with 0.72, 0.72\n", + "leaf WALK_EXP with 0.72, 0.72\n", + "leaf WALK_HVY with 0.72, 0.72\n", + "leaf WALK_COM with 0.72, 0.72\n", + "leaf DRIVE_LOC with 0.72, 0.72\n", + "leaf DRIVE_LRF with 0.72, 0.72\n", + "leaf DRIVE_EXP with 0.72, 0.72\n", + "leaf DRIVE_HVY with 0.72, 0.72\n", + "leaf DRIVE_COM with 0.72, 0.72\n", "node TRANSIT with 0.72, 1.0\n", "leaf TAXI with 0.36, 0.36\n", "leaf TNC_SINGLE with 0.36, 0.36\n", @@ -826,14 +488,14 @@ }, { "cell_type": "code", - "execution_count": 196, + "execution_count": 29, "outputs": [ { "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.262023e-03 0.0 \n642446349 0.0 0.0 3.138876e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.339899e-02 0.0 \n1767182949 0.0 0.0 1.282607e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.103657e-14 0.0 0.039196 0.0 0.197050 0.759044 \n642446349 3.423474e-14 0.0 0.037574 0.0 0.208612 0.750231 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.288763e-13 0.0 0.038976 0.0 0.282647 0.660225 \n1767182949 1.256775e-13 0.0 0.034777 0.0 0.280877 0.666774 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 8.388831e-05 1.141304e-04 2.502967e-04 \n642446349 0.0 0.0 8.053321e-05 1.140970e-04 2.500402e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.247409e-03 2.795129e-03 7.103828e-04 \n1767182949 0.0 0.0 1.229459e-03 2.813489e-03 7.024828e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.262023e-030.04.103657e-140.00.0391960.00.1970500.7590440.00.00.00.00.00.00.00.08.388831e-051.141304e-042.502967e-04
6424463490.00.03.138876e-030.03.423474e-140.00.0375740.00.2086120.7502310.00.00.00.00.00.00.00.08.053321e-051.140970e-042.500402e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.339899e-020.01.288763e-130.00.0389760.00.2826470.6602250.00.00.00.00.00.00.00.01.247409e-032.795129e-037.103828e-04
17671829490.00.01.282607e-020.01.256775e-130.00.0347770.00.2808770.6667740.00.00.00.00.00.00.00.01.229459e-032.813489e-037.024828e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" }, - "execution_count": 196, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -878,7 +540,7 @@ }, { "cell_type": "code", - "execution_count": 197, + "execution_count": 30, "outputs": [], "source": [ "def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples):\n", @@ -1081,7 +743,7 @@ }, { "cell_type": "code", - "execution_count": 198, + "execution_count": 31, "outputs": [ { "name": "stderr", @@ -1096,39 +758,14 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (100000 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n", - "numpy: Warning: underflow encountered in exp expression: origTaxiWaitTime = rng.lognormal_for_df(df, mu=origin_zone_taxi_wait_time_mean, sigma=origin_zone_taxi_wait_time_sd, broadcast=True, scale=True).clip(min_waitTime, max_waitTime)\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "trip_mode_choice tour_type 'social' (10000 trips)\n", "Done\n" ] }, { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.005210 0.000000e+00 0.000130 0.000280 0.000100 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.045410 0.061630 0.887240 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0052100.000000e+000.0001300.0002800.0001000.0454100.0616300.887240
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004262 4.103657e-14 0.000084 0.00025 0.000114 \ntrip_mode 0.004300 0.000000e+00 0.000100 0.00050 0.000100 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.039196 0.19705 0.759044 \ntrip_mode 0.039100 0.20000 0.755900 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0042624.103657e-140.0000840.000250.0001140.0391960.197050.759044
trip_mode0.0043000.000000e+000.0001000.000500.0001000.0391000.200000.755900
\n
" }, "metadata": {}, "output_type": "display_data" @@ -1136,7 +773,7 @@ ], "source": [ "choose_individual_max_utility = False\n", - "num_samples = 100000\n", + "num_samples = 10000\n", "trip_id_to_check = 642446345\n", "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" ], @@ -1149,7 +786,7 @@ }, { "cell_type": "code", - "execution_count": 199, + "execution_count": 38, "id": "1fcdc0ff-c510-4878-8672-8365e046442c", "metadata": {}, "outputs": [ @@ -1166,14 +803,14 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (100000 trips)\n", + "trip_mode_choice tour_type 'social' (10000 trips)\n", "Done\n" ] }, { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.000000 0.000000e+00 0.000000 0.000000 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.002600 0.020660 0.976740 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0000000.000000e+000.0000000.0000000.0000000.0026000.0206600.976740
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004262 4.103657e-14 0.000084 0.00025 0.000114 \ntrip_mode 0.000400 0.000000e+00 0.000000 0.00000 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.039196 0.19705 0.759044 \ntrip_mode 0.011600 0.20270 0.785300 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0042624.103657e-140.0000840.000250.0001140.0391960.197050.759044
trip_mode0.0004000.000000e+000.0000000.000000.0000000.0116000.202700.785300
\n
" }, "metadata": {}, "output_type": "display_data" @@ -1181,8 +818,8 @@ ], "source": [ "choose_individual_max_utility = True\n", - "num_samples = 100000 # 1e5 takes about 25s\n", - "trip_id_to_check = 642446345\n", + "num_samples = 10000 # 1e5 takes about 25s\n", + "trip_id_to_check = 642446345 # 2464104885 # 642446345 # 1767182945\n", "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" ] }, @@ -1190,7 +827,9 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [], + "source": [ + "# something is still slightly off. WHY?" + ], "metadata": { "collapsed": false, "pycharm": { From 4883c3342bcc0766fb4ea9f43b712b86e0ac5044 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 6 May 2022 12:13:41 +1000 Subject: [PATCH 013/298] recursive utility calculation fix --- activitysim/core/logit.py | 20 +- activitysim/core/simulate.py | 19 +- ...ested_logit_frozen_rand_individ_util.ipynb | 196 ------------------ notebooks/validate_frozen_impl.ipynb | 68 ++++-- 4 files changed, 59 insertions(+), 244 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index dd3c401eb2..048b37da42 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -197,14 +197,9 @@ def add_ev1_random(df, nest_spec): nest_utils_for_choice = df.copy() for n in each_nest(nest_spec): if n.level == 1: - continue # skip the root level - # TODO: check parent nest level scale is what we want + continue # skip the root level, not needed uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice) - #if n.is_leaf: - # scale = n.coefficient # product_of_coefficients parent_nest_scale coefficient - #else: - scale = 1.0 # n.coefficient - rands = inverse_ev1_cdf(uniform_rands, scale=scale) + rands = inverse_ev1_cdf(uniform_rands) nest_utils_for_choice.loc[:, n.name] += rands[:, 0] # inverse_ev1_cdf of single-row df adds dimension return nest_utils_for_choice @@ -377,12 +372,10 @@ def __init__(self, name=None, level=0): self.ancestors = [] self.alternatives = None self.coefficient = 0 - self.parent_nest_scale = 1.0 def print(self): - print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s parent nest scale: " - "%s" % - (self.name, self.level, self.coefficient, self.product_of_coefficients, self.ancestors, self.parent_nest_scale)) + print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s " % + (self.name, self.level, self.coefficient, self.product_of_coefficients, self.ancestors)) @property def is_leaf(self): @@ -453,7 +446,6 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients * coefficient nest.alternatives = alternatives nest.ancestors = parent_nest.ancestors + [name] - nest.parent_nest_scale = parent_nest.coefficient if pre_order: yield spec, nest @@ -475,10 +467,6 @@ def _each_nest(spec, parent_nest, post_order): nest.ancestors = parent_nest.ancestors + [name] nest.coefficient = parent_nest.coefficient - # this is used for frozen individual ru - see discussion in simulate.py l.740 - #nest.parent_nest_scale = parent_nest.product_of_coefficients - nest.parent_nest_scale = parent_nest.coefficient - yield spec, nest diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index f3843bd0c7..f5667ef307 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -736,21 +736,18 @@ def compute_nested_utilities(raw_utilities, nest_spec): for nest in logit.each_nest(nest_spec, post_order=True): name = nest.name if nest.is_leaf: - #nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients - nested_utilities[name] = raw_utilities[name].astype(float) / nest.coefficient - # WHY does asim use product of coeffs here? using that gives us identical root logsums - # but then we need to scale ev1 accordingly for alternatives, right? + # do not scale here, do afterwards so recursive structure works + nested_utilities[name] = raw_utilities[name].astype(float) #/ nest.coefficient else: # the alternative nested_utilities will already have been computed due to post_order - #if nest.level == 1: # FIXME: work out the parent scale mess - # scale = 1.0 - #else: - # scale = nest.parent_nest_scale - scale = 1.0 # IT'S RECURSIVE for lowest level but check for higher level - with np.errstate(divide='ignore'): nested_utilities[name] = \ - nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / scale).sum(axis=1)) + nest.coefficient * np.log( + np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1) + ) + + for nest in logit.each_nest(nest_spec, type="leaf"): + nested_utilities[nest.name] /= nest.coefficient return nested_utilities diff --git a/notebooks/nested_logit_frozen_rand_individ_util.ipynb b/notebooks/nested_logit_frozen_rand_individ_util.ipynb index 4f06fca9ef..fbf4fce6be 100644 --- a/notebooks/nested_logit_frozen_rand_individ_util.ipynb +++ b/notebooks/nested_logit_frozen_rand_individ_util.ipynb @@ -302,202 +302,6 @@ } } }, - { - "cell_type": "markdown", - "source": [ - "# This is the Zenith way as it was designed originally\n", - "\n", - "w/o scaling utilities in logsums, everything else is identical" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 47, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.19304564457406473 0.6714008596782736 0.1355534957476616\n" - ] - } - ], - "source": [ - "cost = 3.0\n", - "nest_scale = 0.6\n", - "\n", - "util_3 = utility(cost, utility_spec, 3)\n", - "util_2 = utility(cost, utility_spec, 2)\n", - "logsum_bus = logsum(np.array([util_2, util_3])) #, nest_scale=nest_scale) # DIFF\n", - "nest_util = nest_scale * logsum_bus\n", - "\n", - "util_1 = utility(cost, utility_spec, 1)\n", - "prob_1 = np.exp(util_1) / (np.exp(util_1) + np.exp(nest_util))\n", - "\n", - "nest_cond_prob = np.exp(nest_util) / (np.exp(util_1) + np.exp(nest_util))\n", - "nest_marg_prob_2 = np.exp(util_2) / (np.exp(util_2) + np.exp(util_3)) # DIFF\n", - "nest_marg_prob_3 = np.exp(util_3) / (np.exp(util_2) + np.exp(util_3)) # DIFF\n", - "\n", - "prob_2 = nest_cond_prob * nest_marg_prob_2\n", - "prob_3 = nest_cond_prob * nest_marg_prob_3\n", - "\n", - "print(prob_1, prob_2, prob_3)\n", - "assert np.isclose(1.0, sum([prob_1, prob_2, prob_3]))" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 48, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "closed form probs: 0.193046, 0.671401, 0.135553\n", - " simulated probs: 0.1930163, 0.6715585, 0.1354252\n" - ] - } - ], - "source": [ - "# test w/o scaling in utility? is this what previous zenith version does?\n", - "\n", - "# conditional error term are given by logit with scale given by nest scale\n", - "num_draws_dec = 10000000\n", - "\n", - "util_3 = utility(cost, utility_spec, 3)\n", - "util_2 = utility(cost, utility_spec, 2)\n", - "logsum_bus = logsum(np.array([util_2, util_3])) # , nest_scale=nest_scale) # THAT's the only diff right #\n", - "util_1 = utility(cost, utility_spec, 1)\n", - "\n", - "\n", - "rng_dec = default_rng(9)\n", - "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", - "ev1_lower = inverse_ev1_cdf(rands_dec) #, scale=nest_scale)\n", - "\n", - "lower_utils_2 = util_2 + ev1_lower[num_draws_dec:]\n", - "lower_utils_3 = util_3 + ev1_lower[:num_draws_dec]\n", - "\n", - "#logsum_bus = logsum(np.array([lower_utils_2, lower_utils_3]), nest_scale=nest_scale)\n", - "ev1_upper = inverse_ev1_cdf(rng_dec.uniform(size=2*num_draws_dec))\n", - "nest_util = nest_scale * logsum_bus + ev1_upper[num_draws_dec:]\n", - "\n", - "full_util_1 = util_1 + ev1_upper[:num_draws_dec]\n", - "\n", - "choices = np.array([full_util_1, nest_util]).argmax(axis=0)\n", - "nest_indexes = np.nonzero(choices == 1)[0]\n", - "nest_choices = np.array([lower_utils_2[nest_indexes], lower_utils_3[nest_indexes]]).argmax(axis=0)\n", - "nest_choices += 1\n", - "choices = np.append(choices[choices == 0], nest_choices)\n", - "\n", - "vals, counts = np.unique(choices, return_counts=True)\n", - "probs_dec = {i+1: counts[i] / num_draws_dec for i in vals}\n", - "\n", - "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", - "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index bfb9b39faf..8af08252eb 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -452,9 +452,41 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 39, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nest name: DRIVEALONEFREE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'DRIVEALONEFREE'] parent nest scale: 0.72\n", + "Nest name: DRIVEALONEPAY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'DRIVEALONEPAY'] parent nest scale: 0.72\n", + "Nest name: SHARED2FREE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED2FREE'] parent nest scale: 0.72\n", + "Nest name: SHARED2PAY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED2PAY'] parent nest scale: 0.72\n", + "Nest name: SHARED3FREE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED3FREE'] parent nest scale: 0.72\n", + "Nest name: SHARED3PAY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED3PAY'] parent nest scale: 0.72\n", + "Nest name: WALK level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'WALK'] parent nest scale: 0.72\n", + "Nest name: BIKE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'BIKE'] parent nest scale: 0.72\n", + "Nest name: WALK_LOC level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_LOC'] parent nest scale: 0.72\n", + "Nest name: WALK_LRF level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_LRF'] parent nest scale: 0.72\n", + "Nest name: WALK_EXP level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_EXP'] parent nest scale: 0.72\n", + "Nest name: WALK_HVY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_HVY'] parent nest scale: 0.72\n", + "Nest name: WALK_COM level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_COM'] parent nest scale: 0.72\n", + "Nest name: DRIVE_LOC level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_LOC'] parent nest scale: 0.72\n", + "Nest name: DRIVE_LRF level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_LRF'] parent nest scale: 0.72\n", + "Nest name: DRIVE_EXP level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_EXP'] parent nest scale: 0.72\n", + "Nest name: DRIVE_HVY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_HVY'] parent nest scale: 0.72\n", + "Nest name: DRIVE_COM level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_COM'] parent nest scale: 0.72\n", + "Nest name: TAXI level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TAXI'] parent nest scale: 0.36\n", + "Nest name: TNC_SINGLE level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SINGLE'] parent nest scale: 0.36\n", + "Nest name: TNC_SHARED level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SHARED'] parent nest scale: 0.36\n" + ] + } + ], + "source": [ + "nest_spec = ns[0]\n", + "for nest in logit.each_nest(nest_spec, type='leaf'):\n", + " nest.print()" + ], "metadata": { "collapsed": false, "pycharm": { @@ -540,7 +572,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 40, "outputs": [], "source": [ "def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples):\n", @@ -743,7 +775,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 41, "outputs": [ { "name": "stderr", @@ -786,9 +818,14 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "id": "1fcdc0ff-c510-4878-8672-8365e046442c", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%%\n", + "is_executing": true + } + }, "outputs": [ { "name": "stderr", @@ -803,22 +840,13 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (10000 trips)\n", - "Done\n" + "trip_mode_choice tour_type 'social' (100000 trips)\n" ] - }, - { - "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004262 4.103657e-14 0.000084 0.00025 0.000114 \ntrip_mode 0.000400 0.000000e+00 0.000000 0.00000 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.039196 0.19705 0.759044 \ntrip_mode 0.011600 0.20270 0.785300 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0042624.103657e-140.0000840.000250.0001140.0391960.197050.759044
trip_mode0.0004000.000000e+000.0000000.000000.0000000.0116000.202700.785300
\n
" - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ "choose_individual_max_utility = True\n", - "num_samples = 10000 # 1e5 takes about 25s\n", + "num_samples = 100000 # 1e5 takes about 25s\n", "trip_id_to_check = 642446345 # 2464104885 # 642446345 # 1767182945\n", "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" ] @@ -827,9 +855,7 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [ - "# something is still slightly off. WHY?" - ], + "source": [], "metadata": { "collapsed": false, "pycharm": { From b60702b438bc8612eda6a124b61af7d9569f1cca Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 6 May 2022 12:21:50 +1000 Subject: [PATCH 014/298] clean up --- activitysim/core/simulate.py | 2 +- notebooks/fru_utils.py | 193 +++++++++++ notebooks/validate_frozen_impl.ipynb | 467 +++------------------------ 3 files changed, 236 insertions(+), 426 deletions(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index f5667ef307..add013e668 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -737,7 +737,7 @@ def compute_nested_utilities(raw_utilities, nest_spec): name = nest.name if nest.is_leaf: # do not scale here, do afterwards so recursive structure works - nested_utilities[name] = raw_utilities[name].astype(float) #/ nest.coefficient + nested_utilities[name] = raw_utilities[name].astype(float) else: # the alternative nested_utilities will already have been computed due to post_order with np.errstate(divide='ignore'): diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py index 4ead449bb9..935e8b0289 100644 --- a/notebooks/fru_utils.py +++ b/notebooks/fru_utils.py @@ -18,6 +18,199 @@ from activitysim.core.util import assign_in_place + +def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples): + """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" + resume_after = "trip_scheduling" + model_name = "trip_mode_choice" + chunk_size = 0 # test_mtc means no chunking + + pipeline.open_pipeline(resume_after) + # preload any bulky injectables (e.g. skims) not in pipeline + inject.get_injectable('preload_injectables', None) + pipeline._PIPELINE.rng().begin_step(model_name) + #step_name = model_name + args = {} + #checkpoint = pipeline.intermediate_checkpoint(model_name) + inject.set_step_args(args) + + trips = inject.get_table('trips') + tours_merged = inject.get_table('tours_merged') + network_los = inject.get_injectable('network_los') + + trace_label = 'trip_mode_choice' + model_settings_file_name = 'trip_mode_choice.yaml' + model_settings = config.read_model_settings(model_settings_file_name) + + logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME') + mode_column_name = 'trip_mode' + + trips_df = trips.to_frame() + print("Running with %d trips", trips_df.shape[0]) + + tours_merged = tours_merged.to_frame() + tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] + + # - trips_merged - merge trips and tours_merged + trips_merged = pd.merge( + trips_df, + tours_merged, + left_on='tour_id', + right_index=True, + how="left") + assert trips_merged.index.equals(trips.index) + + # setup skim keys + assert ('trip_period' not in trips_merged) + trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) + + orig_col = 'origin' + dest_col = 'destination' + + constants = {} + constants.update(config.get_model_constants(model_settings)) + constants.update({ + 'ORIGIN': orig_col, + 'DESTINATION': dest_col + }) + + skim_dict = network_los.get_default_skim_dict() + + odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col, + dim3_key='trip_period') + dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col, + dim3_key='trip_period') + od_skim_wrapper = skim_dict.wrap('origin', 'destination') + + skims = { + "odt_skims": odt_skim_stack_wrapper, + "dot_skims": dot_skim_stack_wrapper, + "od_skims": od_skim_wrapper, + } + + model_spec = simulate.read_model_spec(file_name=model_settings['SPEC']) + nest_specs = config.get_logit_model_settings(model_settings) + + estimator = estimation.manager.begin_estimation('trip_mode_choice') + + #choices_list = [] + + + # grab one, duplicate num_samples times + trips_segment = trips_merged.loc[trips_merged.index == trip_id_to_check].copy() + primary_purpose = trips_segment['primary_purpose'].values[0] + trips_segment = trips_segment.loc[trips_segment.index.repeat(num_samples)] + + # need to add new row_states for rng here, need to ensure there are no collisions with existing keys + existing_indexes = pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values + num_new_indexes = trips_segment.shape[0] + new_indexes = np.arange(existing_indexes.max()+1, existing_indexes.max() + num_new_indexes + 1) + + trips_segment.index = new_indexes #+= np.arange(num_samples) + # name index so tracing knows how to slice + trips_segment.index.name = 'trip_id' + + + # #logger.warning("Change seeding back when done with testing") + # pipeline._PIPELINE.rng.row_states = pd.DataFrame(columns=['row_seed', 'offset'], index=trips_segment.index) + # pipeline._PIPELINE.rng.row_states["row_seed"] = trips_segment.index.values + # pipeline._PIPELINE.rng.row_states["offset"] = 0 + pipeline._PIPELINE.rng().add_channel("trips", trips_segment) + + + #for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'): + #if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes): + # continue + + print("trip_mode_choice tour_type '%s' (%s trips)" % + (primary_purpose, len(trips_segment.index), )) + + coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose) + + locals_dict = {} + locals_dict.update(constants) + locals_dict.update(coefficients) + + segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose) + + expressions.annotate_preprocessors( + trips_segment, locals_dict, skims, + model_settings, segment_trace_label) + + locals_dict.update(skims) + + ################ Replace wrapper function + # choices = mode_choice_simulate(...) + spec=simulate.eval_coefficients(model_spec, coefficients, estimator) + nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label) + choices = simulate.simple_simulate( + choosers=trips_segment, + spec=spec, + nest_spec=nest_spec, + skims=skims, + locals_d=locals_dict, + chunk_size=chunk_size, + want_logsums=logsum_column_name is not None, + trace_label=segment_trace_label, + trace_choice_name='trip_mode_choice', + estimator=estimator, + trace_column_names=None, + choose_individual_max_utility=choose_individual_max_utility) + # for consistency, always return dataframe, whether or not logsums were requested + if isinstance(choices, pd.Series): + choices = choices.to_frame('choice') + choices.rename(columns={'logsum': logsum_column_name, + 'choice': mode_column_name}, + inplace=True) + if not choose_individual_max_utility: + alts = spec.columns + choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) + ################ + #choices_list.append(choices) + #choices_df_asim = pd.concat(choices_list) + + # update trips table with choices (and potionally logssums) + #trips_df = trips_merged.copy() # trips.to_frame() + + #if (do_these_purposes is not None): + # trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)] + + #assign_in_place(trips_df, choices) + #assert not trips_df[mode_column_name].isnull().any() + + finalise = True + if finalise: + inject.set_step_args(None) + # + pipeline._PIPELINE.rng().end_step(model_name) + pipeline.add_checkpoint(model_name) + if not pipeline.intermediate_checkpoint(): + pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME) + + pipeline.close_pipeline() + + print("Done") + return trips_merged, choices + + +def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check): + t_, c_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, + trip_id_to_check=trip_id_to_check, num_samples=num_samples) + + sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0] + #sim_mode_shares.columns = ["mode_share_sim"] + obs_probs = base_probs[0].loc[base_probs[0].index == trip_id_to_check].T + obs_probs.columns = ["mode_share_obs"] + ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how="outer").fillna(0) + ms_comp["diff"] = ms_comp["trip_mode"] - ms_comp["mode_share_obs"] + ms_comp["rel_diff"] = ms_comp["diff"] / ms_comp["mode_share_obs"] + #ms_comp.style.format('{:.2}') + #with pd.option_context("precision", 3): + # display(ms_comp) + return ms_comp + + + def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=True): """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 8af08252eb..58b7fae0c4 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 56, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -72,15 +72,8 @@ "\n", "from activitysim.cli import run\n", "from activitysim.core import inject\n", - "from activitysim.core import tracing\n", - "from activitysim.core import config\n", - "from activitysim.core import pipeline\n", - "from activitysim.core import chunk\n", - "from activitysim.core import simulate\n", - "from activitysim.core import logit\n", - "from activitysim.abm.models.util import estimation\n", - "from activitysim.core import expressions\n", - "from activitysim.core.util import assign_in_place" + "\n", + "from fru_utils import run_trip_mode_choice, mode_choice_for_trip" ] }, { @@ -170,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 57, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -219,7 +212,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Done\n" + "Done\n", + "Running with %d trips 482\n" ] }, { @@ -233,7 +227,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", "trip_mode_choice tour_type 'escort' (6 trips)\n", @@ -249,21 +242,20 @@ } ], "source": [ - "from fru_utils import run_trip_mode_choice\n", "trips_df_ru = run_trip_mode_choice()\n", "trips_df = run_trip_mode_choice(choose_individual_max_utility=False)" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 58, "outputs": [ { "data": { - "text/plain": " trip_mode_fru trip_mode_asim\nWALK 299 304\nWALK_LOC 94 85\nWALK_LRF 57 53\nBIKE 17 17\nDRIVEALONEFREE 6 4\nWALK_HVY 4 4\nSHARED2FREE 3 4\nTNC_SINGLE 2 4", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK299304
WALK_LOC9485
WALK_LRF5753
BIKE1717
DRIVEALONEFREE64
WALK_HVY44
SHARED2FREE34
TNC_SINGLE24
\n
" + "text/plain": " trip_mode_fru trip_mode_asim\nWALK 332 304\nWALK_LOC 72 85\nWALK_LRF 45 53\nBIKE 16 17\nDRIVEALONEFREE 7 4\nWALK_HVY 4 4\nTNC_SINGLE 3 4\nSHARED2FREE 2 4\nTNC_SHARED 1 4", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK332304
WALK_LOC7285
WALK_LRF4553
BIKE1617
DRIVEALONEFREE74
WALK_HVY44
TNC_SINGLE34
SHARED2FREE24
TNC_SHARED14
\n
" }, - "execution_count": 9, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -283,14 +275,17 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 59, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logsums agree\n" - ] + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" } ], "source": [ @@ -306,20 +301,6 @@ } } }, - { - "cell_type": "code", - "execution_count": 12, - "outputs": [], - "source": [ - "#simulate.compute_nested_utilities(ru[0], ns[0])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, { "cell_type": "markdown", "source": [ @@ -339,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 60, "outputs": [ { "name": "stderr", @@ -372,169 +353,12 @@ }, { "cell_type": "code", - "execution_count": 27, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root'] parent nest scale: 0\n", - "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] parent nest scale: 1.0\n", - "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED'] parent nest scale: 1.0\n", - "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT'] parent nest scale: 1.0\n", - "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL'] parent nest scale: 1.0\n" - ] - } - ], - "source": [ - "nest_spec = ns[0]\n", - "for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", - " nest.print()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 28, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "leaf DRIVEALONEFREE with 0.72, 0.72\n", - "leaf DRIVEALONEPAY with 0.72, 0.72\n", - "leaf SHARED2FREE with 0.72, 0.72\n", - "leaf SHARED2PAY with 0.72, 0.72\n", - "leaf SHARED3FREE with 0.72, 0.72\n", - "leaf SHARED3PAY with 0.72, 0.72\n", - "node AUTO with 0.72, 1.0\n", - "leaf WALK with 0.72, 0.72\n", - "leaf BIKE with 0.72, 0.72\n", - "node NONMOTORIZED with 0.72, 1.0\n", - "leaf WALK_LOC with 0.72, 0.72\n", - "leaf WALK_LRF with 0.72, 0.72\n", - "leaf WALK_EXP with 0.72, 0.72\n", - "leaf WALK_HVY with 0.72, 0.72\n", - "leaf WALK_COM with 0.72, 0.72\n", - "leaf DRIVE_LOC with 0.72, 0.72\n", - "leaf DRIVE_LRF with 0.72, 0.72\n", - "leaf DRIVE_EXP with 0.72, 0.72\n", - "leaf DRIVE_HVY with 0.72, 0.72\n", - "leaf DRIVE_COM with 0.72, 0.72\n", - "node TRANSIT with 0.72, 1.0\n", - "leaf TAXI with 0.36, 0.36\n", - "leaf TNC_SINGLE with 0.36, 0.36\n", - "leaf TNC_SHARED with 0.36, 0.36\n", - "node RIDEHAIL with 0.36, 1.0\n", - "node root with 1.0, 0\n" - ] - } - ], - "source": [ - "for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " print(f\"leaf {name} with {nest.product_of_coefficients}, {nest.parent_nest_scale}\")\n", - " else:\n", - " print(f\"node {name} with {nest.coefficient}, {nest.parent_nest_scale}\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 39, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nest name: DRIVEALONEFREE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'DRIVEALONEFREE'] parent nest scale: 0.72\n", - "Nest name: DRIVEALONEPAY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'DRIVEALONEPAY'] parent nest scale: 0.72\n", - "Nest name: SHARED2FREE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED2FREE'] parent nest scale: 0.72\n", - "Nest name: SHARED2PAY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED2PAY'] parent nest scale: 0.72\n", - "Nest name: SHARED3FREE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED3FREE'] parent nest scale: 0.72\n", - "Nest name: SHARED3PAY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO', 'SHARED3PAY'] parent nest scale: 0.72\n", - "Nest name: WALK level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'WALK'] parent nest scale: 0.72\n", - "Nest name: BIKE level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'BIKE'] parent nest scale: 0.72\n", - "Nest name: WALK_LOC level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_LOC'] parent nest scale: 0.72\n", - "Nest name: WALK_LRF level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_LRF'] parent nest scale: 0.72\n", - "Nest name: WALK_EXP level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_EXP'] parent nest scale: 0.72\n", - "Nest name: WALK_HVY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_HVY'] parent nest scale: 0.72\n", - "Nest name: WALK_COM level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'WALK_COM'] parent nest scale: 0.72\n", - "Nest name: DRIVE_LOC level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_LOC'] parent nest scale: 0.72\n", - "Nest name: DRIVE_LRF level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_LRF'] parent nest scale: 0.72\n", - "Nest name: DRIVE_EXP level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_EXP'] parent nest scale: 0.72\n", - "Nest name: DRIVE_HVY level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_HVY'] parent nest scale: 0.72\n", - "Nest name: DRIVE_COM level: 3 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT', 'DRIVE_COM'] parent nest scale: 0.72\n", - "Nest name: TAXI level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TAXI'] parent nest scale: 0.36\n", - "Nest name: TNC_SINGLE level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SINGLE'] parent nest scale: 0.36\n", - "Nest name: TNC_SHARED level: 3 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SHARED'] parent nest scale: 0.36\n" - ] - } - ], - "source": [ - "nest_spec = ns[0]\n", - "for nest in logit.each_nest(nest_spec, type='leaf'):\n", - " nest.print()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 49, "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 29, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.262023e-03 0.0 \n642446349 0.0 0.0 3.138876e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.339899e-02 0.0 \n1767182949 0.0 0.0 1.282607e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.103657e-14 0.0 0.039196 0.0 0.197050 0.759044 \n642446349 3.423474e-14 0.0 0.037574 0.0 0.208612 0.750231 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.288763e-13 0.0 0.038976 0.0 0.282647 0.660225 \n1767182949 1.256775e-13 0.0 0.034777 0.0 0.280877 0.666774 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 8.388831e-05 1.141304e-04 2.502967e-04 \n642446349 0.0 0.0 8.053321e-05 1.140970e-04 2.500402e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.247409e-03 2.795129e-03 7.103828e-04 \n1767182949 0.0 0.0 1.229459e-03 2.813489e-03 7.024828e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.262023e-030.04.103657e-140.00.0391960.00.1970500.7590440.00.00.00.00.00.00.00.08.388831e-051.141304e-042.502967e-04
6424463490.00.03.138876e-030.03.423474e-140.00.0375740.00.2086120.7502310.00.00.00.00.00.00.00.08.053321e-051.140970e-042.500402e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.339899e-020.01.288763e-130.00.0389760.00.2826470.6602250.00.00.00.00.00.00.00.01.247409e-032.795129e-037.103828e-04
17671829490.00.01.282607e-020.01.256775e-130.00.0347770.00.2808770.6667740.00.00.00.00.00.00.00.01.229459e-032.813489e-037.024828e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "# nested_probs[0] - these are relative in each nest, so the full probs are potentially just the product of these\n", - "base_probs[0]" + "# nest_spec = ns[0]\n", + "# for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", + "# nest.print()" ], "metadata": { "collapsed": false, @@ -545,226 +369,10 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 62, "outputs": [], "source": [ - "# for later: this seems to be a non-driving segment - grab workers or something later?\n", - "# for now: implement a run with seeding so I can reproduce these counts\n", - "\n", - "# actually, can I manipulate pipeline OR inject table dynamically? If I reproduce a single observation many times we\n", - "# can check mode shares\n", - "# 1) check tests\n", - "# 2) check code in mode choice, maybe duplicate here, see old code below" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": 40, - "outputs": [], - "source": [ - "def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples):\n", - " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", - " resume_after = \"trip_scheduling\"\n", - " model_name = \"trip_mode_choice\"\n", - " chunk_size = 0 # test_mtc means no chunking\n", - "\n", - " pipeline.open_pipeline(resume_after)\n", - " # preload any bulky injectables (e.g. skims) not in pipeline\n", - " inject.get_injectable('preload_injectables', None)\n", - " pipeline._PIPELINE.rng().begin_step(model_name)\n", - " #step_name = model_name\n", - " args = {}\n", - " #checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - " inject.set_step_args(args)\n", - "\n", - " trips = inject.get_table('trips')\n", - " tours_merged = inject.get_table('tours_merged')\n", - " network_los = inject.get_injectable('network_los')\n", - "\n", - " trace_label = 'trip_mode_choice'\n", - " model_settings_file_name = 'trip_mode_choice.yaml'\n", - " model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - " mode_column_name = 'trip_mode'\n", - "\n", - " trips_df = trips.to_frame()\n", - " print(\"Running with %d trips\", trips_df.shape[0])\n", - "\n", - " tours_merged = tours_merged.to_frame()\n", - " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "\n", - " # - trips_merged - merge trips and tours_merged\n", - " trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - " assert trips_merged.index.equals(trips.index)\n", - "\n", - " # setup skim keys\n", - " assert ('trip_period' not in trips_merged)\n", - " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - " orig_col = 'origin'\n", - " dest_col = 'destination'\n", - "\n", - " constants = {}\n", - " constants.update(config.get_model_constants(model_settings))\n", - " constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - " })\n", - "\n", - " skim_dict = network_los.get_default_skim_dict()\n", - "\n", - " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "\n", - " skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - " }\n", - "\n", - " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - " nest_specs = config.get_logit_model_settings(model_settings)\n", - "\n", - " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - " #choices_list = []\n", - "\n", - "\n", - " # grab one, duplicate num_samples times\n", - " trips_segment = trips_merged.loc[trips_merged.index == trip_id_to_check].copy()\n", - " primary_purpose = trips_segment['primary_purpose'].values[0]\n", - " trips_segment = trips_segment.loc[trips_segment.index.repeat(num_samples)]\n", - "\n", - " # need to add new row_states for rng here, need to ensure there are no collisions with existing keys\n", - " existing_indexes = pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values\n", - " num_new_indexes = trips_segment.shape[0]\n", - " new_indexes = np.arange(existing_indexes.max()+1, existing_indexes.max() + num_new_indexes + 1)\n", - "\n", - " trips_segment.index = new_indexes #+= np.arange(num_samples)\n", - " # name index so tracing knows how to slice\n", - " trips_segment.index.name = 'trip_id'\n", - "\n", - "\n", - " # #logger.warning(\"Change seeding back when done with testing\")\n", - " # pipeline._PIPELINE.rng.row_states = pd.DataFrame(columns=['row_seed', 'offset'], index=trips_segment.index)\n", - " # pipeline._PIPELINE.rng.row_states[\"row_seed\"] = trips_segment.index.values\n", - " # pipeline._PIPELINE.rng.row_states[\"offset\"] = 0\n", - " pipeline._PIPELINE.rng().add_channel(\"trips\", trips_segment)\n", - "\n", - "\n", - " #for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", - " # continue\n", - "\n", - " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " (primary_purpose, len(trips_segment.index), ))\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " ################ Replace wrapper function\n", - " # choices = mode_choice_simulate(...)\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", - " choices = simulate.simple_simulate(\n", - " choosers=trips_segment,\n", - " spec=spec,\n", - " nest_spec=nest_spec,\n", - " skims=skims,\n", - " locals_d=locals_dict,\n", - " chunk_size=chunk_size,\n", - " want_logsums=logsum_column_name is not None,\n", - " trace_label=segment_trace_label,\n", - " trace_choice_name='trip_mode_choice',\n", - " estimator=estimator,\n", - " trace_column_names=None,\n", - " choose_individual_max_utility=choose_individual_max_utility)\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " if not choose_individual_max_utility:\n", - " alts = spec.columns\n", - " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", - " ################\n", - " #choices_list.append(choices)\n", - " #choices_df_asim = pd.concat(choices_list)\n", - "\n", - " # update trips table with choices (and potionally logssums)\n", - " #trips_df = trips_merged.copy() # trips.to_frame()\n", - "\n", - " #if (do_these_purposes is not None):\n", - " # trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", - "\n", - " #assign_in_place(trips_df, choices)\n", - " #assert not trips_df[mode_column_name].isnull().any()\n", - "\n", - " finalise = True\n", - " if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - " print(\"Done\")\n", - " return trips_merged, choices\n", - "\n", - "\n", - "def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check):\n", - " t_, c_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility,\n", - " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", - "\n", - " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", - " #sim_mode_shares.columns = [\"mode_share_sim\"]\n", - " obs_probs = base_probs[0].loc[base_probs[0].index == trip_id_to_check].T\n", - " obs_probs.columns = [\"mode_share_obs\"]\n", - " ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how=\"outer\").fillna(0)\n", - " ms_comp[\"diff\"] = ms_comp[\"trip_mode\"] - ms_comp[\"mode_share_obs\"]\n", - " ms_comp[\"rel_diff\"] = ms_comp[\"diff\"] / ms_comp[\"mode_share_obs\"]\n", - " #ms_comp.style.format('{:.2}')\n", - " #with pd.option_context(\"precision\", 3):\n", - " # display(ms_comp)\n", - " display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)\n", - " return ms_comp" + "#base_probs[0]" ], "metadata": { "collapsed": false, @@ -775,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 63, "outputs": [ { "name": "stderr", @@ -796,8 +404,16 @@ }, { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004262 4.103657e-14 0.000084 0.00025 0.000114 \ntrip_mode 0.004300 0.000000e+00 0.000100 0.00050 0.000100 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.039196 0.19705 0.759044 \ntrip_mode 0.039100 0.20000 0.755900 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0042624.103657e-140.0000840.000250.0001140.0391960.197050.759044
trip_mode0.0043000.000000e+000.0001000.000500.0001000.0391000.200000.755900
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.004800 0.000000e+00 0.000000 0.000500 0.000200 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.044300 0.060600 0.889600 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0048000.000000e+000.0000000.0005000.0002000.0443000.0606000.889600
\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.004800 0.000000e+00 0.000000 0.000500 0.000200 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.044300 0.060600 0.889600 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0048000.000000e+000.0000000.0005000.0002000.0443000.0606000.889600
\n
" }, "metadata": {}, "output_type": "display_data" @@ -807,7 +423,8 @@ "choose_individual_max_utility = False\n", "num_samples = 10000\n", "trip_id_to_check = 642446345\n", - "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" + "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)\n", + "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" ], "metadata": { "collapsed": false, @@ -840,14 +457,14 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (100000 trips)\n" + "trip_mode_choice tour_type 'social' (10000 trips)\n" ] } ], "source": [ "choose_individual_max_utility = True\n", - "num_samples = 100000 # 1e5 takes about 25s\n", - "trip_id_to_check = 642446345 # 2464104885 # 642446345 # 1767182945\n", + "num_samples = 10000 # 1e5 takes about 25s\n", + "trip_id_to_check = 1767182945 # 2464104885 # 642446345 # 1767182945\n", "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" ] }, From 7c21407cfd2d667a3bf45f7c3f5ea5a830ca3750 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 6 May 2022 13:27:26 +1000 Subject: [PATCH 015/298] adds nested utility scale --- activitysim/core/logit.py | 3 + activitysim/core/simulate.py | 8 +- notebooks/validate_frozen_impl.ipynb | 137 +++++++++++++++++++-------- 3 files changed, 105 insertions(+), 43 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 048b37da42..0e7feb33a1 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -372,6 +372,7 @@ def __init__(self, name=None, level=0): self.ancestors = [] self.alternatives = None self.coefficient = 0 + self.parent_scale = 1 def print(self): print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s " % @@ -446,6 +447,7 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients * coefficient nest.alternatives = alternatives nest.ancestors = parent_nest.ancestors + [name] + nest.parent_scale = parent_nest.coefficient if pre_order: yield spec, nest @@ -466,6 +468,7 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients nest.ancestors = parent_nest.ancestors + [name] nest.coefficient = parent_nest.coefficient + nest.parent_scale = parent_nest.coefficient yield spec, nest diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index add013e668..b3e64abb65 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -746,8 +746,12 @@ def compute_nested_utilities(raw_utilities, nest_spec): np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1) ) - for nest in logit.each_nest(nest_spec, type="leaf"): - nested_utilities[nest.name] /= nest.coefficient + + # calculate scaled utilties, as would be used for probability calculations + for nest in logit.each_nest(nest_spec): + if nest.level == 1: + continue # root does not have parent + nested_utilities[nest.name] /= nest.parent_scale return nested_utilities diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 58b7fae0c4..4fd1c37a86 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 88, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -73,12 +73,12 @@ "from activitysim.cli import run\n", "from activitysim.core import inject\n", "\n", - "from fru_utils import run_trip_mode_choice, mode_choice_for_trip" + "from fru_utils import run_trip_mode_choice, comp_mode_shares" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 89, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -97,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 90, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -117,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 91, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -136,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 92, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 93, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -212,8 +212,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Done\n", - "Running with %d trips 482\n" + "Done\n" ] }, { @@ -227,6 +226,7 @@ "name": "stdout", "output_type": "stream", "text": [ + "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", "trip_mode_choice tour_type 'escort' (6 trips)\n", @@ -237,25 +237,28 @@ "trip_mode_choice tour_type 'social' (19 trips)\n", "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n" + "Done\n", + "CPU times: user 21.5 s, sys: 2.27 s, total: 23.8 s\n", + "Wall time: 21.1 s\n" ] } ], "source": [ + "%%time\n", "trips_df_ru = run_trip_mode_choice()\n", "trips_df = run_trip_mode_choice(choose_individual_max_utility=False)" ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 94, "outputs": [ { "data": { - "text/plain": " trip_mode_fru trip_mode_asim\nWALK 332 304\nWALK_LOC 72 85\nWALK_LRF 45 53\nBIKE 16 17\nDRIVEALONEFREE 7 4\nWALK_HVY 4 4\nTNC_SINGLE 3 4\nSHARED2FREE 2 4\nTNC_SHARED 1 4", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK332304
WALK_LOC7285
WALK_LRF4553
BIKE1617
DRIVEALONEFREE74
WALK_HVY44
TNC_SINGLE34
SHARED2FREE24
TNC_SHARED14
\n
" + "text/plain": " trip_mode_fru trip_mode_asim\nWALK 297 304\nWALK_LOC 100 85\nWALK_LRF 52 53\nBIKE 16 17\nDRIVEALONEFREE 7 4\nWALK_HVY 4 4\nTNC_SINGLE 3 4\nSHARED2FREE 2 4\nTNC_SHARED 1 4", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK297304
WALK_LOC10085
WALK_LRF5253
BIKE1617
DRIVEALONEFREE74
WALK_HVY44
TNC_SINGLE34
SHARED2FREE24
TNC_SHARED14
\n
" }, - "execution_count": 58, + "execution_count": 94, "metadata": {}, "output_type": "execute_result" } @@ -275,7 +278,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 95, "outputs": [ { "data": { @@ -301,6 +304,30 @@ } } }, + { + "cell_type": "code", + "execution_count": 96, + "outputs": [ + { + "data": { + "text/plain": "trip_id\n535694417 0.143382\n943749469 0.109040\n1004301761 0.122286\n1767182865 0.119168\n1767183297 0.138448\n1767186581 0.111968\ndtype: float64" + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x_ = np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim)\n", + "x_.loc[x_ > 0.1]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "markdown", "source": [ @@ -320,7 +347,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 104, "outputs": [ { "name": "stderr", @@ -353,12 +380,29 @@ }, { "cell_type": "code", - "execution_count": 49, - "outputs": [], + "execution_count": 105, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root'] \n", + "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] \n", + "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] \n", + "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2'] \n", + "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3'] \n", + "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED'] \n", + "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT'] \n", + "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS'] \n", + "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS'] \n", + "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL'] \n" + ] + } + ], "source": [ - "# nest_spec = ns[0]\n", - "# for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", - "# nest.print()" + "nest_spec = ns[0]\n", + "for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", + " nest.print()" ], "metadata": { "collapsed": false, @@ -369,10 +413,20 @@ }, { "cell_type": "code", - "execution_count": 62, - "outputs": [], + "execution_count": 106, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "#base_probs[0]" + "base_probs[0]" ], "metadata": { "collapsed": false, @@ -383,7 +437,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 100, "outputs": [ { "name": "stderr", @@ -404,16 +458,8 @@ }, { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.004800 0.000000e+00 0.000000 0.000500 0.000200 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.044300 0.060600 0.889600 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0048000.000000e+000.0000000.0005000.0002000.0443000.0606000.889600
\n
" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.004800 0.000000e+00 0.000000 0.000500 0.000200 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.044300 0.060600 0.889600 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0048000.000000e+000.0000000.0005000.0002000.0443000.0606000.889600
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED \\\nmode_share_obs 0.000022 2.084422e-16 1.325277e-07 3.433307e-07 \ntrip_mode 0.000000 0.000000e+00 0.000000e+00 0.000000e+00 \n\n TNC_SINGLE WALK WALK_LOC \nmode_share_obs 8.507634e-12 0.627973 0.372005 \ntrip_mode 0.000000e+00 0.620900 0.379100 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOC
mode_share_obs0.0000222.084422e-161.325277e-073.433307e-078.507634e-120.6279730.372005
trip_mode0.0000000.000000e+000.000000e+000.000000e+000.000000e+000.6209000.379100
\n
" }, "metadata": {}, "output_type": "display_data" @@ -422,7 +468,7 @@ "source": [ "choose_individual_max_utility = False\n", "num_samples = 10000\n", - "trip_id_to_check = 642446345\n", + "trip_id_to_check = 2464104885\n", "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)\n", "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" ], @@ -435,12 +481,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 107, "id": "1fcdc0ff-c510-4878-8672-8365e046442c", "metadata": { "pycharm": { - "name": "#%%\n", - "is_executing": true + "name": "#%%\n" } }, "outputs": [ @@ -457,15 +502,25 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (10000 trips)\n" + "trip_mode_choice tour_type 'social' (10000 trips)\n", + "Done\n" ] + }, + { + "data": { + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.016101 1.548643e-13 0.001499 0.000854 0.003359 \ntrip_mode 0.000000 0.000000e+00 0.000000 0.000000 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.046835 0.144256 0.787096 \ntrip_mode 0.012600 0.223600 0.763800 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0161011.548643e-130.0014990.0008540.0033590.0468350.1442560.787096
trip_mode0.0000000.000000e+000.0000000.0000000.0000000.0126000.2236000.763800
\n
" + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ "choose_individual_max_utility = True\n", "num_samples = 10000 # 1e5 takes about 25s\n", "trip_id_to_check = 1767182945 # 2464104885 # 642446345 # 1767182945\n", - "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)" + "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)\n", + "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" ] }, { From 932d5adc13776a1f2d7a44381ac46cbe184fa529 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 6 May 2022 16:41:13 +1000 Subject: [PATCH 016/298] scaling in recursive nest utilities --- activitysim/core/simulate.py | 41 ++- notebooks/validate_frozen_impl.ipynb | 411 +++++++++++++++++++++++---- 2 files changed, 386 insertions(+), 66 deletions(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index b3e64abb65..7bdb5ed7e2 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -743,15 +743,10 @@ def compute_nested_utilities(raw_utilities, nest_spec): with np.errstate(divide='ignore'): nested_utilities[name] = \ nest.coefficient * np.log( - np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1) - ) + np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) - - # calculate scaled utilties, as would be used for probability calculations - for nest in logit.each_nest(nest_spec): - if nest.level == 1: - continue # root does not have parent - nested_utilities[nest.name] /= nest.parent_scale + if nest.level > 1: # root does not have parent. also, parent_scale seems to be 0. + nested_utilities[name] = nested_utilities[name] / nest.parent_scale return nested_utilities @@ -1042,6 +1037,36 @@ def eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, estima tracing.trace_df(nested_utilities, '%s.nested_utilities' % trace_label, column_labels=['alternative', 'utility']) + + ########### comparison only + # probabilities of alternatives relative to siblings sharing the same nest + nested_probabilities = \ + compute_nested_probabilities(np.exp(nested_utilities), nest_spec, trace_label=trace_label) + chunk.log_df(trace_label, "nested_probabilities", nested_probabilities) + + if want_logsums: + # logsum of nest root + logsums = pd.Series(nested_utilities.root, index=choosers.index) + chunk.log_df(trace_label, "logsums", logsums) + + if have_trace_targets: + tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label, + column_labels=['alternative', 'probability']) + + base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) + chunk.log_df(trace_label, "base_probabilities", base_probabilities) + + del nested_probabilities + chunk.log_df(trace_label, "nested_probabilities", None) + + if have_trace_targets: + tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label, + column_labels=['alternative', 'probability']) + + del(base_probabilities) + chunk.log_df(trace_label, "base_probabilities", None) + ########### end comparison only + # TODO - check this is correct if want_logsums: # logsum of nest root diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 4fd1c37a86..ec136d150d 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 191, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { "execution": { @@ -22,14 +22,23 @@ }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 192, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { "execution": { @@ -48,7 +57,7 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": 193, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -78,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 194, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -97,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 195, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -117,7 +126,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 196, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -136,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 197, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -163,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": 198, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -205,20 +214,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -226,6 +222,7 @@ "name": "stdout", "output_type": "stream", "text": [ + "Done\n", "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", @@ -238,8 +235,8 @@ "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n", - "CPU times: user 21.5 s, sys: 2.27 s, total: 23.8 s\n", - "Wall time: 21.1 s\n" + "CPU times: user 23.4 s, sys: 2.09 s, total: 25.5 s\n", + "Wall time: 24 s\n" ] } ], @@ -251,14 +248,14 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": 184, "outputs": [ { "data": { "text/plain": " trip_mode_fru trip_mode_asim\nWALK 297 304\nWALK_LOC 100 85\nWALK_LRF 52 53\nBIKE 16 17\nDRIVEALONEFREE 7 4\nWALK_HVY 4 4\nTNC_SINGLE 3 4\nSHARED2FREE 2 4\nTNC_SHARED 1 4", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK297304
WALK_LOC10085
WALK_LRF5253
BIKE1617
DRIVEALONEFREE74
WALK_HVY44
TNC_SINGLE34
SHARED2FREE24
TNC_SHARED14
\n
" }, - "execution_count": 94, + "execution_count": 184, "metadata": {}, "output_type": "execute_result" } @@ -278,7 +275,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 185, "outputs": [ { "data": { @@ -306,13 +303,97 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 200, + "outputs": [ + { + "data": { + "text/plain": "trip_id\n106170305 4.050638\n106170309 0.553496\n1767013721 0.482067\n535694461 0.251889\n943749469 0.203583\n ... \n535672921 0.000000\n535620049 0.000000\n535388485 0.000000\n535388481 0.000000\n943749471 0.000000\nLength: 482, dtype: float64" + }, + "execution_count": 200, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ls_ = np.abs(((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru))\n", + "ls_.sort_values(ascending=False)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 201, + "outputs": [ + { + "data": { + "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n106170305 WALK_LRF -0.002341 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n106170305 -0.011825 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
106170305WALK_LRF-0.002341WALK_LRF-0.011825
\n
" + }, + "execution_count": 201, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trips.loc[trips.index == 106170305]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 210, + "outputs": [ + { + "data": { + "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n86627409 WALK_LRF 2.435199 WALK_LRF \n86627413 WALK_LRF 2.665463 WALK_LRF \n86673661 WALK_LRF 1.842576 WALK_LRF \n106170305 WALK_LRF -0.002341 WALK_LRF \n106170309 WALK_LRF -0.017607 WALK_LRF \n... ... ... ... \n1767186253 WALK_LRF 0.976319 WALK_LRF \n1767186581 WALK_HVY 1.712704 WALK_LRF \n1768237161 WALK 6.262944 WALK \n1768237167 WALK_LRF 11.191330 WALK \n2473024477 WALK_LRF 0.637076 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n86627409 2.407248 \n86627413 2.642707 \n86673661 1.834538 \n106170305 -0.011825 \n106170309 -0.027353 \n... ... \n1767186253 0.880819 \n1767186581 1.600736 \n1768237161 6.226927 \n1768237167 11.144496 \n2473024477 0.624304 \n\n[74 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
86627409WALK_LRF2.435199WALK_LRF2.407248
86627413WALK_LRF2.665463WALK_LRF2.642707
86673661WALK_LRF1.842576WALK_LRF1.834538
106170305WALK_LRF-0.002341WALK_LRF-0.011825
106170309WALK_LRF-0.017607WALK_LRF-0.027353
...............
1767186253WALK_LRF0.976319WALK_LRF0.880819
1767186581WALK_HVY1.712704WALK_LRF1.600736
1768237161WALK6.262944WALK6.226927
1768237167WALK_LRF11.191330WALK11.144496
2473024477WALK_LRF0.637076WALK_LRF0.624304
\n

74 rows × 4 columns

\n
" + }, + "execution_count": 210, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 141, "outputs": [ { "data": { "text/plain": "trip_id\n535694417 0.143382\n943749469 0.109040\n1004301761 0.122286\n1767182865 0.119168\n1767183297 0.138448\n1767186581 0.111968\ndtype: float64" }, - "execution_count": 96, + "execution_count": 141, "metadata": {}, "output_type": "execute_result" } @@ -347,7 +428,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 207, "outputs": [ { "name": "stderr", @@ -362,14 +443,23 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", + "trip_mode_choice tour_type 'atwork' (27 trips)\n", + "trip_mode_choice tour_type 'eatout' (33 trips)\n", + "trip_mode_choice tour_type 'escort' (6 trips)\n", + "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", + "trip_mode_choice tour_type 'othmaint' (46 trips)\n", + "trip_mode_choice tour_type 'school' (37 trips)\n", + "trip_mode_choice tour_type 'shopping' (77 trips)\n", "trip_mode_choice tour_type 'social' (19 trips)\n", + "trip_mode_choice tour_type 'univ' (26 trips)\n", + "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n" ] } ], "source": [ "from fru_utils import get_stuff\n", - "t, ru, neu, nu, ns, nested_probs, base_probs = get_stuff(do_these_purposes=[\"social\"])" + "t, ru, neu, nu, ns, nested_probs, base_probs = get_stuff() #do_these_purposes=[\"social\"]) #social" ], "metadata": { "collapsed": false, @@ -380,29 +470,20 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 209, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root'] \n", - "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] \n", - "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] \n", - "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2'] \n", - "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3'] \n", - "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED'] \n", - "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT'] \n", - "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS'] \n", - "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS'] \n", - "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL'] \n" - ] + "data": { + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n106170305 323689 256660 work 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n106170305 1 1 10 13271288 work \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n106170305 NaN 11.0 WALK_LRF -0.011825 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
106170305323689256660work1True111013271288workNaN11.0WALK_LRF-0.011825
\n
" + }, + "execution_count": 209, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "nest_spec = ns[0]\n", - "for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", - " nest.print()" + "t.loc[t.index == 106170305]" ], "metadata": { "collapsed": false, @@ -413,20 +494,48 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 187, + "outputs": [], + "source": [ + "# nest_spec = ns[0]\n", + "# for nest in logit.each_nest(nest_spec, post_order=False):\n", + "# print(f\"{nest.level}, {nest.name}, {nest.coefficient}, parent scale: {nest.parent_scale}\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 188, "outputs": [ { "data": { "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" }, - "execution_count": 106, + "execution_count": 188, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "base_probs[0]" + "base_probs[0] #615236801" ], "metadata": { "collapsed": false, @@ -437,7 +546,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 189, "outputs": [ { "name": "stderr", @@ -481,7 +590,7 @@ }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 190, "id": "1fcdc0ff-c510-4878-8672-8365e046442c", "metadata": { "pycharm": { @@ -508,8 +617,8 @@ }, { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.016101 1.548643e-13 0.001499 0.000854 0.003359 \ntrip_mode 0.000000 0.000000e+00 0.000000 0.000000 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.046835 0.144256 0.787096 \ntrip_mode 0.012600 0.223600 0.763800 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0161011.548643e-130.0014990.0008540.0033590.0468350.1442560.787096
trip_mode0.0000000.000000e+000.0000000.0000000.0000000.0126000.2236000.763800
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED \\\nmode_share_obs 0.000022 2.084422e-16 1.325277e-07 3.433307e-07 \ntrip_mode 0.000000 0.000000e+00 0.000000e+00 0.000000e+00 \n\n TNC_SINGLE WALK WALK_LOC \nmode_share_obs 8.507634e-12 0.627973 0.372005 \ntrip_mode 0.000000e+00 0.673100 0.326900 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOC
mode_share_obs0.0000222.084422e-161.325277e-073.433307e-078.507634e-120.6279730.372005
trip_mode0.0000000.000000e+000.000000e+000.000000e+000.000000e+000.6731000.326900
\n
" }, "metadata": {}, "output_type": "display_data" @@ -518,11 +627,197 @@ "source": [ "choose_individual_max_utility = True\n", "num_samples = 10000 # 1e5 takes about 25s\n", - "trip_id_to_check = 1767182945 # 2464104885 # 642446345 # 1767182945\n", + "trip_id_to_check = 2464104885\n", + "# work 615236801\n", + "# social 2464104885 642446345 1767182945\n", "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)\n", "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" ] }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 160, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# stuff\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 172, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [982875].\n" + ] + } + ], + "source": [ + "from activitysim.core import pipeline\n", + "\n", + "resume_after = \"trip_scheduling\"\n", + "model_name = \"trip_mode_choice\"\n", + "chunk_size = 0 # test_mtc means no chunking\n", + "\n", + "pipeline.open_pipeline(resume_after)\n", + "# preload any bulky injectables (e.g. skims) not in pipeline\n", + "inject.get_injectable('preload_injectables', None)\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "#step_name = model_name\n", + "args = {}\n", + "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 176, + "outputs": [], + "source": [ + "t_ = trips.to_frame()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 177, + "outputs": [ + { + "data": { + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n2464104885 7512514 2821179 social 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n2464104885 1 8 9 308013110 home \n\n destination_logsum depart \ntrip_id \n2464104885 NaN 16.0 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdepart
trip_id
246410488575125142821179social1False189308013110homeNaN16.0
\n
" + }, + "execution_count": 177, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "t_.loc[t_.index == 2464104885]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, From 1286183cd2f34dda168baeaac2a6f84d25bc3ed6 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 6 May 2022 16:57:43 +1000 Subject: [PATCH 017/298] output clean up --- notebooks/fru_utils.py | 2 +- notebooks/validate_frozen_impl.ipynb | 219 ++++++++++----------------- 2 files changed, 82 insertions(+), 139 deletions(-) diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py index 935e8b0289..5f616e8ecf 100644 --- a/notebooks/fru_utils.py +++ b/notebooks/fru_utils.py @@ -199,7 +199,7 @@ def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, tri sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0] #sim_mode_shares.columns = ["mode_share_sim"] - obs_probs = base_probs[0].loc[base_probs[0].index == trip_id_to_check].T + obs_probs = base_probs.loc[base_probs.index == trip_id_to_check].T obs_probs.columns = ["mode_share_obs"] ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how="outer").fillna(0) ms_comp["diff"] = ms_comp["trip_mode"] - ms_comp["mode_share_obs"] diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index ec136d150d..d7e2ab1905 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -57,7 +57,7 @@ }, { "cell_type": "code", - "execution_count": 193, + "execution_count": 224, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -87,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 194, + "execution_count": 225, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 195, + "execution_count": 226, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -126,7 +126,7 @@ }, { "cell_type": "code", - "execution_count": 196, + "execution_count": 227, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -145,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 197, + "execution_count": 228, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -172,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 198, + "execution_count": 229, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -207,7 +207,8 @@ "trip_mode_choice tour_type 'shopping' (77 trips)\n", "trip_mode_choice tour_type 'social' (19 trips)\n", "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n" + "trip_mode_choice tour_type 'work' (168 trips)\n", + "Done\n" ] }, { @@ -222,7 +223,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "Done\n", "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", @@ -235,8 +235,8 @@ "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n", - "CPU times: user 23.4 s, sys: 2.09 s, total: 25.5 s\n", - "Wall time: 24 s\n" + "CPU times: user 25.2 s, sys: 2.31 s, total: 27.5 s\n", + "Wall time: 26.5 s\n" ] } ], @@ -248,14 +248,14 @@ }, { "cell_type": "code", - "execution_count": 184, + "execution_count": 231, "outputs": [ { "data": { "text/plain": " trip_mode_fru trip_mode_asim\nWALK 297 304\nWALK_LOC 100 85\nWALK_LRF 52 53\nBIKE 16 17\nDRIVEALONEFREE 7 4\nWALK_HVY 4 4\nTNC_SINGLE 3 4\nSHARED2FREE 2 4\nTNC_SHARED 1 4", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK297304
WALK_LOC10085
WALK_LRF5253
BIKE1617
DRIVEALONEFREE74
WALK_HVY44
TNC_SINGLE34
SHARED2FREE24
TNC_SHARED14
\n
" }, - "execution_count": 184, + "execution_count": 231, "metadata": {}, "output_type": "execute_result" } @@ -275,7 +275,7 @@ }, { "cell_type": "code", - "execution_count": 185, + "execution_count": 232, "outputs": [ { "data": { @@ -303,13 +303,13 @@ }, { "cell_type": "code", - "execution_count": 200, + "execution_count": 233, "outputs": [ { "data": { "text/plain": "trip_id\n106170305 4.050638\n106170309 0.553496\n1767013721 0.482067\n535694461 0.251889\n943749469 0.203583\n ... \n535672921 0.000000\n535620049 0.000000\n535388485 0.000000\n535388481 0.000000\n943749471 0.000000\nLength: 482, dtype: float64" }, - "execution_count": 200, + "execution_count": 233, "metadata": {}, "output_type": "execute_result" } @@ -327,14 +327,14 @@ }, { "cell_type": "code", - "execution_count": 201, + "execution_count": 234, "outputs": [ { "data": { - "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n106170305 WALK_LRF -0.002341 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n106170305 -0.011825 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
106170305WALK_LRF-0.002341WALK_LRF-0.011825
\n
" + "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n615236801 WALK_LOC 7.627291 WALK_LOC \n\n mode_choice_logsum_asim \ntrip_id \n615236801 7.627291 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
615236801WALK_LOC7.627291WALK_LOC7.627291
\n
" }, - "execution_count": 201, + "execution_count": 234, "metadata": {}, "output_type": "execute_result" } @@ -385,30 +385,6 @@ } } }, - { - "cell_type": "code", - "execution_count": 141, - "outputs": [ - { - "data": { - "text/plain": "trip_id\n535694417 0.143382\n943749469 0.109040\n1004301761 0.122286\n1767182865 0.119168\n1767183297 0.138448\n1767186581 0.111968\ndtype: float64" - }, - "execution_count": 141, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x_ = np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim)\n", - "x_.loc[x_ > 0.1]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, { "cell_type": "markdown", "source": [ @@ -428,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 207, + "execution_count": 235, "outputs": [ { "name": "stderr", @@ -470,20 +446,20 @@ }, { "cell_type": "code", - "execution_count": 209, + "execution_count": 236, "outputs": [ { "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n106170305 323689 256660 work 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n106170305 1 1 10 13271288 work \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n106170305 NaN 11.0 WALK_LRF -0.011825 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
106170305323689256660work1True111013271288workNaN11.0WALK_LRF-0.011825
\n
" + "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n86627409 WALK_LRF 2.435199 WALK_LRF \n86627413 WALK_LRF 2.665463 WALK_LRF \n86673661 WALK_LRF 1.842576 WALK_LRF \n106170305 WALK_LRF -0.002341 WALK_LRF \n106170309 WALK_LRF -0.017607 WALK_LRF \n... ... ... ... \n1767186253 WALK_LRF 0.976319 WALK_LRF \n1767186581 WALK_HVY 1.712704 WALK_LRF \n1768237161 WALK 6.262944 WALK \n1768237167 WALK_LRF 11.191330 WALK \n2473024477 WALK_LRF 0.637076 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n86627409 2.407248 \n86627413 2.642707 \n86673661 1.834538 \n106170305 -0.011825 \n106170309 -0.027353 \n... ... \n1767186253 0.880819 \n1767186581 1.600736 \n1768237161 6.226927 \n1768237167 11.144496 \n2473024477 0.624304 \n\n[74 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
86627409WALK_LRF2.435199WALK_LRF2.407248
86627413WALK_LRF2.665463WALK_LRF2.642707
86673661WALK_LRF1.842576WALK_LRF1.834538
106170305WALK_LRF-0.002341WALK_LRF-0.011825
106170309WALK_LRF-0.017607WALK_LRF-0.027353
...............
1767186253WALK_LRF0.976319WALK_LRF0.880819
1767186581WALK_HVY1.712704WALK_LRF1.600736
1768237161WALK6.262944WALK6.226927
1768237167WALK_LRF11.191330WALK11.144496
2473024477WALK_LRF0.637076WALK_LRF0.624304
\n

74 rows × 4 columns

\n
" }, - "execution_count": 209, + "execution_count": 236, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "t.loc[t.index == 106170305]" + "trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" ], "metadata": { "collapsed": false, @@ -494,24 +470,20 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" + "execution_count": 237, + "outputs": [ + { + "data": { + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n86627409 264107 226869 work 1 True \n86627413 264107 226869 work 1 False \n86673661 264248 226939 work 1 False \n106170305 323689 256660 work 1 True \n106170309 323689 256660 work 1 False \n... ... ... ... ... ... \n1767186253 5387762 2223027 work 1 False \n1767186581 5387763 2223027 work 1 False \n1768237161 5390966 2224629 work 1 True \n1768237167 5390966 2224629 work 3 False \n2473024477 7539708 2848373 univ 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n86627409 1 24 9 10828426 work \n86627413 1 9 24 10828426 home \n86673661 1 10 22 10834207 home \n106170305 1 1 10 13271288 work \n106170309 1 10 1 13271288 home \n... ... ... ... ... ... \n1767186253 1 9 4 220898281 home \n1767186581 2 9 14 220898322 othmaint \n1768237161 1 16 17 221029645 work \n1768237167 3 17 7 221029645 home \n2473024477 1 18 13 309128059 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n86627409 NaN 7.0 WALK_LRF 2.407248 \n86627413 NaN 19.0 WALK_LRF 2.642707 \n86673661 NaN 19.0 WALK_LRF 1.834538 \n106170305 NaN 11.0 WALK_LRF -0.011825 \n106170309 NaN 18.0 WALK_LRF -0.027353 \n... ... ... ... ... \n1767186253 NaN 17.0 WALK_LRF 0.880819 \n1767186581 32.045096 17.0 WALK_LRF 1.600736 \n1768237161 NaN 8.0 WALK 6.226927 \n1768237167 NaN 19.0 WALK 11.144496 \n2473024477 NaN 23.0 WALK_LRF 0.624304 \n\n[74 rows x 14 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
86627409264107226869work1True124910828426workNaN7.0WALK_LRF2.407248
86627413264107226869work1False192410828426homeNaN19.0WALK_LRF2.642707
86673661264248226939work1False1102210834207homeNaN19.0WALK_LRF1.834538
106170305323689256660work1True111013271288workNaN11.0WALK_LRF-0.011825
106170309323689256660work1False110113271288homeNaN18.0WALK_LRF-0.027353
.............................................
176718625353877622223027work1False194220898281homeNaN17.0WALK_LRF0.880819
176718658153877632223027work1False2914220898322othmaint32.04509617.0WALK_LRF1.600736
176823716153909662224629work1True11617221029645workNaN8.0WALK6.226927
176823716753909662224629work3False3177221029645homeNaN19.0WALK11.144496
247302447775397082848373univ1False11813309128059homeNaN23.0WALK_LRF0.624304
\n

74 rows × 14 columns

\n
" + }, + "execution_count": 237, + "metadata": {}, + "output_type": "execute_result" } - } - }, - { - "cell_type": "code", - "execution_count": 187, - "outputs": [], + ], "source": [ - "# nest_spec = ns[0]\n", - "# for nest in logit.each_nest(nest_spec, post_order=False):\n", - "# print(f\"{nest.level}, {nest.name}, {nest.coefficient}, parent scale: {nest.parent_scale}\")" + "t.loc[t.index.isin(trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0].index.values)]" ], "metadata": { "collapsed": false, @@ -522,20 +494,36 @@ }, { "cell_type": "code", - "execution_count": 188, + "execution_count": 238, "outputs": [ { "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n615236801 1875721 982875 work 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n615236801 1 10 16 76904600 work \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n615236801 NaN 8.0 WALK_LOC 7.627291 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
6152368011875721982875work1True1101676904600workNaN8.0WALK_LOC7.627291
\n
" }, - "execution_count": 188, + "execution_count": 238, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "base_probs[0] #615236801" + "t.loc[t.index == 615236801]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 187, + "outputs": [], + "source": [ + "# nest_spec = ns[0]\n", + "# for nest in logit.each_nest(nest_spec, post_order=False):\n", + "# print(f\"{nest.level}, {nest.name}, {nest.coefficient}, parent scale: {nest.parent_scale}\")" ], "metadata": { "collapsed": false, @@ -546,40 +534,20 @@ }, { "cell_type": "code", - "execution_count": 189, + "execution_count": 220, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (10000 trips)\n", - "Done\n" - ] - }, { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED \\\nmode_share_obs 0.000022 2.084422e-16 1.325277e-07 3.433307e-07 \ntrip_mode 0.000000 0.000000e+00 0.000000e+00 0.000000e+00 \n\n TNC_SINGLE WALK WALK_LOC \nmode_share_obs 8.507634e-12 0.627973 0.372005 \ntrip_mode 0.000000e+00 0.620900 0.379100 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOC
mode_share_obs0.0000222.084422e-161.325277e-073.433307e-078.507634e-120.6279730.372005
trip_mode0.0000000.000000e+000.000000e+000.000000e+000.000000e+000.6209000.379100
\n
" + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" }, + "execution_count": 220, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "choose_individual_max_utility = False\n", - "num_samples = 10000\n", - "trip_id_to_check = 2464104885\n", - "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)\n", - "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" + "base_probs[7] #615236801" ], "metadata": { "collapsed": false, @@ -590,13 +558,7 @@ }, { "cell_type": "code", - "execution_count": 190, - "id": "1fcdc0ff-c510-4878-8672-8365e046442c", - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, + "execution_count": null, "outputs": [ { "name": "stderr", @@ -611,50 +573,31 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (10000 trips)\n", - "Done\n" + "trip_mode_choice tour_type 'work' (10000 trips)\n" ] - }, - { - "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED \\\nmode_share_obs 0.000022 2.084422e-16 1.325277e-07 3.433307e-07 \ntrip_mode 0.000000 0.000000e+00 0.000000e+00 0.000000e+00 \n\n TNC_SINGLE WALK WALK_LOC \nmode_share_obs 8.507634e-12 0.627973 0.372005 \ntrip_mode 0.000000e+00 0.673100 0.326900 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOC
mode_share_obs0.0000222.084422e-161.325277e-073.433307e-078.507634e-120.6279730.372005
trip_mode0.0000000.000000e+000.000000e+000.000000e+000.000000e+000.6731000.326900
\n
" - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ - "choose_individual_max_utility = True\n", - "num_samples = 10000 # 1e5 takes about 25s\n", - "trip_id_to_check = 2464104885\n", - "# work 615236801\n", + "num_samples = 10000\n", + "trip_id_to_check = 615236801 # 1e5 takes about 25s\n", + "# work 615236801 also in trace log\n", "# social 2464104885 642446345 1767182945\n", - "ms_comp = comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check)\n", + "\n", + "bp = base_probs[9] # 7 for social, 9 for work. Check t[t.index == trip_id_to_check] to see which purpose, and\n", + "# output above to see which index\n", + "# choose_individual_max_utility = False\n", + "#ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", + "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)\n", + "\n", + "choose_individual_max_utility = True\n", + "ms_comp = comp_mode_shares(base_probs[9], choose_individual_max_utility, num_samples, trip_id_to_check)\n", "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 160, - "outputs": [], - "source": [], + ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%%\n", + "is_executing": true } } }, From c95f68df5c08ffd04916fe0ea82c4f52904838f3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 8 May 2022 21:15:41 +1000 Subject: [PATCH 018/298] fixes bug in choice for tree --- activitysim/core/logit.py | 67 ++-- notebooks/fru_utils.py | 2 - notebooks/validate_frozen_impl.ipynb | 445 ++++++++++++++++++++------- 3 files changed, 378 insertions(+), 136 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 0e7feb33a1..8166607945 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -192,37 +192,53 @@ def inverse_ev1_cdf(x, location=0.0, scale=1.0): return location - scale * np.log(-np.log(x)) -def add_ev1_random(df, nest_spec): - # TODO: generalise to logit for nest_spec==None by adding one rand (scale=1) to all columns +# def add_ev1_random(df, nest_spec): +# # TODO: generalise to logit for nest_spec==None by adding one rand (scale=1) to all columns +# nest_utils_for_choice = df.copy() +# for n in each_nest(nest_spec): +# if n.level == 1: +# continue # skip the root level, not needed +# uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice) +# rands = inverse_ev1_cdf(uniform_rands) +# nest_utils_for_choice.loc[:, n.name] += rands[:, 0] # inverse_ev1_cdf of single-row df adds dimension +# return nest_utils_for_choice +def add_ev1_random(df): nest_utils_for_choice = df.copy() - for n in each_nest(nest_spec): - if n.level == 1: - continue # skip the root level, not needed - uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice) - rands = inverse_ev1_cdf(uniform_rands) - nest_utils_for_choice.loc[:, n.name] += rands[:, 0] # inverse_ev1_cdf of single-row df adds dimension + uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice, n=df.shape[1]) + rands = inverse_ev1_cdf(uniform_rands) + nest_utils_for_choice += rands return nest_utils_for_choice -def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_choosers=None): - trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') - nest_utils_for_choice = add_ev1_random(nested_utilities, nest_spec) - all_alternatives = list(map(lambda x: x.name, filter(lambda x: x.is_leaf, each_nest(nest_spec)))) - - def is_alternative(name): - return name in all_alternatives - - nest_utils_for_choice["choice"] = None - for level, alts in group_nests_by_level(nest_spec).items(): +def choose_from_tree(nest_utils, nest_spec): + all_alternatives = [nest.name for nest in each_nest(nest_spec, type='leaf')] + for level, nests_at_level in group_nests_by_level(nest_spec).items(): + nest_alts = [nest.name for nest in nests_at_level] if level == 1: + assert len(nests_at_level) == 1 + assert len(nest_alts) == 1 + next_level_alts = nests_at_level[0].alternatives continue - no_choices_made_yet = nest_utils_for_choice["choice"].isnull() - choice_this_level = nest_utils_for_choice.loc[no_choices_made_yet][alts].idxmax(1) - nest_utils_for_choice.loc[no_choices_made_yet, "choice"] = \ - np.where(choice_this_level.apply(is_alternative), choice_this_level, None) + # all alternatives from the previous level + alts_this_level = list(filter(lambda x: x in next_level_alts, nest_alts)) + choice_this_level = nest_utils[nest_utils.index.isin(alts_this_level)].idxmax() + if choice_this_level in all_alternatives: + return choice_this_level + chosen_nest = list(filter(lambda x: x.name == choice_this_level, nests_at_level)) + assert len(chosen_nest) == 1 + next_level_alts = chosen_nest[0].alternatives - assert not nest_utils_for_choice["choice"].isnull().any(), "No choice for XXX - implement reporting" - choices = pd.Series(nest_utils_for_choice["choice"], index=nested_utilities.index) + raise ValueError("This should never happen - no alternative found") + + +def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_choosers=None): + """ walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" + trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') + nest_utils_for_choice = add_ev1_random(nested_utilities) + # TODO: the following apply is slow, try to improve it + choices = nest_utils_for_choice.apply(lambda x: choose_from_tree(x, nest_spec), axis=1) + assert not choices.isnull().any(), "No choice for XXX - implement reporting" + choices = pd.Series(choices, index=nested_utilities.index) return choices @@ -523,6 +539,5 @@ def group_nests_by_level(nest_spec): depth = np.max([x.level for x in each_nest(nest_spec)]) nest_levels = {x: [] for x in range(1, depth+1)} for n in each_nest(nest_spec): - nest_levels[n.level].append(n.name) - assert len(nest_levels[1]) == 1 # only one root + nest_levels[n.level].append(n) return nest_levels diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py index 5f616e8ecf..0f13679b52 100644 --- a/notebooks/fru_utils.py +++ b/notebooks/fru_utils.py @@ -1,11 +1,9 @@ import os -import argparse from datetime import datetime import numpy as np import pandas as pd -from activitysim.cli import run from activitysim.core import inject from activitysim.core import tracing from activitysim.core import config diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index d7e2ab1905..f7270bdf64 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 191, + "execution_count": 1, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { "execution": { @@ -22,23 +22,14 @@ }, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": 192, + "execution_count": 2, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { "execution": { @@ -57,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 224, + "execution_count": 19, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -87,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": 225, + "execution_count": 20, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -106,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 226, + "execution_count": 21, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -126,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 227, + "execution_count": 23, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -145,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 228, + "execution_count": 24, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -172,7 +163,25 @@ }, { "cell_type": "code", - "execution_count": 229, + "execution_count": 25, + "outputs": [], + "source": [ + "# need to do this before loading checkpoint tables\n", + "trace_hh_ids = 1024353\n", + "#1024353 has trip id 642446345 (at least) where logsum and probs are different\n", + "#2821179 # has trip_ids 2464104881 and 2464104885\n", + "inject.add_injectable(\"trace_hh_id\", trace_hh_ids)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 26, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -189,7 +198,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", + "register joint_tour_participants: no rows with household_id in [1024353].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -207,15 +216,28 @@ "trip_mode_choice tour_type 'shopping' (77 trips)\n", "trip_mode_choice tour_type 'social' (19 trips)\n", "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n" + "trip_mode_choice tour_type 'work' (168 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "register joint_tour_participants: no rows with household_id in [1024353].\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n", + "Running with %d trips 482\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -223,7 +245,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", "trip_mode_choice tour_type 'escort' (6 trips)\n", @@ -235,8 +256,8 @@ "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n", - "CPU times: user 25.2 s, sys: 2.31 s, total: 27.5 s\n", - "Wall time: 26.5 s\n" + "CPU times: user 18.1 s, sys: 2.48 s, total: 20.6 s\n", + "Wall time: 17 s\n" ] } ], @@ -248,14 +269,14 @@ }, { "cell_type": "code", - "execution_count": 231, + "execution_count": 27, "outputs": [ { "data": { - "text/plain": " trip_mode_fru trip_mode_asim\nWALK 297 304\nWALK_LOC 100 85\nWALK_LRF 52 53\nBIKE 16 17\nDRIVEALONEFREE 7 4\nWALK_HVY 4 4\nTNC_SINGLE 3 4\nSHARED2FREE 2 4\nTNC_SHARED 1 4", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK297304
WALK_LOC10085
WALK_LRF5253
BIKE1617
DRIVEALONEFREE74
WALK_HVY44
TNC_SINGLE34
SHARED2FREE24
TNC_SHARED14
\n
" + "text/plain": " trip_mode_fru trip_mode_asim\nWALK 304 304\nWALK_LOC 91 91\nWALK_LRF 49 47\nBIKE 15 17\nTNC_SINGLE 6 4\nDRIVEALONEFREE 5 4\nWALK_HVY 5 4\nSHARED2FREE 3 4\nTNC_SHARED 2 4\nTAXI 2 2", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK304304
WALK_LOC9191
WALK_LRF4947
BIKE1517
TNC_SINGLE64
DRIVEALONEFREE54
WALK_HVY54
SHARED2FREE34
TNC_SHARED24
TAXI22
\n
" }, - "execution_count": 231, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -275,17 +296,14 @@ }, { "cell_type": "code", - "execution_count": 232, + "execution_count": 28, "outputs": [ { - "data": { - "text/plain": "
", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "Logsums agree\n" + ] } ], "source": [ @@ -303,13 +321,13 @@ }, { "cell_type": "code", - "execution_count": 233, + "execution_count": 12, "outputs": [ { "data": { "text/plain": "trip_id\n106170305 4.050638\n106170309 0.553496\n1767013721 0.482067\n535694461 0.251889\n943749469 0.203583\n ... \n535672921 0.000000\n535620049 0.000000\n535388485 0.000000\n535388481 0.000000\n943749471 0.000000\nLength: 482, dtype: float64" }, - "execution_count": 233, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -327,20 +345,20 @@ }, { "cell_type": "code", - "execution_count": 234, + "execution_count": 13, "outputs": [ { "data": { - "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n615236801 WALK_LOC 7.627291 WALK_LOC \n\n mode_choice_logsum_asim \ntrip_id \n615236801 7.627291 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
615236801WALK_LOC7.627291WALK_LOC7.627291
\n
" + "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n642446345 WALK_LRF 1.634462 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n642446345 1.593075 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
642446345WALK_LRF1.634462WALK_LRF1.593075
\n
" }, - "execution_count": 234, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "trips.loc[trips.index == 106170305]" + "trips.loc[trips.index == 642446345]" ], "metadata": { "collapsed": false, @@ -351,20 +369,20 @@ }, { "cell_type": "code", - "execution_count": 210, + "execution_count": 14, "outputs": [ { "data": { "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n86627409 WALK_LRF 2.435199 WALK_LRF \n86627413 WALK_LRF 2.665463 WALK_LRF \n86673661 WALK_LRF 1.842576 WALK_LRF \n106170305 WALK_LRF -0.002341 WALK_LRF \n106170309 WALK_LRF -0.017607 WALK_LRF \n... ... ... ... \n1767186253 WALK_LRF 0.976319 WALK_LRF \n1767186581 WALK_HVY 1.712704 WALK_LRF \n1768237161 WALK 6.262944 WALK \n1768237167 WALK_LRF 11.191330 WALK \n2473024477 WALK_LRF 0.637076 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n86627409 2.407248 \n86627413 2.642707 \n86673661 1.834538 \n106170305 -0.011825 \n106170309 -0.027353 \n... ... \n1767186253 0.880819 \n1767186581 1.600736 \n1768237161 6.226927 \n1768237167 11.144496 \n2473024477 0.624304 \n\n[74 rows x 4 columns]", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
86627409WALK_LRF2.435199WALK_LRF2.407248
86627413WALK_LRF2.665463WALK_LRF2.642707
86673661WALK_LRF1.842576WALK_LRF1.834538
106170305WALK_LRF-0.002341WALK_LRF-0.011825
106170309WALK_LRF-0.017607WALK_LRF-0.027353
...............
1767186253WALK_LRF0.976319WALK_LRF0.880819
1767186581WALK_HVY1.712704WALK_LRF1.600736
1768237161WALK6.262944WALK6.226927
1768237167WALK_LRF11.191330WALK11.144496
2473024477WALK_LRF0.637076WALK_LRF0.624304
\n

74 rows × 4 columns

\n
" }, - "execution_count": 210, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" + "#trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" ], "metadata": { "collapsed": false, @@ -404,13 +422,13 @@ }, { "cell_type": "code", - "execution_count": 235, + "execution_count": 29, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", + "register joint_tour_participants: no rows with household_id in [1024353].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -446,20 +464,10 @@ }, { "cell_type": "code", - "execution_count": 236, - "outputs": [ - { - "data": { - "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n86627409 WALK_LRF 2.435199 WALK_LRF \n86627413 WALK_LRF 2.665463 WALK_LRF \n86673661 WALK_LRF 1.842576 WALK_LRF \n106170305 WALK_LRF -0.002341 WALK_LRF \n106170309 WALK_LRF -0.017607 WALK_LRF \n... ... ... ... \n1767186253 WALK_LRF 0.976319 WALK_LRF \n1767186581 WALK_HVY 1.712704 WALK_LRF \n1768237161 WALK 6.262944 WALK \n1768237167 WALK_LRF 11.191330 WALK \n2473024477 WALK_LRF 0.637076 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n86627409 2.407248 \n86627413 2.642707 \n86673661 1.834538 \n106170305 -0.011825 \n106170309 -0.027353 \n... ... \n1767186253 0.880819 \n1767186581 1.600736 \n1768237161 6.226927 \n1768237167 11.144496 \n2473024477 0.624304 \n\n[74 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
86627409WALK_LRF2.435199WALK_LRF2.407248
86627413WALK_LRF2.665463WALK_LRF2.642707
86673661WALK_LRF1.842576WALK_LRF1.834538
106170305WALK_LRF-0.002341WALK_LRF-0.011825
106170309WALK_LRF-0.017607WALK_LRF-0.027353
...............
1767186253WALK_LRF0.976319WALK_LRF0.880819
1767186581WALK_HVY1.712704WALK_LRF1.600736
1768237161WALK6.262944WALK6.226927
1768237167WALK_LRF11.191330WALK11.144496
2473024477WALK_LRF0.637076WALK_LRF0.624304
\n

74 rows × 4 columns

\n
" - }, - "execution_count": 236, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 30, + "outputs": [], "source": [ - "trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" + "#trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" ], "metadata": { "collapsed": false, @@ -470,20 +478,11 @@ }, { "cell_type": "code", - "execution_count": 237, - "outputs": [ - { - "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n86627409 264107 226869 work 1 True \n86627413 264107 226869 work 1 False \n86673661 264248 226939 work 1 False \n106170305 323689 256660 work 1 True \n106170309 323689 256660 work 1 False \n... ... ... ... ... ... \n1767186253 5387762 2223027 work 1 False \n1767186581 5387763 2223027 work 1 False \n1768237161 5390966 2224629 work 1 True \n1768237167 5390966 2224629 work 3 False \n2473024477 7539708 2848373 univ 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n86627409 1 24 9 10828426 work \n86627413 1 9 24 10828426 home \n86673661 1 10 22 10834207 home \n106170305 1 1 10 13271288 work \n106170309 1 10 1 13271288 home \n... ... ... ... ... ... \n1767186253 1 9 4 220898281 home \n1767186581 2 9 14 220898322 othmaint \n1768237161 1 16 17 221029645 work \n1768237167 3 17 7 221029645 home \n2473024477 1 18 13 309128059 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n86627409 NaN 7.0 WALK_LRF 2.407248 \n86627413 NaN 19.0 WALK_LRF 2.642707 \n86673661 NaN 19.0 WALK_LRF 1.834538 \n106170305 NaN 11.0 WALK_LRF -0.011825 \n106170309 NaN 18.0 WALK_LRF -0.027353 \n... ... ... ... ... \n1767186253 NaN 17.0 WALK_LRF 0.880819 \n1767186581 32.045096 17.0 WALK_LRF 1.600736 \n1768237161 NaN 8.0 WALK 6.226927 \n1768237167 NaN 19.0 WALK 11.144496 \n2473024477 NaN 23.0 WALK_LRF 0.624304 \n\n[74 rows x 14 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
86627409264107226869work1True124910828426workNaN7.0WALK_LRF2.407248
86627413264107226869work1False192410828426homeNaN19.0WALK_LRF2.642707
86673661264248226939work1False1102210834207homeNaN19.0WALK_LRF1.834538
106170305323689256660work1True111013271288workNaN11.0WALK_LRF-0.011825
106170309323689256660work1False110113271288homeNaN18.0WALK_LRF-0.027353
.............................................
176718625353877622223027work1False194220898281homeNaN17.0WALK_LRF0.880819
176718658153877632223027work1False2914220898322othmaint32.04509617.0WALK_LRF1.600736
176823716153909662224629work1True11617221029645workNaN8.0WALK6.226927
176823716753909662224629work3False3177221029645homeNaN19.0WALK11.144496
247302447775397082848373univ1False11813309128059homeNaN23.0WALK_LRF0.624304
\n

74 rows × 14 columns

\n
" - }, - "execution_count": 237, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 31, + "outputs": [], "source": [ - "t.loc[t.index.isin(trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0].index.values)]" + "#t.loc[t.index.isin(trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0].index.values)]\\\n", + "# .sort_values(by=[\"primary_purpose\"])" ], "metadata": { "collapsed": false, @@ -494,20 +493,12 @@ }, { "cell_type": "code", - "execution_count": 238, - "outputs": [ - { - "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n615236801 1875721 982875 work 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n615236801 1 10 16 76904600 work \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n615236801 NaN 8.0 WALK_LOC 7.627291 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
6152368011875721982875work1True1101676904600workNaN8.0WALK_LOC7.627291
\n
" - }, - "execution_count": 238, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 32, + "outputs": [], "source": [ - "t.loc[t.index == 615236801]" + "# nest_spec = ns[0]\n", + "# for nest in logit.each_nest(nest_spec, post_order=False):\n", + "# print(f\"{nest.level}, {nest.name}, {nest.coefficient}, parent scale: {nest.parent_scale}\")" ], "metadata": { "collapsed": false, @@ -518,12 +509,10 @@ }, { "cell_type": "code", - "execution_count": 187, + "execution_count": 33, "outputs": [], "source": [ - "# nest_spec = ns[0]\n", - "# for nest in logit.each_nest(nest_spec, post_order=False):\n", - "# print(f\"{nest.level}, {nest.name}, {nest.coefficient}, parent scale: {nest.parent_scale}\")" + "#base_probs[7] #615236801" ], "metadata": { "collapsed": false, @@ -534,20 +523,20 @@ }, { "cell_type": "code", - "execution_count": 220, + "execution_count": 34, "outputs": [ { "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n8805121 0.0 0.0 0.000000e+00 0.0 \n8805125 0.0 0.0 0.000000e+00 0.0 \n8805126 0.0 0.0 0.000000e+00 0.0 \n468119921 0.0 0.0 0.000000e+00 0.0 \n468119922 0.0 0.0 0.000000e+00 0.0 \n468119925 0.0 0.0 0.000000e+00 0.0 \n468119926 0.0 0.0 0.000000e+00 0.0 \n468119927 0.0 0.0 0.000000e+00 0.0 \n468119928 0.0 0.0 0.000000e+00 0.0 \n642446345 0.0 0.0 4.882838e-03 0.0 \n642446349 0.0 0.0 3.622165e-03 0.0 \n1179608009 0.0 0.0 5.572746e-05 0.0 \n1179608013 0.0 0.0 1.108122e-05 0.0 \n1658807153 0.0 0.0 4.252415e-07 0.0 \n1658807157 0.0 0.0 5.038020e-07 0.0 \n1767182945 0.0 0.0 1.610090e-02 0.0 \n1767182949 0.0 0.0 1.540381e-02 0.0 \n2464104881 0.0 0.0 2.228876e-05 0.0 \n2464104885 0.0 0.0 2.163505e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n8805121 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805125 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n8805126 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119921 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119922 0.000000e+00 0.0 0.999874 0.0 0.000000 0.000000 \n468119925 0.000000e+00 0.0 0.999875 0.0 0.000000 0.000000 \n468119926 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119927 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n468119928 0.000000e+00 0.0 1.000000 0.0 0.000000 0.000000 \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n642446349 3.950582e-14 0.0 0.043359 0.0 0.068362 0.884144 \n1179608009 1.964682e-05 0.0 0.969538 0.0 0.000000 0.000000 \n1179608013 7.469124e-06 0.0 0.969598 0.0 0.000000 0.000000 \n1658807153 1.499257e-07 0.0 0.048044 0.0 0.951445 0.000000 \n1658807157 1.907576e-07 0.0 0.065847 0.0 0.933430 0.000000 \n1767182945 1.548643e-13 0.0 0.046835 0.0 0.144256 0.787096 \n1767182949 1.509357e-13 0.0 0.041767 0.0 0.141232 0.795898 \n2464104881 2.147403e-16 0.0 0.646947 0.0 0.353030 0.000000 \n2464104885 2.084422e-16 0.0 0.627973 0.0 0.372005 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n8805121 0.0 0.0 0.0 0.0 0.0 0.0 \n8805125 0.0 0.0 0.0 0.0 0.0 0.0 \n8805126 0.0 0.0 0.0 0.0 0.0 0.0 \n468119921 0.0 0.0 0.0 0.0 0.0 0.0 \n468119922 0.0 0.0 0.0 0.0 0.0 0.0 \n468119925 0.0 0.0 0.0 0.0 0.0 0.0 \n468119926 0.0 0.0 0.0 0.0 0.0 0.0 \n468119927 0.0 0.0 0.0 0.0 0.0 0.0 \n468119928 0.0 0.0 0.0 0.0 0.0 0.0 \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n642446349 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608009 0.0 0.0 0.0 0.0 0.0 0.0 \n1179608013 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807153 0.0 0.0 0.0 0.0 0.0 0.0 \n1658807157 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182945 0.0 0.0 0.0 0.0 0.0 0.0 \n1767182949 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104881 0.0 0.0 0.0 0.0 0.0 0.0 \n2464104885 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n8805121 0.0 0.0 4.266799e-08 7.843494e-08 2.071998e-08 \n8805125 0.0 0.0 4.057327e-08 7.325707e-08 1.950799e-08 \n8805126 0.0 0.0 4.864169e-08 9.376443e-08 2.428075e-08 \n468119921 0.0 0.0 1.096638e-08 2.754998e-10 4.051502e-08 \n468119922 0.0 0.0 2.658716e-05 6.599176e-07 9.832967e-05 \n468119925 0.0 0.0 2.651144e-05 6.601311e-07 9.831370e-05 \n468119926 0.0 0.0 1.568041e-08 2.408144e-10 3.615344e-08 \n468119927 0.0 0.0 5.308505e-08 1.315547e-10 2.041405e-08 \n468119928 0.0 0.0 2.367465e-09 1.493582e-10 1.939597e-08 \n642446345 0.0 0.0 9.610763e-05 1.307549e-04 2.867554e-04 \n642446349 0.0 0.0 9.293281e-05 1.316643e-04 2.885385e-04 \n1179608009 0.0 0.0 5.225133e-06 2.766547e-02 2.716407e-03 \n1179608013 0.0 0.0 5.222635e-06 2.766274e-02 2.715479e-03 \n1658807153 0.0 0.0 7.091296e-08 4.695006e-04 4.115873e-05 \n1658807157 0.0 0.0 9.574501e-08 6.645671e-04 5.801899e-05 \n1767182945 0.0 0.0 1.498950e-03 3.358768e-03 8.536317e-04 \n1767182949 0.0 0.0 1.476552e-03 3.378934e-03 8.436653e-04 \n2464104881 0.0 0.0 1.365321e-07 8.764695e-12 3.537046e-07 \n2464104885 0.0 0.0 1.325277e-07 8.507634e-12 3.433307e-07 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
88051210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.266799e-087.843494e-082.071998e-08
88051250.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.057327e-087.325707e-081.950799e-08
88051260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.04.864169e-089.376443e-082.428075e-08
4681199210.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.096638e-082.754998e-104.051502e-08
4681199220.00.00.000000e+000.00.000000e+000.00.9998740.00.0000000.0000000.00.00.00.00.00.00.00.02.658716e-056.599176e-079.832967e-05
4681199250.00.00.000000e+000.00.000000e+000.00.9998750.00.0000000.0000000.00.00.00.00.00.00.00.02.651144e-056.601311e-079.831370e-05
4681199260.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.01.568041e-082.408144e-103.615344e-08
4681199270.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.05.308505e-081.315547e-102.041405e-08
4681199280.00.00.000000e+000.00.000000e+000.01.0000000.00.0000000.0000000.00.00.00.00.00.00.00.02.367465e-091.493582e-101.939597e-08
6424463450.00.04.882838e-030.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.09.610763e-051.307549e-042.867554e-04
6424463490.00.03.622165e-030.03.950582e-140.00.0433590.00.0683620.8841440.00.00.00.00.00.00.00.09.293281e-051.316643e-042.885385e-04
11796080090.00.05.572746e-050.01.964682e-050.00.9695380.00.0000000.0000000.00.00.00.00.00.00.00.05.225133e-062.766547e-022.716407e-03
11796080130.00.01.108122e-050.07.469124e-060.00.9695980.00.0000000.0000000.00.00.00.00.00.00.00.05.222635e-062.766274e-022.715479e-03
16588071530.00.04.252415e-070.01.499257e-070.00.0480440.00.9514450.0000000.00.00.00.00.00.00.00.07.091296e-084.695006e-044.115873e-05
16588071570.00.05.038020e-070.01.907576e-070.00.0658470.00.9334300.0000000.00.00.00.00.00.00.00.09.574501e-086.645671e-045.801899e-05
17671829450.00.01.610090e-020.01.548643e-130.00.0468350.00.1442560.7870960.00.00.00.00.00.00.00.01.498950e-033.358768e-038.536317e-04
17671829490.00.01.540381e-020.01.509357e-130.00.0417670.00.1412320.7958980.00.00.00.00.00.00.00.01.476552e-033.378934e-038.436653e-04
24641048810.00.02.228876e-050.02.147403e-160.00.6469470.00.3530300.0000000.00.00.00.00.00.00.00.01.365321e-078.764695e-123.537046e-07
24641048850.00.02.163505e-050.02.084422e-160.00.6279730.00.3720050.0000000.00.00.00.00.00.00.00.01.325277e-078.507634e-123.433307e-07
\n
" + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n642446345 1958677 1024353 social 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n642446345 1 14 9 80305793 social \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n642446345 NaN 10.0 WALK_LRF 1.729057 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
64244634519586771024353social1True114980305793socialNaN10.0WALK_LRF1.729057
\n
" }, - "execution_count": 220, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "base_probs[7] #615236801" + "t.loc[t.index == 642446345]" ], "metadata": { "collapsed": false, @@ -558,13 +547,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 183, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", + "register joint_tour_participants: no rows with household_id in [1024353].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -573,39 +562,279 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'work' (10000 trips)\n" + "trip_mode_choice tour_type 'social' (10000 trips)\n", + "Done\n" ] + }, + { + "data": { + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004262 4.103657e-14 0.000084 0.00025 0.000114 \ntrip_mode 0.003800 0.000000e+00 0.000000 0.00020 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.039196 0.19705 0.759044 \ntrip_mode 0.038000 0.20070 0.757300 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0042624.103657e-140.0000840.000250.0001140.0391960.197050.759044
trip_mode0.0038000.000000e+000.0000000.000200.0000000.0380000.200700.757300
\n
" + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ "num_samples = 10000\n", - "trip_id_to_check = 615236801 # 1e5 takes about 25s\n", + "trip_id_to_check = 642446345 # 1e5 takes about 25s\n", "# work 615236801 also in trace log\n", "# social 2464104885 642446345 1767182945\n", "\n", - "bp = base_probs[9] # 7 for social, 9 for work. Check t[t.index == trip_id_to_check] to see which purpose, and\n", + "bp = base_probs[7] # 7 for social, 9 for work. Check t[t.index == trip_id_to_check] to see which purpose, and\n", "# output above to see which index\n", "# choose_individual_max_utility = False\n", "#ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)\n", "\n", "choose_individual_max_utility = True\n", - "ms_comp = comp_mode_shares(base_probs[9], choose_individual_max_utility, num_samples, trip_id_to_check)\n", + "ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n", - "is_executing": true + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Investigate differing probs\n", + "\n", + "is it due to clipping, due to idxmax always returning the first and by coincidence having equal probs somewhere, or\n", + "bad methodology?" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" } } }, { "cell_type": "code", - "execution_count": null, + "execution_count": 121, "outputs": [], - "source": [], + "source": [ + "def compute_probs(nested_utils, nest_spec):\n", + " nested_exp_utils = compute_nested_probabilities(np.exp(nested_utils), nest_spec)\n", + " base_probabilities = pd.DataFrame(index=nested_exp_utils.index)\n", + " for nest in logit.each_nest(nest_spec, type='leaf', post_order=False):\n", + " # skip root: it has a prob of 1 but we didn't compute a nested probability column for it\n", + " ancestors = nest.ancestors[1:]\n", + " base_probabilities[nest.name] = nested_exp_utils[ancestors].prod(axis=1)\n", + " return base_probabilities\n", + "\n", + "def compute_nested_probabilities(nested_exp_utilities, nest_spec):\n", + " nested_probabilities = pd.DataFrame(index=nested_exp_utilities.index)\n", + " for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", + " probs = u_to_p(nested_exp_utilities[nest.alternatives])\n", + " nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)\n", + " return nested_probabilities\n", + "\n", + "EXP_UTIL_MIN = 1e-300\n", + "EXP_UTIL_MAX = np.inf\n", + "\n", + "PROB_MIN = 0.0\n", + "PROB_MAX = 1.0\n", + "\n", + "def u_to_p(utils, trace_label=None):\n", + " utils_arr = utils.values\n", + " np.clip(utils_arr, EXP_UTIL_MIN, EXP_UTIL_MAX, out=utils_arr)\n", + " utils_arr = np.where(utils_arr == EXP_UTIL_MIN, 0.0, utils_arr)\n", + " arr_sum = utils_arr.sum(axis=1)\n", + " inf_utils = np.isinf(arr_sum)\n", + " if inf_utils.any():\n", + " print(\"INF\")\n", + " with np.errstate(invalid='ignore', divide='ignore'):\n", + " np.divide(utils_arr, arr_sum.reshape(len(utils_arr), 1), out=utils_arr)\n", + " ## if allow_zero_probs, this will cause EXP_UTIL_MIN util rows to have all zero probabilities\n", + " utils_arr[np.isnan(utils_arr)] = PROB_MIN\n", + " np.clip(utils_arr, PROB_MIN, PROB_MAX, out=utils_arr)\n", + " probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)\n", + " return probs" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 177, + "outputs": [], + "source": [ + "from activitysim.core import logit\n", + "from numpy.random import default_rng\n", + "\n", + "rng = default_rng(999)\n", + "\n", + "def random_for_df(df):\n", + " return rng.random(size=df.shape)\n", + "\n", + "def add_ev1_random(df):\n", + " uniform_rands = random_for_df(df)\n", + " df = df + logit.inverse_ev1_cdf(uniform_rands)\n", + " return df\n", + "\n", + "def group_nests_by_level(nest_spec):\n", + " # group nests by level, returns {level: [nest.name at that level]}\n", + " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in logit.each_nest(nest_spec):\n", + " nest_levels[n.level].append(n.name)\n", + " assert len(nest_levels[1]) == 1 # only one root\n", + " return nest_levels\n", + "\n", + "def make_choices_ru_frozen(nested_utilities, nest_spec):\n", + " nest_utils_for_choice = add_ev1_random(nested_utilities)\n", + " all_alternatives = list(map(lambda x: x.name, filter(lambda x: x.is_leaf, logit.each_nest(nest_spec))))\n", + "\n", + " def is_alternative(name):\n", + " return name in all_alternatives\n", + "\n", + " nest_utils_for_choice[\"choice\"] = None\n", + " for level, alts in group_nests_by_level(nest_spec).items():\n", + " if level == 1:\n", + " continue\n", + " no_choices_made_yet = nest_utils_for_choice[\"choice\"].isnull()\n", + " choice_this_level = nest_utils_for_choice.loc[no_choices_made_yet][alts].idxmax(1)\n", + " nest_utils_for_choice.loc[no_choices_made_yet, \"choice\"] = \\\n", + " np.where(choice_this_level.apply(is_alternative), choice_this_level, None)\n", + "\n", + " assert not nest_utils_for_choice[\"choice\"].isnull().any(), \"No choice for XXX - implement reporting\"\n", + " choices = pd.Series(nest_utils_for_choice[\"choice\"], index=nested_utilities.index)\n", + " return choices, nest_utils_for_choice" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 178, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n642446345 0.0 0.0 0.004262 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n642446345 4.103657e-14 0.0 0.039196 0.0 0.19705 0.759044 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n642446345 0.0 0.0 0.000084 0.000114 0.00025 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
6424463450.00.00.0042620.04.103657e-140.00.0391960.00.197050.7590440.00.00.00.00.00.00.00.00.0000840.0001140.00025
\n
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "nest_spec = ns[7] # 7 is social\n", + "nest_util = nu[7].loc[nu[7].index == trip_id_to_check]\n", + "\n", + "n = 100000\n", + "\n", + "nest_util = nest_util.loc[nest_util.index.repeat(n)]\n", + "nest_util.index += np.arange(n)\n", + "\n", + "display(base_probs[7].loc[base_probs[7].index == trip_id_to_check])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 179, + "outputs": [ + { + "data": { + "text/plain": "WALK_LRF 0.78148\nWALK_LOC 0.20604\nWALK 0.01194\nSHARED2FREE 0.00054\nName: choice, dtype: float64" + }, + "execution_count": 179, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "choices, nu_choices = make_choices_ru_frozen(nest_util, nest_spec)\n", + "choices.value_counts() / n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 180, + "outputs": [], + "source": [ + "def group_nests_by_level(nest_spec):\n", + " # group nests by level, returns {level: [nest]}\n", + " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in logit.each_nest(nest_spec):\n", + " nest_levels[n.level].append(n)\n", + " assert len(nest_levels[1]) == 1 # only one root\n", + " return nest_levels\n", + "\n", + "def make_choices_ru_frozen_apply(nested_utilities, nest_spec):\n", + " nest_utils_for_choice = add_ev1_random(nested_utilities)\n", + " all_alternatives = [nest.name for nest in logit.each_nest(nest_spec, type='leaf')]\n", + "\n", + " for level, nests_at_level in group_nests_by_level(nest_spec).items():\n", + " nest_alts = [nest.name for nest in nests_at_level]\n", + " if level == 1:\n", + " assert len(nests_at_level) == 1\n", + " assert len(nest_alts) == 1\n", + " next_level_alts = nests_at_level[0].alternatives\n", + " continue\n", + "\n", + " # all alternatives from the previous level\n", + " alts_this_level = list(filter(lambda x: x in next_level_alts, nest_alts))\n", + " #print(f\"Level {level} alternatives are {alts_this_level}\")\n", + " choice_this_level = nest_utils_for_choice[nest_utils_for_choice.index.isin(alts_this_level)].idxmax()\n", + " #print(f\"choice is {choice_this_level}\")\n", + " if choice_this_level in all_alternatives:\n", + " return choice_this_level\n", + " chosen_nest = list(filter(lambda x: x.name == choice_this_level, nests_at_level))\n", + " assert len(chosen_nest) == 1\n", + " next_level_alts = chosen_nest[0].alternatives\n", + "\n", + " raise ValueError(\"This should never happen - no alternative found\")\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 182, + "outputs": [ + { + "data": { + "text/plain": "WALK_LRF 0.75972\nWALK_LOC 0.19677\nWALK 0.03901\nSHARED2FREE 0.00406\nTNC_SHARED 0.00025\nTNC_SINGLE 0.00010\nTAXI 0.00009\ndtype: float64" + }, + "execution_count": 182, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "choices_apply = nest_util.apply(lambda x: make_choices_ru_frozen_apply(x, nest_spec), axis=1)\n", + "choices_apply.value_counts() / n" + ], "metadata": { "collapsed": false, "pycharm": { From 9f4d2622b09f52e1cf33ac2389a5a04cd8a59e1f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 8 May 2022 21:32:31 +1000 Subject: [PATCH 019/298] check --- notebooks/validate_frozen_impl.ipynb | 296 +++++++++------------------ 1 file changed, 96 insertions(+), 200 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index f7270bdf64..00fd6d8c7b 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -181,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 187, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -230,8 +230,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Done\n", - "Running with %d trips 482\n" + "Done\n" ] }, { @@ -245,6 +244,7 @@ "name": "stdout", "output_type": "stream", "text": [ + "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", "trip_mode_choice tour_type 'escort' (6 trips)\n", @@ -256,8 +256,8 @@ "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n", - "CPU times: user 18.1 s, sys: 2.48 s, total: 20.6 s\n", - "Wall time: 17 s\n" + "CPU times: user 18.1 s, sys: 2.36 s, total: 20.5 s\n", + "Wall time: 17.3 s\n" ] } ], @@ -269,14 +269,14 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 188, "outputs": [ { "data": { - "text/plain": " trip_mode_fru trip_mode_asim\nWALK 304 304\nWALK_LOC 91 91\nWALK_LRF 49 47\nBIKE 15 17\nTNC_SINGLE 6 4\nDRIVEALONEFREE 5 4\nWALK_HVY 5 4\nSHARED2FREE 3 4\nTNC_SHARED 2 4\nTAXI 2 2", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK304304
WALK_LOC9191
WALK_LRF4947
BIKE1517
TNC_SINGLE64
DRIVEALONEFREE54
WALK_HVY54
SHARED2FREE34
TNC_SHARED24
TAXI22
\n
" + "text/plain": " trip_mode_fru trip_mode_asim\nWALK 308 304\nWALK_LOC 87 85\nWALK_LRF 54 53\nBIKE 15 17\nTNC_SINGLE 7 4\nDRIVEALONEFREE 5 4\nSHARED2FREE 3 4\nSHARED3FREE 2 1\nWALK_HVY 1 4", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK308304
WALK_LOC8785
WALK_LRF5453
BIKE1517
TNC_SINGLE74
DRIVEALONEFREE54
SHARED2FREE34
SHARED3FREE21
WALK_HVY14
\n
" }, - "execution_count": 27, + "execution_count": 188, "metadata": {}, "output_type": "execute_result" } @@ -296,14 +296,17 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 189, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logsums agree\n" - ] + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" } ], "source": [ @@ -321,13 +324,13 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 190, "outputs": [ { "data": { "text/plain": "trip_id\n106170305 4.050638\n106170309 0.553496\n1767013721 0.482067\n535694461 0.251889\n943749469 0.203583\n ... \n535672921 0.000000\n535620049 0.000000\n535388485 0.000000\n535388481 0.000000\n943749471 0.000000\nLength: 482, dtype: float64" }, - "execution_count": 12, + "execution_count": 190, "metadata": {}, "output_type": "execute_result" } @@ -345,14 +348,14 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 191, "outputs": [ { "data": { "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n642446345 WALK_LRF 1.634462 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n642446345 1.593075 ", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
642446345WALK_LRF1.634462WALK_LRF1.593075
\n
" }, - "execution_count": 13, + "execution_count": 191, "metadata": {}, "output_type": "execute_result" } @@ -369,18 +372,8 @@ }, { "cell_type": "code", - "execution_count": 14, - "outputs": [ - { - "data": { - "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n86627409 WALK_LRF 2.435199 WALK_LRF \n86627413 WALK_LRF 2.665463 WALK_LRF \n86673661 WALK_LRF 1.842576 WALK_LRF \n106170305 WALK_LRF -0.002341 WALK_LRF \n106170309 WALK_LRF -0.017607 WALK_LRF \n... ... ... ... \n1767186253 WALK_LRF 0.976319 WALK_LRF \n1767186581 WALK_HVY 1.712704 WALK_LRF \n1768237161 WALK 6.262944 WALK \n1768237167 WALK_LRF 11.191330 WALK \n2473024477 WALK_LRF 0.637076 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n86627409 2.407248 \n86627413 2.642707 \n86673661 1.834538 \n106170305 -0.011825 \n106170309 -0.027353 \n... ... \n1767186253 0.880819 \n1767186581 1.600736 \n1768237161 6.226927 \n1768237167 11.144496 \n2473024477 0.624304 \n\n[74 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
86627409WALK_LRF2.435199WALK_LRF2.407248
86627413WALK_LRF2.665463WALK_LRF2.642707
86673661WALK_LRF1.842576WALK_LRF1.834538
106170305WALK_LRF-0.002341WALK_LRF-0.011825
106170309WALK_LRF-0.017607WALK_LRF-0.027353
...............
1767186253WALK_LRF0.976319WALK_LRF0.880819
1767186581WALK_HVY1.712704WALK_LRF1.600736
1768237161WALK6.262944WALK6.226927
1768237167WALK_LRF11.191330WALK11.144496
2473024477WALK_LRF0.637076WALK_LRF0.624304
\n

74 rows × 4 columns

\n
" - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 192, + "outputs": [], "source": [ "#trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" ], @@ -393,7 +386,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 192, "outputs": [], "source": [], "metadata": { @@ -422,7 +415,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 193, "outputs": [ { "name": "stderr", @@ -464,7 +457,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 194, "outputs": [], "source": [ "#trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" @@ -478,7 +471,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 195, "outputs": [], "source": [ "#t.loc[t.index.isin(trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0].index.values)]\\\n", @@ -493,7 +486,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 196, "outputs": [], "source": [ "# nest_spec = ns[0]\n", @@ -509,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 197, "outputs": [], "source": [ "#base_probs[7] #615236801" @@ -523,14 +516,14 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 198, "outputs": [ { "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n642446345 1958677 1024353 social 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n642446345 1 14 9 80305793 social \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n642446345 NaN 10.0 WALK_LRF 1.729057 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
64244634519586771024353social1True114980305793socialNaN10.0WALK_LRF1.729057
\n
" + "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n642446345 1958677 1024353 social 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n642446345 1 14 9 80305793 social \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n642446345 NaN 10.0 WALK_LRF 1.593075 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
64244634519586771024353social1True114980305793socialNaN10.0WALK_LRF1.593075
\n
" }, - "execution_count": 34, + "execution_count": 198, "metadata": {}, "output_type": "execute_result" } @@ -547,7 +540,7 @@ }, { "cell_type": "code", - "execution_count": 183, + "execution_count": null, "outputs": [ { "name": "stderr", @@ -562,22 +555,15 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (10000 trips)\n", - "Done\n" + "trip_mode_choice tour_type 'social' (100000 trips)\n" ] - }, - { - "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004262 4.103657e-14 0.000084 0.00025 0.000114 \ntrip_mode 0.003800 0.000000e+00 0.000000 0.00020 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.039196 0.19705 0.759044 \ntrip_mode 0.038000 0.20070 0.757300 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0042624.103657e-140.0000840.000250.0001140.0391960.197050.759044
trip_mode0.0038000.000000e+000.0000000.000200.0000000.0380000.200700.757300
\n
" - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ - "num_samples = 10000\n", - "trip_id_to_check = 642446345 # 1e5 takes about 25s\n", + "%%time\n", + "\n", + "num_samples = 100000\n", + "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like XXX and 1e4 takes 1m21s\n", "# work 615236801 also in trace log\n", "# social 2464104885 642446345 1767182945\n", "\n", @@ -594,78 +580,52 @@ "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%%\n", + "is_executing": true } } }, { - "cell_type": "markdown", - "source": [ - "# Investigate differing probs\n", - "\n", - "is it due to clipping, due to idxmax always returning the first and by coincidence having equal probs somewhere, or\n", - "bad methodology?" - ], + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], "metadata": { "collapsed": false, "pycharm": { - "name": "#%% md\n" + "name": "#%%\n" } } }, { "cell_type": "code", - "execution_count": 121, + "execution_count": null, "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", "source": [ - "def compute_probs(nested_utils, nest_spec):\n", - " nested_exp_utils = compute_nested_probabilities(np.exp(nested_utils), nest_spec)\n", - " base_probabilities = pd.DataFrame(index=nested_exp_utils.index)\n", - " for nest in logit.each_nest(nest_spec, type='leaf', post_order=False):\n", - " # skip root: it has a prob of 1 but we didn't compute a nested probability column for it\n", - " ancestors = nest.ancestors[1:]\n", - " base_probabilities[nest.name] = nested_exp_utils[ancestors].prod(axis=1)\n", - " return base_probabilities\n", - "\n", - "def compute_nested_probabilities(nested_exp_utilities, nest_spec):\n", - " nested_probabilities = pd.DataFrame(index=nested_exp_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, type='node', post_order=False):\n", - " probs = u_to_p(nested_exp_utilities[nest.alternatives])\n", - " nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)\n", - " return nested_probabilities\n", - "\n", - "EXP_UTIL_MIN = 1e-300\n", - "EXP_UTIL_MAX = np.inf\n", - "\n", - "PROB_MIN = 0.0\n", - "PROB_MAX = 1.0\n", + "## try to improve runtime of apply\n", "\n", - "def u_to_p(utils, trace_label=None):\n", - " utils_arr = utils.values\n", - " np.clip(utils_arr, EXP_UTIL_MIN, EXP_UTIL_MAX, out=utils_arr)\n", - " utils_arr = np.where(utils_arr == EXP_UTIL_MIN, 0.0, utils_arr)\n", - " arr_sum = utils_arr.sum(axis=1)\n", - " inf_utils = np.isinf(arr_sum)\n", - " if inf_utils.any():\n", - " print(\"INF\")\n", - " with np.errstate(invalid='ignore', divide='ignore'):\n", - " np.divide(utils_arr, arr_sum.reshape(len(utils_arr), 1), out=utils_arr)\n", - " ## if allow_zero_probs, this will cause EXP_UTIL_MIN util rows to have all zero probabilities\n", - " utils_arr[np.isnan(utils_arr)] = PROB_MIN\n", - " np.clip(utils_arr, PROB_MIN, PROB_MAX, out=utils_arr)\n", - " probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)\n", - " return probs" + "try caching {nest_name: nest alternatives}\n" ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } } }, { "cell_type": "code", - "execution_count": 177, + "execution_count": 184, "outputs": [], "source": [ "from activitysim.core import logit\n", @@ -681,34 +641,29 @@ " df = df + logit.inverse_ev1_cdf(uniform_rands)\n", " return df\n", "\n", - "def group_nests_by_level(nest_spec):\n", - " # group nests by level, returns {level: [nest.name at that level]}\n", - " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in logit.each_nest(nest_spec):\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1 # only one root\n", - " return nest_levels\n", - "\n", - "def make_choices_ru_frozen(nested_utilities, nest_spec):\n", - " nest_utils_for_choice = add_ev1_random(nested_utilities)\n", - " all_alternatives = list(map(lambda x: x.name, filter(lambda x: x.is_leaf, logit.each_nest(nest_spec))))\n", - "\n", - " def is_alternative(name):\n", - " return name in all_alternatives\n", + "def make_choices_ru_frozen_apply(nested_utilities, nest_spec):\n", + " all_alternatives = [nest.name for nest in logit.each_nest(nest_spec, type='leaf')]\n", "\n", - " nest_utils_for_choice[\"choice\"] = None\n", - " for level, alts in group_nests_by_level(nest_spec).items():\n", + " for level, nests_at_level in logit.group_nests_by_level(nest_spec).items():\n", + " nest_alts = [nest.name for nest in nests_at_level]\n", " if level == 1:\n", + " assert len(nests_at_level) == 1\n", + " assert len(nest_alts) == 1\n", + " next_level_alts = nests_at_level[0].alternatives\n", " continue\n", - " no_choices_made_yet = nest_utils_for_choice[\"choice\"].isnull()\n", - " choice_this_level = nest_utils_for_choice.loc[no_choices_made_yet][alts].idxmax(1)\n", - " nest_utils_for_choice.loc[no_choices_made_yet, \"choice\"] = \\\n", - " np.where(choice_this_level.apply(is_alternative), choice_this_level, None)\n", "\n", - " assert not nest_utils_for_choice[\"choice\"].isnull().any(), \"No choice for XXX - implement reporting\"\n", - " choices = pd.Series(nest_utils_for_choice[\"choice\"], index=nested_utilities.index)\n", - " return choices, nest_utils_for_choice" + " # all alternatives from the previous level\n", + " alts_this_level = list(filter(lambda x: x in next_level_alts, nest_alts))\n", + " #print(f\"Level {level} alternatives are {alts_this_level}\")\n", + " choice_this_level = nested_utilities[nested_utilities.index.isin(alts_this_level)].idxmax()\n", + " #print(f\"choice is {choice_this_level}\")\n", + " if choice_this_level in all_alternatives:\n", + " return choice_this_level\n", + " chosen_nest = list(filter(lambda x: x.name == choice_this_level, nests_at_level))\n", + " assert len(chosen_nest) == 1\n", + " next_level_alts = chosen_nest[0].alternatives\n", + "\n", + " raise ValueError(\"This should never happen - no alternative found\")" ], "metadata": { "collapsed": false, @@ -719,7 +674,7 @@ }, { "cell_type": "code", - "execution_count": 178, + "execution_count": 185, "outputs": [ { "data": { @@ -750,89 +705,30 @@ }, { "cell_type": "code", - "execution_count": 179, + "execution_count": 186, "outputs": [ { - "data": { - "text/plain": "WALK_LRF 0.78148\nWALK_LOC 0.20604\nWALK 0.01194\nSHARED2FREE 0.00054\nName: choice, dtype: float64" - }, - "execution_count": 179, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "choices, nu_choices = make_choices_ru_frozen(nest_util, nest_spec)\n", - "choices.value_counts() / n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 180, - "outputs": [], - "source": [ - "def group_nests_by_level(nest_spec):\n", - " # group nests by level, returns {level: [nest]}\n", - " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in logit.each_nest(nest_spec):\n", - " nest_levels[n.level].append(n)\n", - " assert len(nest_levels[1]) == 1 # only one root\n", - " return nest_levels\n", - "\n", - "def make_choices_ru_frozen_apply(nested_utilities, nest_spec):\n", - " nest_utils_for_choice = add_ev1_random(nested_utilities)\n", - " all_alternatives = [nest.name for nest in logit.each_nest(nest_spec, type='leaf')]\n", - "\n", - " for level, nests_at_level in group_nests_by_level(nest_spec).items():\n", - " nest_alts = [nest.name for nest in nests_at_level]\n", - " if level == 1:\n", - " assert len(nests_at_level) == 1\n", - " assert len(nest_alts) == 1\n", - " next_level_alts = nests_at_level[0].alternatives\n", - " continue\n", - "\n", - " # all alternatives from the previous level\n", - " alts_this_level = list(filter(lambda x: x in next_level_alts, nest_alts))\n", - " #print(f\"Level {level} alternatives are {alts_this_level}\")\n", - " choice_this_level = nest_utils_for_choice[nest_utils_for_choice.index.isin(alts_this_level)].idxmax()\n", - " #print(f\"choice is {choice_this_level}\")\n", - " if choice_this_level in all_alternatives:\n", - " return choice_this_level\n", - " chosen_nest = list(filter(lambda x: x.name == choice_this_level, nests_at_level))\n", - " assert len(chosen_nest) == 1\n", - " next_level_alts = chosen_nest[0].alternatives\n", - "\n", - " raise ValueError(\"This should never happen - no alternative found\")\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 182, - "outputs": [ + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 1min 8s, sys: 1.91 s, total: 1min 10s\n", + "Wall time: 1min 9s\n" + ] + }, { "data": { - "text/plain": "WALK_LRF 0.75972\nWALK_LOC 0.19677\nWALK 0.03901\nSHARED2FREE 0.00406\nTNC_SHARED 0.00025\nTNC_SINGLE 0.00010\nTAXI 0.00009\ndtype: float64" + "text/plain": "WALK_LRF 0.75759\nWALK_LOC 0.19934\nWALK 0.03826\nSHARED2FREE 0.00428\nTNC_SHARED 0.00027\nTNC_SINGLE 0.00017\nTAXI 0.00009\ndtype: float64" }, - "execution_count": 182, + "execution_count": 186, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "choices_apply = nest_util.apply(lambda x: make_choices_ru_frozen_apply(x, nest_spec), axis=1)\n", + "%%time\n", + "\n", + "nest_utils_for_choice = add_ev1_random(nest_util)\n", + "choices_apply = nest_utils_for_choice.apply(lambda x: make_choices_ru_frozen_apply(x, nest_spec), axis=1)\n", "choices_apply.value_counts() / n" ], "metadata": { From 6c17d89bace65edff58d5426cbc34a6c8128518f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 8 May 2022 22:26:14 +1000 Subject: [PATCH 020/298] caching nest level names and alternatives --- activitysim/core/logit.py | 34 +++--- notebooks/validate_frozen_impl.ipynb | 159 ++++++++++++++------------- 2 files changed, 98 insertions(+), 95 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 8166607945..8e563ac3d7 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -210,24 +210,15 @@ def add_ev1_random(df): return nest_utils_for_choice -def choose_from_tree(nest_utils, nest_spec): - all_alternatives = [nest.name for nest in each_nest(nest_spec, type='leaf')] - for level, nests_at_level in group_nests_by_level(nest_spec).items(): - nest_alts = [nest.name for nest in nests_at_level] +def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name): + for level, nest_names in logit_nest_groups.items(): if level == 1: - assert len(nests_at_level) == 1 - assert len(nest_alts) == 1 - next_level_alts = nests_at_level[0].alternatives + next_level_alts = nest_alternatives_by_name[nest_names[0]] continue - # all alternatives from the previous level - alts_this_level = list(filter(lambda x: x in next_level_alts, nest_alts)) - choice_this_level = nest_utils[nest_utils.index.isin(alts_this_level)].idxmax() + choice_this_level = nest_utils[nest_utils.index.isin(next_level_alts)].idxmax() if choice_this_level in all_alternatives: return choice_this_level - chosen_nest = list(filter(lambda x: x.name == choice_this_level, nests_at_level)) - assert len(chosen_nest) == 1 - next_level_alts = chosen_nest[0].alternatives - + next_level_alts = nest_alternatives_by_name[choice_this_level] raise ValueError("This should never happen - no alternative found") @@ -235,8 +226,15 @@ def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_ """ walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') nest_utils_for_choice = add_ev1_random(nested_utilities) - # TODO: the following apply is slow, try to improve it - choices = nest_utils_for_choice.apply(lambda x: choose_from_tree(x, nest_spec), axis=1) + + all_alternatives = set(nest.name for nest in each_nest(nest_spec, type='leaf')) + logit_nest_groups = group_nest_names_by_level(nest_spec) + nest_alternatives_by_name = {n.name: n.alternatives for n in each_nest(nest_spec)} + + choices = nest_utils_for_choice.apply( + lambda x: choose_from_tree(x, all_alternatives, logit_nest_groups, nest_alternatives_by_name), + axis=1 + ) assert not choices.isnull().any(), "No choice for XXX - implement reporting" choices = pd.Series(choices, index=nested_utilities.index) return choices @@ -534,10 +532,10 @@ def count_each_nest(spec, count): return count_each_nest(nest_spec, 0) if nest_spec is not None else 0 -def group_nests_by_level(nest_spec): +def group_nest_names_by_level(nest_spec): # group nests by level, returns {level: [nest.name at that level]} depth = np.max([x.level for x in each_nest(nest_spec)]) nest_levels = {x: [] for x in range(1, depth+1)} for n in each_nest(nest_spec): - nest_levels[n.level].append(n) + nest_levels[n.level].append(n.name) return nest_levels diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 00fd6d8c7b..0a00395558 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -555,14 +555,14 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (100000 trips)\n" + "trip_mode_choice tour_type 'social' (100 trips)\n" ] } ], "source": [ "%%time\n", "\n", - "num_samples = 100000\n", + "num_samples = 100\n", "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like XXX and 1e4 takes 1m21s\n", "# work 615236801 also in trace log\n", "# social 2464104885 642446345 1767182945\n", @@ -598,38 +598,68 @@ } }, { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "cell_type": "markdown", + "source": [ + "## try to improve runtime of apply\n", + "\n", + "try caching {nest_name: nest alternatives}\n" + ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } } }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 239, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n642446345 0.0 0.0 0.004883 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n642446345 0.0 0.0 0.000096 0.000131 0.000287 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
6424463450.00.00.0048830.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.00.0000960.0001310.000287
\n
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "## try to improve runtime of apply\n", + "nest_spec = ns[7] # 7 is social\n", + "nest_util = nu[7].loc[nu[7].index == trip_id_to_check]\n", "\n", - "try caching {nest_name: nest alternatives}\n" + "n = 10000\n", + "\n", + "nest_util = nest_util.loc[nest_util.index.repeat(n)]\n", + "nest_util.index += np.arange(n)\n", + "\n", + "display(base_probs[7].loc[base_probs[7].index == trip_id_to_check])" ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%% md\n" + "name": "#%%\n" } } }, { "cell_type": "code", - "execution_count": 184, - "outputs": [], + "execution_count": 240, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 31.2 ms, sys: 0 ns, total: 31.2 ms\n", + "Wall time: 21.1 ms\n" + ] + } + ], "source": [ - "from activitysim.core import logit\n", + "%%time\n", + "\n", "from numpy.random import default_rng\n", + "from activitysim.core import logit\n", "\n", "rng = default_rng(999)\n", "\n", @@ -641,28 +671,37 @@ " df = df + logit.inverse_ev1_cdf(uniform_rands)\n", " return df\n", "\n", - "def make_choices_ru_frozen_apply(nested_utilities, nest_spec):\n", - " all_alternatives = [nest.name for nest in logit.each_nest(nest_spec, type='leaf')]\n", + "nest_utils_for_choice = add_ev1_random(nest_util)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 241, + "outputs": [], + "source": [ + "def group_nests_by_name(nest_spec):\n", + " # group nests by level, returns {level: [nest.name at that level]}\n", + " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", + " nest_levels = {x: [] for x in range(1, depth+1)}\n", + " for n in logit.each_nest(nest_spec):\n", + " nest_levels[n.level].append(n.name)\n", + " return nest_levels\n", "\n", - " for level, nests_at_level in logit.group_nests_by_level(nest_spec).items():\n", - " nest_alts = [nest.name for nest in nests_at_level]\n", + "def make_choices_apply(nested_utilities, all_alternatives, logit_nest_groups, nest_alternatives_by_name):\n", + " for level, nest_names in logit_nest_groups.items():\n", " if level == 1:\n", - " assert len(nests_at_level) == 1\n", - " assert len(nest_alts) == 1\n", - " next_level_alts = nests_at_level[0].alternatives\n", + " next_level_alts = nest_alternatives_by_name[nest_names[0]]\n", " continue\n", - "\n", - " # all alternatives from the previous level\n", - " alts_this_level = list(filter(lambda x: x in next_level_alts, nest_alts))\n", - " #print(f\"Level {level} alternatives are {alts_this_level}\")\n", - " choice_this_level = nested_utilities[nested_utilities.index.isin(alts_this_level)].idxmax()\n", - " #print(f\"choice is {choice_this_level}\")\n", + " choice_this_level = nested_utilities[nested_utilities.index.isin(next_level_alts)].idxmax()\n", " if choice_this_level in all_alternatives:\n", " return choice_this_level\n", - " chosen_nest = list(filter(lambda x: x.name == choice_this_level, nests_at_level))\n", - " assert len(chosen_nest) == 1\n", - " next_level_alts = chosen_nest[0].alternatives\n", - "\n", + " next_level_alts = nest_alternatives_by_name[choice_this_level]\n", " raise ValueError(\"This should never happen - no alternative found\")" ], "metadata": { @@ -674,27 +713,17 @@ }, { "cell_type": "code", - "execution_count": 185, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n642446345 0.0 0.0 0.004262 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n642446345 4.103657e-14 0.0 0.039196 0.0 0.19705 0.759044 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n642446345 0.0 0.0 0.000084 0.000114 0.00025 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
6424463450.00.00.0042620.04.103657e-140.00.0391960.00.197050.7590440.00.00.00.00.00.00.00.00.0000840.0001140.00025
\n
" - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": 242, + "outputs": [], "source": [ - "nest_spec = ns[7] # 7 is social\n", - "nest_util = nu[7].loc[nu[7].index == trip_id_to_check]\n", - "\n", - "n = 100000\n", + "all_alternatives = set(nest.name for nest in logit.each_nest(nest_spec, type='leaf'))\n", + "logit_nest_groups = group_nests_by_name(nest_spec)\n", + "nest_alternatives_by_name = {n.name: n.alternatives for n in logit.each_nest(nest_spec)}\n", "\n", - "nest_util = nest_util.loc[nest_util.index.repeat(n)]\n", - "nest_util.index += np.arange(n)\n", - "\n", - "display(base_probs[7].loc[base_probs[7].index == trip_id_to_check])" + "choices_apply = nest_utils_for_choice.apply(\n", + " lambda x: make_choices_apply(x, all_alternatives, logit_nest_groups, nest_alternatives_by_name),\n", + " axis=1\n", + ")" ], "metadata": { "collapsed": false, @@ -705,30 +734,18 @@ }, { "cell_type": "code", - "execution_count": 186, + "execution_count": 243, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 1min 8s, sys: 1.91 s, total: 1min 10s\n", - "Wall time: 1min 9s\n" - ] - }, { "data": { - "text/plain": "WALK_LRF 0.75759\nWALK_LOC 0.19934\nWALK 0.03826\nSHARED2FREE 0.00428\nTNC_SHARED 0.00027\nTNC_SINGLE 0.00017\nTAXI 0.00009\ndtype: float64" + "text/plain": "WALK_LRF 0.8383\nWALK_LOC 0.1158\nWALK 0.0405\nSHARED2FREE 0.0048\nTNC_SHARED 0.0003\nTAXI 0.0002\nTNC_SINGLE 0.0001\ndtype: float64" }, - "execution_count": 186, + "execution_count": 243, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "%%time\n", - "\n", - "nest_utils_for_choice = add_ev1_random(nest_util)\n", - "choices_apply = nest_utils_for_choice.apply(lambda x: make_choices_ru_frozen_apply(x, nest_spec), axis=1)\n", "choices_apply.value_counts() / n" ], "metadata": { @@ -774,18 +791,6 @@ } } }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, { "cell_type": "markdown", "source": [ From 798439208bf923919432f28bf5ae0f59e19412ec Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 8 May 2022 22:45:27 +1000 Subject: [PATCH 021/298] removes trace in runtime tests --- activitysim/core/logit.py | 2 +- notebooks/validate_frozen_impl.ipynb | 220 +++++---------------------- 2 files changed, 35 insertions(+), 187 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 8e563ac3d7..40a3a8671f 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -236,7 +236,7 @@ def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_ axis=1 ) assert not choices.isnull().any(), "No choice for XXX - implement reporting" - choices = pd.Series(choices, index=nested_utilities.index) + choices = pd.Series(choices, index=nest_utils_for_choice.index) return choices diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 0a00395558..130f805135 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -48,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 3, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -78,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 4, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -97,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 5, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -117,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 6, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -136,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 7, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -163,14 +163,14 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 8, "outputs": [], "source": [ - "# need to do this before loading checkpoint tables\n", - "trace_hh_ids = 1024353\n", - "#1024353 has trip id 642446345 (at least) where logsum and probs are different\n", - "#2821179 # has trip_ids 2464104881 and 2464104885\n", - "inject.add_injectable(\"trace_hh_id\", trace_hh_ids)" + "## need to do this before loading checkpoint tables\n", + "#trace_hh_ids = 1024353\n", + "##1024353 has trip id 642446345 (at least) where logsum and probs are different\n", + "##2821179 # has trip_ids 2464104881 and 2464104885\n", + "#inject.add_injectable(\"trace_hh_id\", trace_hh_ids)" ], "metadata": { "collapsed": false, @@ -181,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 187, + "execution_count": 9, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -198,7 +198,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [1024353].\n", + "setting trace_hh_id is wrong type, should be an int, but was \n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -219,20 +219,6 @@ "trip_mode_choice tour_type 'work' (168 trips)\n" ] }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [1024353].\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n" - ] - }, { "name": "stderr", "output_type": "stream", @@ -244,6 +230,7 @@ "name": "stdout", "output_type": "stream", "text": [ + "Done\n", "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", @@ -256,8 +243,8 @@ "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n", - "CPU times: user 18.1 s, sys: 2.36 s, total: 20.5 s\n", - "Wall time: 17.3 s\n" + "CPU times: user 16.9 s, sys: 2.78 s, total: 19.7 s\n", + "Wall time: 14.7 s\n" ] } ], @@ -269,14 +256,14 @@ }, { "cell_type": "code", - "execution_count": 188, + "execution_count": 10, "outputs": [ { "data": { "text/plain": " trip_mode_fru trip_mode_asim\nWALK 308 304\nWALK_LOC 87 85\nWALK_LRF 54 53\nBIKE 15 17\nTNC_SINGLE 7 4\nDRIVEALONEFREE 5 4\nSHARED2FREE 3 4\nSHARED3FREE 2 1\nWALK_HVY 1 4", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK308304
WALK_LOC8785
WALK_LRF5453
BIKE1517
TNC_SINGLE74
DRIVEALONEFREE54
SHARED2FREE34
SHARED3FREE21
WALK_HVY14
\n
" }, - "execution_count": 188, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -296,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 189, + "execution_count": 11, "outputs": [ { "data": { @@ -324,57 +311,12 @@ }, { "cell_type": "code", - "execution_count": 190, - "outputs": [ - { - "data": { - "text/plain": "trip_id\n106170305 4.050638\n106170309 0.553496\n1767013721 0.482067\n535694461 0.251889\n943749469 0.203583\n ... \n535672921 0.000000\n535620049 0.000000\n535388485 0.000000\n535388481 0.000000\n943749471 0.000000\nLength: 482, dtype: float64" - }, - "execution_count": 190, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ls_ = np.abs(((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru))\n", - "ls_.sort_values(ascending=False)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 191, - "outputs": [ - { - "data": { - "text/plain": " trip_mode_fru mode_choice_logsum_fru trip_mode_asim \\\ntrip_id \n642446345 WALK_LRF 1.634462 WALK_LRF \n\n mode_choice_logsum_asim \ntrip_id \n642446345 1.593075 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frumode_choice_logsum_frutrip_mode_asimmode_choice_logsum_asim
trip_id
642446345WALK_LRF1.634462WALK_LRF1.593075
\n
" - }, - "execution_count": 191, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trips.loc[trips.index == 642446345]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 192, + "execution_count": 12, "outputs": [], "source": [ + "#ls_ = np.abs(((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru))\n", + "#ls_.sort_values(ascending=False)\n", + "#trips.loc[trips.index == 642446345]\n", "#trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" ], "metadata": { @@ -384,18 +326,6 @@ } } }, - { - "cell_type": "code", - "execution_count": 192, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, { "cell_type": "markdown", "source": [ @@ -415,13 +345,12 @@ }, { "cell_type": "code", - "execution_count": 193, + "execution_count": 13, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [1024353].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -457,96 +386,12 @@ }, { "cell_type": "code", - "execution_count": 194, - "outputs": [], - "source": [ - "#trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 195, - "outputs": [], - "source": [ - "#t.loc[t.index.isin(trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0].index.values)]\\\n", - "# .sort_values(by=[\"primary_purpose\"])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 196, - "outputs": [], - "source": [ - "# nest_spec = ns[0]\n", - "# for nest in logit.each_nest(nest_spec, post_order=False):\n", - "# print(f\"{nest.level}, {nest.name}, {nest.coefficient}, parent scale: {nest.parent_scale}\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 197, - "outputs": [], - "source": [ - "#base_probs[7] #615236801" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 198, - "outputs": [ - { - "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n642446345 1958677 1024353 social 1 True \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n642446345 1 14 9 80305793 social \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n642446345 NaN 10.0 WALK_LRF 1.593075 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
64244634519586771024353social1True114980305793socialNaN10.0WALK_LRF1.593075
\n
" - }, - "execution_count": 198, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "t.loc[t.index == 642446345]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 20, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "register joint_tour_participants: no rows with household_id in [1024353].\n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -555,15 +400,19 @@ "output_type": "stream", "text": [ "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (100 trips)\n" + "trip_mode_choice tour_type 'social' (100000 trips)\n", + "Done\n", + "CPU times: user 1min 1s, sys: 3.78 s, total: 1min 5s\n", + "Wall time: 1min 2s\n" ] } ], "source": [ "%%time\n", + "#prun\n", "\n", - "num_samples = 100\n", - "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like XXX and 1e4 takes 1m21s\n", + "num_samples = 100000\n", + "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like 65s and 1e4 takes 8s\n", "# work 615236801 also in trace log\n", "# social 2464104885 642446345 1767182945\n", "\n", @@ -575,13 +424,12 @@ "\n", "choose_individual_max_utility = True\n", "ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", - "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" + "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n", - "is_executing": true + "name": "#%%\n" } } }, From ebf64c6a238aee78283d4816ab28bf00d27bfc13 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 9 May 2022 17:23:50 +1000 Subject: [PATCH 022/298] larch notebook to calculate probs a different way --- activitysim/core/simulate.py | 5 - .../larch_probabilities_via_estimation.ipynb | 3044 +++++++++++++++++ notebooks/validate_frozen_impl.ipynb | 12 +- 3 files changed, 3052 insertions(+), 9 deletions(-) create mode 100644 notebooks/larch_probabilities_via_estimation.ipynb diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 7bdb5ed7e2..be03116968 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1044,11 +1044,6 @@ def eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, estima compute_nested_probabilities(np.exp(nested_utilities), nest_spec, trace_label=trace_label) chunk.log_df(trace_label, "nested_probabilities", nested_probabilities) - if want_logsums: - # logsum of nest root - logsums = pd.Series(nested_utilities.root, index=choosers.index) - chunk.log_df(trace_label, "logsums", logsums) - if have_trace_targets: tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label, column_labels=['alternative', 'probability']) diff --git a/notebooks/larch_probabilities_via_estimation.ipynb b/notebooks/larch_probabilities_via_estimation.ipynb new file mode 100644 index 0000000000..0bb5f0a04d --- /dev/null +++ b/notebooks/larch_probabilities_via_estimation.ipynb @@ -0,0 +1,3044 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Probabilities for three level nests differ\n", + "\n", + "Asim scales nested utilties differently, for two level and RU2 it's identical to what we do (which agrees with the\n", + "larch paper and Zenith), but for three levels there are small differences of probabilities and logsums. I\n", + "quadruple-checked the scaling and leaf and note utility calculation, let's use larch to see if we get the same as\n", + "Asim or if the results agree with my implementation. The idea is to use estimation mode to load a small data set, and\n", + " then use my Asim setup in validate_frozen_impl.ipynb to calculate Asim's and my probabilities by sampling, and check\n", + " what larch has to say by using m.probabilities() (no need to estimate, use parameters as given by asim model).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "# I installed larch deps (which I collected in a requirements file, xmle and sharrow are not on pypi)\n", + "# python -m pip install -r ../larch/requirements.txt\n", + "\n", + "larch_path = \"/mnt/c/Users/jan.zill/code/larch\"\n", + "if larch_path not in sys.path:\n", + " sys.path.append(larch_path)\n", + "import larch" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 105, + "outputs": [], + "source": [ + "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_estimation_sf\")\n", + "os.chdir(example_dir)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 106, + "outputs": [], + "source": [ + "# create estimation test example\n", + "#!activitysim create -e example_estimation_sf -d test_estimation_sf" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 13, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Configured logging using basicConfig\r\n", + "INFO:activitysim:Configured logging using basicConfig\r\n", + "INFO - Read logging configuration from: configs_estimation/configs/logging.yaml\r\n", + "INFO - SETTING configs_dir: ['configs_estimation/configs', 'configs']\r\n", + "INFO - SETTING settings_file_name: settings.yaml\r\n", + "INFO - SETTING data_dir: ['data_sf']\r\n", + "INFO - SETTING output_dir: output\r\n", + "INFO - SETTING households_sample_size: 0\r\n", + "INFO - SETTING chunk_size: 0\r\n", + "INFO - SETTING chunk_method: hybrid_uss\r\n", + "INFO - SETTING chunk_training_mode: disabled\r\n", + "INFO - SETTING multiprocess: None\r\n", + "INFO - SETTING num_processes: None\r\n", + "INFO - SETTING resume_after: None\r\n", + "INFO - SETTING trace_hh_id: None\r\n", + "INFO - ENV MKL_NUM_THREADS: None\r\n", + "INFO - ENV OMP_NUM_THREADS: None\r\n", + "INFO - ENV OPENBLAS_NUM_THREADS: None\r\n", + "INFO - NUMPY blas_opt_info libraries: ['openblas', 'openblas']\r\n", + "INFO - NUMPY lapack_opt_info libraries: ['openblas', 'openblas']\r\n", + "INFO - run single process simulation\r\n", + "INFO - Time to execute open_pipeline : 0.029 seconds (0.0 minutes)\r\n", + "INFO - preload_injectables\r\n", + "INFO - Time to execute preload_injectables : 0.036 seconds (0.0 minutes)\r\n", + "INFO - #run_model running step initialize_landuse\r\n", + "Running step 'initialize_landuse'\r\n", + "INFO - Reading CSV file data_sf/land_use.csv\r\n", + "INFO - loaded land_use (190, 24)\r\n", + "INFO - initialize_landuse.annotate_tables - annotating land_use SPEC annotate_landuse\r\n", + "INFO - Network_LOS using skim_dict_factory: NumpyArraySkimFactory\r\n", + "INFO - allocate_skim_buffer shared False taz shape (826, 190, 190) total size: 119_274_400 (119.3 MB)\r\n", + "INFO - _read_skims_from_omx data_sf/skims.omx\r\n", + "INFO - _read_skims_from_omx loaded 826 skims from data_sf/skims.omx\r\n", + "INFO - writing skim cache taz (826, 190, 190) to output/cache/cached_taz.mmap\r\n", + "INFO - load_skims_to_buffer taz shape (826, 190, 190)\r\n", + "INFO - get_skim_data taz SkimData shape (826, 190, 190)\r\n", + "INFO - SkimDict init taz\r\n", + "INFO - SkimDict.build_3d_skim_block_offset_table registered 167 3d keys\r\n", + "Time to execute step 'initialize_landuse': 3.69 s\r\n", + "Total time to execute iteration 1 with iteration value None: 3.69 s\r\n", + "INFO - #run_model running step initialize_households\r\n", + "Running step 'initialize_households'\r\n", + "INFO - Reading CSV file data_sf/survey_data/override_households.csv\r\n", + "INFO - full household list contains 2000 households\r\n", + "INFO - loaded households (2000, 7)\r\n", + "INFO - Reading CSV file data_sf/survey_data/override_persons.csv\r\n", + "INFO - loaded persons (4405, 7)\r\n", + "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons\r\n", + "INFO - initialize_households.annotate_tables - annotating households SPEC annotate_households\r\n", + "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons_after_hh\r\n", + "Time to execute step 'initialize_households': 0.39 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.39 s\r\n", + "INFO - #run_model running step compute_accessibility\r\n", + "Running step 'compute_accessibility'\r\n", + "INFO - Running compute_accessibility with 190 orig zones 190 dest zones\r\n", + "INFO - compute_accessibility Running adaptive_chunked_choosers with 190 choosers\r\n", + "INFO - Running chunk 1 of 1 with 190 of 190 choosers\r\n", + "INFO - Running compute_accessibility with 190 orig zones 190 dest zones\r\n", + "INFO - compute_accessibility computed accessibilities (190, 10)\r\n", + "Time to execute step 'compute_accessibility': 0.12 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.12 s\r\n", + "INFO - #run_model running step school_location\r\n", + "Running step 'school_location'\r\n", + "INFO - Initialize Estimator for'school_location'\r\n", + "DEBUG - school_location: estimate.write_spec: output/estimation_data_bundle/school_location/school_location_SPEC.csv\r\n", + "DEBUG - school_location: write_table write: school_location_coefficients.csv\r\n", + "DEBUG - school_location: write_table write: size_terms\r\n", + "DEBUG - school_location: write_table write: landuse\r\n", + "INFO - Running school_location.i1.sample.university with 329 persons\r\n", + "INFO - Estimation mode for school_location.i1.sample.university using unsampled alternatives short_circuit_choices\r\n", + "INFO - school_location.i1.sample.university.interaction_sample Running adaptive_chunked_choosers with 329 choosers\r\n", + "INFO - Running chunk 1 of 1 with 329 of 329 choosers\r\n", + "INFO - Running eval_interaction_utilities on 5593 rows\r\n", + "INFO - Estimation mode for school_location.i1.sample.university.interaction_sample using unsampled alternatives\r\n", + "INFO - Running school_location.i1.logsums.university with 5593 rows\r\n", + "INFO - school_location.i1.logsums.university.compute_logsums Running adaptive_chunked_choosers with 5593 choosers\r\n", + "INFO - Running chunk 1 of 1 with 5593 of 5593 choosers\r\n", + "INFO - Running school_location.i1.simulate.university with 329 persons\r\n", + "DEBUG - school_location: write_table cache: choosers\r\n", + "DEBUG - school_location: write_table cache: interaction_sample_alternatives\r\n", + "INFO - school_location.i1.simulate.university.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 329 choosers and 5593 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 329 of 329 choosers\r\n", + "INFO - Running eval_interaction_utilities on 5593 rows\r\n", + "INFO - school_location: eval_interaction_utilities write_interaction_expression_values school_location.i1.simulate.university.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - school_location: write_table cache: interaction_expression_values\r\n", + "DEBUG - school_location: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - school_location: write_table cache: override_choices\r\n", + "INFO - Running school_location.i1.sample.highschool with 111 persons\r\n", + "INFO - Estimation mode for school_location.i1.sample.highschool using unsampled alternatives short_circuit_choices\r\n", + "INFO - school_location.i1.sample.highschool.interaction_sample Running adaptive_chunked_choosers with 111 choosers\r\n", + "INFO - Running chunk 1 of 1 with 111 of 111 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2775 rows\r\n", + "INFO - Estimation mode for school_location.i1.sample.highschool.interaction_sample using unsampled alternatives\r\n", + "INFO - Running school_location.i1.logsums.highschool with 2775 rows\r\n", + "INFO - school_location.i1.logsums.highschool.compute_logsums Running adaptive_chunked_choosers with 2775 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2775 of 2775 choosers\r\n", + "INFO - Running school_location.i1.simulate.highschool with 111 persons\r\n", + "DEBUG - school_location: write_table cache: choosers\r\n", + "DEBUG - school_location: write_table cache: interaction_sample_alternatives\r\n", + "INFO - school_location.i1.simulate.highschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 111 choosers and 2775 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 111 of 111 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2775 rows\r\n", + "INFO - school_location: eval_interaction_utilities write_interaction_expression_values school_location.i1.simulate.highschool.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - school_location: write_table cache: interaction_expression_values\r\n", + "DEBUG - school_location: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - school_location: write_table cache: override_choices\r\n", + "INFO - Running school_location.i1.sample.gradeschool with 544 persons\r\n", + "INFO - Estimation mode for school_location.i1.sample.gradeschool using unsampled alternatives short_circuit_choices\r\n", + "INFO - school_location.i1.sample.gradeschool.interaction_sample Running adaptive_chunked_choosers with 544 choosers\r\n", + "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", + "INFO - Running eval_interaction_utilities on 103360 rows\r\n", + "INFO - Estimation mode for school_location.i1.sample.gradeschool.interaction_sample using unsampled alternatives\r\n", + "INFO - Running school_location.i1.logsums.gradeschool with 103360 rows\r\n", + "INFO - school_location.i1.logsums.gradeschool.compute_logsums Running adaptive_chunked_choosers with 103360 choosers\r\n", + "INFO - Running chunk 1 of 1 with 103360 of 103360 choosers\r\n", + "INFO - Running school_location.i1.simulate.gradeschool with 544 persons\r\n", + "DEBUG - school_location: write_table cache: choosers\r\n", + "DEBUG - school_location: write_table cache: interaction_sample_alternatives\r\n", + "INFO - school_location.i1.simulate.gradeschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 544 choosers and 103360 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", + "INFO - Running eval_interaction_utilities on 103360 rows\r\n", + "INFO - school_location: eval_interaction_utilities write_interaction_expression_values school_location.i1.simulate.gradeschool.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - school_location: write_table cache: interaction_expression_values\r\n", + "DEBUG - school_location: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - school_location: write_table cache: override_choices\r\n", + "INFO - write_trace_files iteration 1\r\n", + "INFO - school_location_logsum top 10 value counts:\r\n", + "-1.824183 2\r\n", + "-0.909839 2\r\n", + "-1.366984 2\r\n", + "-1.278060 1\r\n", + "-1.180527 1\r\n", + "-1.674889 1\r\n", + "-1.291101 1\r\n", + "-1.813244 1\r\n", + "-1.069701 1\r\n", + " 0.053658 1\r\n", + "Name: logsum, dtype: int64\r\n", + "DEBUG - school_location: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - school_location: write_omnibus_choosers: output/estimation_data_bundle/school_location/school_location_choosers_combined.csv\r\n", + "DEBUG - school_location: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "DEBUG - school_location: write_omnibus_choosers: output/estimation_data_bundle/school_location/school_location_alternatives_combined.csv\r\n", + "INFO - school_location: end estimation\r\n", + "Time to execute step 'school_location': 15.20 s\r\n", + "Total time to execute iteration 1 with iteration value None: 15.21 s\r\n", + "INFO - #run_model running step workplace_location\r\n", + "Running step 'workplace_location'\r\n", + "INFO - Initialize Estimator for'workplace_location'\r\n", + "DEBUG - workplace_location: estimate.write_spec: output/estimation_data_bundle/workplace_location/workplace_location_SPEC.csv\r\n", + "DEBUG - workplace_location: write_table write: workplace_location_coefficients.csv\r\n", + "DEBUG - workplace_location: write_table write: size_terms\r\n", + "DEBUG - workplace_location: write_table write: landuse\r\n", + "INFO - Running workplace_location.i1.sample.work_low with 518 persons\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_low using unsampled alternatives short_circuit_choices\r\n", + "INFO - workplace_location.i1.sample.work_low.interaction_sample Running adaptive_chunked_choosers with 518 choosers\r\n", + "INFO - Running chunk 1 of 1 with 518 of 518 choosers\r\n", + "INFO - Running eval_interaction_utilities on 98420 rows\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_low.interaction_sample using unsampled alternatives\r\n", + "INFO - Running workplace_location.i1.logsums.work_low with 98420 rows\r\n", + "INFO - workplace_location.i1.logsums.work_low.compute_logsums Running adaptive_chunked_choosers with 98420 choosers\r\n", + "INFO - Running chunk 1 of 1 with 98420 of 98420 choosers\r\n", + "INFO - Running workplace_location.i1.simulate.work_low with 518 persons\r\n", + "DEBUG - workplace_location: write_table cache: choosers\r\n", + "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", + "INFO - workplace_location.i1.simulate.work_low.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 518 choosers and 98420 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 518 of 518 choosers\r\n", + "INFO - Running eval_interaction_utilities on 98420 rows\r\n", + "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_low.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", + "DEBUG - workplace_location: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - workplace_location: write_table cache: override_choices\r\n", + "INFO - Running workplace_location.i1.sample.work_med with 545 persons\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_med using unsampled alternatives short_circuit_choices\r\n", + "INFO - workplace_location.i1.sample.work_med.interaction_sample Running adaptive_chunked_choosers with 545 choosers\r\n", + "INFO - Running chunk 1 of 1 with 545 of 545 choosers\r\n", + "INFO - Running eval_interaction_utilities on 103550 rows\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_med.interaction_sample using unsampled alternatives\r\n", + "INFO - Running workplace_location.i1.logsums.work_med with 103550 rows\r\n", + "INFO - workplace_location.i1.logsums.work_med.compute_logsums Running adaptive_chunked_choosers with 103550 choosers\r\n", + "INFO - Running chunk 1 of 1 with 103550 of 103550 choosers\r\n", + "INFO - Running workplace_location.i1.simulate.work_med with 545 persons\r\n", + "DEBUG - workplace_location: write_table cache: choosers\r\n", + "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", + "INFO - workplace_location.i1.simulate.work_med.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 545 choosers and 103550 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 545 of 545 choosers\r\n", + "INFO - Running eval_interaction_utilities on 103550 rows\r\n", + "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_med.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", + "DEBUG - workplace_location: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - workplace_location: write_table cache: override_choices\r\n", + "INFO - Running workplace_location.i1.sample.work_high with 614 persons\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_high using unsampled alternatives short_circuit_choices\r\n", + "INFO - workplace_location.i1.sample.work_high.interaction_sample Running adaptive_chunked_choosers with 614 choosers\r\n", + "INFO - Running chunk 1 of 1 with 614 of 614 choosers\r\n", + "INFO - Running eval_interaction_utilities on 116660 rows\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_high.interaction_sample using unsampled alternatives\r\n", + "INFO - Running workplace_location.i1.logsums.work_high with 116660 rows\r\n", + "INFO - workplace_location.i1.logsums.work_high.compute_logsums Running adaptive_chunked_choosers with 116660 choosers\r\n", + "INFO - Running chunk 1 of 1 with 116660 of 116660 choosers\r\n", + "INFO - Running workplace_location.i1.simulate.work_high with 614 persons\r\n", + "DEBUG - workplace_location: write_table cache: choosers\r\n", + "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", + "INFO - workplace_location.i1.simulate.work_high.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 614 choosers and 116660 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 614 of 614 choosers\r\n", + "INFO - Running eval_interaction_utilities on 116660 rows\r\n", + "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_high.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", + "DEBUG - workplace_location: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - workplace_location: write_table cache: override_choices\r\n", + "INFO - Running workplace_location.i1.sample.work_veryhigh with 905 persons\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_veryhigh using unsampled alternatives short_circuit_choices\r\n", + "INFO - workplace_location.i1.sample.work_veryhigh.interaction_sample Running adaptive_chunked_choosers with 905 choosers\r\n", + "INFO - Running chunk 1 of 1 with 905 of 905 choosers\r\n", + "INFO - Running eval_interaction_utilities on 171950 rows\r\n", + "INFO - Estimation mode for workplace_location.i1.sample.work_veryhigh.interaction_sample using unsampled alternatives\r\n", + "INFO - Running workplace_location.i1.logsums.work_veryhigh with 171950 rows\r\n", + "INFO - workplace_location.i1.logsums.work_veryhigh.compute_logsums Running adaptive_chunked_choosers with 171950 choosers\r\n", + "INFO - Running chunk 1 of 1 with 171950 of 171950 choosers\r\n", + "INFO - Running workplace_location.i1.simulate.work_veryhigh with 905 persons\r\n", + "DEBUG - workplace_location: write_table cache: choosers\r\n", + "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", + "INFO - workplace_location.i1.simulate.work_veryhigh.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 905 choosers and 171950 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 905 of 905 choosers\r\n", + "INFO - Running eval_interaction_utilities on 171950 rows\r\n", + "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_veryhigh.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", + "DEBUG - workplace_location: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - workplace_location: write_table cache: override_choices\r\n", + "INFO - write_trace_files iteration 1\r\n", + "INFO - workplace_location_logsum top 10 value counts:\r\n", + "-0.007794 2\r\n", + " 1.586426 2\r\n", + "-1.938350 2\r\n", + " 0.491470 2\r\n", + " 0.267120 2\r\n", + " 0.047726 2\r\n", + " 0.657742 1\r\n", + "-0.051882 1\r\n", + " 6.017921 1\r\n", + "-0.325755 1\r\n", + "Name: logsum, dtype: int64\r\n", + "DEBUG - workplace_location: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - workplace_location: write_omnibus_choosers: output/estimation_data_bundle/workplace_location/workplace_location_choosers_combined.csv\r\n", + "DEBUG - workplace_location: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "DEBUG - workplace_location: write_omnibus_choosers: output/estimation_data_bundle/workplace_location/workplace_location_alternatives_combined.csv\r\n", + "INFO - workplace_location: end estimation\r\n", + "Time to execute step 'workplace_location': 51.96 s\r\n", + "Total time to execute iteration 1 with iteration value None: 51.96 s\r\n", + "INFO - #run_model running step auto_ownership_simulate\r\n", + "Running step 'auto_ownership_simulate'\r\n", + "INFO - Initialize Estimator for'auto_ownership'\r\n", + "INFO - Running auto_ownership_simulate with 2000 households\r\n", + "DEBUG - auto_ownership: estimate.write_spec: output/estimation_data_bundle/auto_ownership/auto_ownership_SPEC.csv\r\n", + "DEBUG - auto_ownership: write_table write: auto_ownership_coefficients.csv\r\n", + "DEBUG - auto_ownership: write_table cache: choosers\r\n", + "INFO - auto_ownership_simulate.simple_simulate Running adaptive_chunked_choosers with 2000 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2000 of 2000 choosers\r\n", + "DEBUG - auto_ownership: write_table cache: expression_values\r\n", + "DEBUG - auto_ownership: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using households.index\r\n", + "DEBUG - auto_ownership: write_table cache: override_choices\r\n", + "DEBUG - auto_ownership: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - auto_ownership: write_omnibus_choosers: output/estimation_data_bundle/auto_ownership/auto_ownership_values_combined.csv\r\n", + "INFO - auto_ownership: end estimation\r\n", + "INFO - auto_ownership top 10 value counts:\r\n", + "1 883\r\n", + "0 615\r\n", + "2 390\r\n", + "3 74\r\n", + "4 38\r\n", + "Name: auto_ownership, dtype: int64\r\n", + "Time to execute step 'auto_ownership_simulate': 0.41 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.41 s\r\n", + "INFO - #run_model running step free_parking\r\n", + "Running step 'free_parking'\r\n", + "INFO - Running free_parking with 2582 persons\r\n", + "INFO - Initialize Estimator for'free_parking'\r\n", + "DEBUG - free_parking: estimate.write_spec: output/estimation_data_bundle/free_parking/free_parking_SPEC.csv\r\n", + "DEBUG - free_parking: write_table write: free_parking_coefficients.csv\r\n", + "DEBUG - free_parking: write_table cache: choosers\r\n", + "INFO - free_parking.simple_simulate Running adaptive_chunked_choosers with 2582 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2582 of 2582 choosers\r\n", + "DEBUG - free_parking: write_table cache: expression_values\r\n", + "DEBUG - free_parking: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - free_parking: write_table cache: override_choices\r\n", + "DEBUG - free_parking: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - free_parking: write_omnibus_choosers: output/estimation_data_bundle/free_parking/free_parking_values_combined.csv\r\n", + "INFO - free_parking: end estimation\r\n", + "INFO - free_parking top 10 value counts:\r\n", + "False 4272\r\n", + "True 133\r\n", + "Name: free_parking_at_work, dtype: int64\r\n", + "Time to execute step 'free_parking': 0.31 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.31 s\r\n", + "INFO - #run_model running step cdap_simulate\r\n", + "Running step 'cdap_simulate'\r\n", + "INFO - Initialize Estimator for'cdap'\r\n", + "INFO - Pre-building cdap specs\r\n", + "INFO - Time to execute build_cdap_spec hh_size 2 : 0.214 seconds (0.0 minutes)\r\n", + "INFO - Time to execute build_cdap_spec hh_size 3 : 0.5 seconds (0.0 minutes)\r\n", + "INFO - Time to execute build_cdap_spec hh_size 4 : 1.268 seconds (0.0 minutes)\r\n", + "INFO - Time to execute build_cdap_spec hh_size 5 : 3.119 seconds (0.1 minutes)\r\n", + "DEBUG - cdap: estimate.write_spec: output/estimation_data_bundle/cdap/cdap_INDIV_AND_HHSIZE1_SPEC.csv\r\n", + "DEBUG - cdap: estimate.write_spec: output/estimation_data_bundle/cdap/cdap_FIXED_RELATIVE_PROPORTIONS_SPEC.csv\r\n", + "DEBUG - cdap: write_table write: cdap_coefficients.csv\r\n", + "DEBUG - cdap: write_table write: interaction_coefficients\r\n", + "DEBUG - cdap: write_table cache: choosers\r\n", + "DEBUG - cdap: write_table write: spec_2\r\n", + "DEBUG - cdap: write_table write: spec_3\r\n", + "DEBUG - cdap: write_table write: spec_4\r\n", + "DEBUG - cdap: write_table write: spec_5\r\n", + "INFO - Running cdap_simulate with 4405 persons\r\n", + "INFO - cdap.cdap Running chunk 1 of 1 with 2000 of 2000 choosers\r\n", + "DEBUG - cdap: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - cdap: write_table cache: override_choices\r\n", + "DEBUG - cdap: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - cdap: write_omnibus_choosers: output/estimation_data_bundle/cdap/cdap_values_combined.csv\r\n", + "INFO - cdap: end estimation\r\n", + "INFO - cdap_activity top 10 value counts:\r\n", + "M 2620\r\n", + "N 1213\r\n", + "H 572\r\n", + "Name: cdap_activity, dtype: int64\r\n", + "INFO - cdap crosstabs:\r\n", + "cdap_activity H M N All\r\n", + "ptype \r\n", + "1 156 1606 158 1920\r\n", + "2 47 331 118 496\r\n", + "3 33 230 66 329\r\n", + "4 114 0 420 534\r\n", + "5 114 0 357 471\r\n", + "6 22 42 8 72\r\n", + "7 39 274 39 352\r\n", + "8 47 137 47 231\r\n", + "All 572 2620 1213 4405\r\n", + "Time to execute step 'cdap_simulate': 9.84 s\r\n", + "Total time to execute iteration 1 with iteration value None: 9.84 s\r\n", + "INFO - #run_model running step mandatory_tour_frequency\r\n", + "Running step 'mandatory_tour_frequency'\r\n", + "INFO - Running mandatory_tour_frequency with 2620 persons\r\n", + "INFO - Initialize Estimator for'mandatory_tour_frequency'\r\n", + "DEBUG - mandatory_tour_frequency: estimate.write_spec: output/estimation_data_bundle/mandatory_tour_frequency/mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - mandatory_tour_frequency: write_table write: mandatory_tour_frequency_coefficients.csv\r\n", + "DEBUG - mandatory_tour_frequency: write_table cache: choosers\r\n", + "INFO - mandatory_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 2620 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2620 of 2620 choosers\r\n", + "DEBUG - mandatory_tour_frequency: write_table cache: expression_values\r\n", + "DEBUG - mandatory_tour_frequency: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - mandatory_tour_frequency: write_table cache: override_choices\r\n", + "DEBUG - mandatory_tour_frequency: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - mandatory_tour_frequency: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_frequency/mandatory_tour_frequency_values_combined.csv\r\n", + "INFO - mandatory_tour_frequency: end estimation\r\n", + "INFO - mandatory_tour_frequency top 10 value counts:\r\n", + "work1 1941\r\n", + " 1785\r\n", + "school1 566\r\n", + "work2 81\r\n", + "work_and_school 21\r\n", + "school2 11\r\n", + "Name: mandatory_tour_frequency, dtype: int64\r\n", + "Time to execute step 'mandatory_tour_frequency': 1.21 s\r\n", + "Total time to execute iteration 1 with iteration value None: 1.21 s\r\n", + "INFO - #run_model running step mandatory_tour_scheduling\r\n", + "Running step 'mandatory_tour_scheduling'\r\n", + "DEBUG - @inject timetable\r\n", + "INFO - Initialize Estimator for'mandatory_tour_scheduling_work'\r\n", + "DEBUG - mandatory_tour_scheduling_work: estimate.write_spec: output/estimation_data_bundle/mandatory_tour_scheduling_work/mandatory_tour_scheduling_work_SPEC.csv\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table write: tour_scheduling_work_coefficients.csv\r\n", + "INFO - Initialize Estimator for'mandatory_tour_scheduling_school'\r\n", + "DEBUG - mandatory_tour_scheduling_school: estimate.write_spec: output/estimation_data_bundle/mandatory_tour_scheduling_school/mandatory_tour_scheduling_school_SPEC.csv\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table write: tour_scheduling_school_coefficients.csv\r\n", + "INFO - mandatory_tour_scheduling_work: timetable.begin_transaction person_windows\r\n", + "INFO - mandatory_tour_scheduling_school: timetable.begin_transaction person_windows\r\n", + "INFO - Running mandatory_tour_scheduling with 2733 tours\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 2043 tour choices\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work Running adaptive_chunked_choosers with 2043 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2043 of 2043 choosers\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 2043 tour choices\r\n", + "INFO - tdd_alt_segments specified for representative logsums\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 388170 to 30645 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums compute_logsums for 30645 choosers 30645 alts\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums Running adaptive_chunked_choosers with 30645 choosers\r\n", + "INFO - Running chunk 1 of 1 with 30645 of 30645 choosers\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: choosers\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_sample_alternatives\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 2043 choosers and 388170 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 2043 of 2043 choosers\r\n", + "INFO - Running eval_interaction_utilities on 388170 rows\r\n", + "INFO - mandatory_tour_scheduling_work: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_expression_values\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 451 tour choices\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school Running adaptive_chunked_choosers with 451 choosers\r\n", + "INFO - Running chunk 1 of 1 with 451 of 451 choosers\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 451 tour choices\r\n", + "INFO - tdd_alt_segments specified for representative logsums\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 85690 to 6765 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums compute_logsums for 6765 choosers 6765 alts\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums Running adaptive_chunked_choosers with 6765 choosers\r\n", + "INFO - Running chunk 1 of 1 with 6765 of 6765 choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 451 choosers and 85690 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 451 of 451 choosers\r\n", + "INFO - Running eval_interaction_utilities on 85690 rows\r\n", + "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 126 tour choices\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ Running adaptive_chunked_choosers with 126 choosers\r\n", + "INFO - Running chunk 1 of 1 with 126 of 126 choosers\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 126 tour choices\r\n", + "INFO - tdd_alt_segments specified for representative logsums\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 23940 to 1890 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums compute_logsums for 1890 choosers 1890 alts\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums Running adaptive_chunked_choosers with 1890 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1890 of 1890 choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 126 choosers and 23940 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 126 of 126 choosers\r\n", + "INFO - Running eval_interaction_utilities on 23940 rows\r\n", + "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work schedule_tours running 81 tour choices\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work Running adaptive_chunked_choosers with 81 choosers\r\n", + "INFO - Running chunk 1 of 1 with 81 of 81 choosers\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work schedule_tours running 81 tour choices\r\n", + "INFO - tdd_alt_segments specified for representative logsums\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 89.13% from 6074 to 660 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.compute_logsums.logsums compute_logsums for 660 choosers 660 alts\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.compute_logsums.logsums Running adaptive_chunked_choosers with 660 choosers\r\n", + "INFO - Running chunk 1 of 1 with 660 of 660 choosers\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: choosers\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_sample_alternatives\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 81 choosers and 6074 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 81 of 81 choosers\r\n", + "INFO - Running eval_interaction_utilities on 6074 rows\r\n", + "INFO - mandatory_tour_scheduling_work: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_expression_values\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school schedule_tours running 9 tour choices\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school Running adaptive_chunked_choosers with 9 choosers\r\n", + "INFO - Running chunk 1 of 1 with 9 of 9 choosers\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school schedule_tours running 9 tour choices\r\n", + "INFO - tdd_alt_segments specified for representative logsums\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 89.84% from 817 to 83 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.compute_logsums.logsums compute_logsums for 83 choosers 83 alts\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.compute_logsums.logsums Running adaptive_chunked_choosers with 83 choosers\r\n", + "INFO - Running chunk 1 of 1 with 83 of 83 choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 9 choosers and 817 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 9 of 9 choosers\r\n", + "INFO - Running eval_interaction_utilities on 817 rows\r\n", + "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ schedule_tours running 23 tour choices\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ Running adaptive_chunked_choosers with 23 choosers\r\n", + "INFO - Running chunk 1 of 1 with 23 of 23 choosers\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ schedule_tours running 23 tour choices\r\n", + "INFO - tdd_alt_segments specified for representative logsums\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 90.26% from 2176 to 212 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.compute_logsums.logsums compute_logsums for 212 choosers 212 alts\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.compute_logsums.logsums Running adaptive_chunked_choosers with 212 choosers\r\n", + "INFO - Running chunk 1 of 1 with 212 of 212 choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 23 choosers and 2176 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 23 of 23 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2176 rows\r\n", + "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_table cache: override_choices\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_work/mandatory_tour_scheduling_work_choosers_combined.csv\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "DEBUG - mandatory_tour_scheduling_work: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_work/mandatory_tour_scheduling_work_alternatives_combined.csv\r\n", + "INFO - mandatory_tour_scheduling_work: end estimation\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_table cache: override_choices\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_school/mandatory_tour_scheduling_school_choosers_combined.csv\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "DEBUG - mandatory_tour_scheduling_school: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_school/mandatory_tour_scheduling_school_alternatives_combined.csv\r\n", + "INFO - mandatory_tour_scheduling_school: end estimation\r\n", + "INFO - mandatory_tour_scheduling_work: timetable.rollback person_windows\r\n", + "INFO - mandatory_tour_scheduling_school: timetable.rollback person_windows\r\n", + "Time to execute step 'mandatory_tour_scheduling': 84.87 s\r\n", + "Total time to execute iteration 1 with iteration value None: 84.87 s\r\n", + "INFO - #run_model running step joint_tour_frequency\r\n", + "Running step 'joint_tour_frequency'\r\n", + "INFO - Initialize Estimator for'joint_tour_frequency'\r\n", + "INFO - Running joint_tour_frequency with 1028 multi-person households\r\n", + "DEBUG - @inject timetable\r\n", + "DEBUG - joint_tour_frequency: estimate.write_spec: output/estimation_data_bundle/joint_tour_frequency/joint_tour_frequency_SPEC.csv\r\n", + "DEBUG - joint_tour_frequency: write_table write: joint_tour_frequency_coefficients.csv\r\n", + "DEBUG - joint_tour_frequency: write_table cache: choosers\r\n", + "INFO - joint_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 1028 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1028 of 1028 choosers\r\n", + "DEBUG - joint_tour_frequency: write_table cache: expression_values\r\n", + "DEBUG - joint_tour_frequency: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using households.index\r\n", + "DEBUG - joint_tour_frequency: write_table cache: override_choices\r\n", + "DEBUG - joint_tour_frequency: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - joint_tour_frequency: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_frequency/joint_tour_frequency_values_combined.csv\r\n", + "INFO - joint_tour_frequency: end estimation\r\n", + "INFO - joint_tour_frequency top 10 value counts:\r\n", + "0_tours 1910\r\n", + "1_Main 26\r\n", + "1_Disc 20\r\n", + "1_Shop 16\r\n", + "1_Eat 15\r\n", + "1_Visit 12\r\n", + "2_SS 1\r\n", + "Name: joint_tour_frequency, dtype: int64\r\n", + "len(survey_tours) 91\r\n", + "len(joint_tours) 91\r\n", + "Time to execute step 'joint_tour_frequency': 0.68 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.68 s\r\n", + "INFO - #run_model running step joint_tour_composition\r\n", + "Running step 'joint_tour_composition'\r\n", + "INFO - Initialize Estimator for'joint_tour_composition'\r\n", + "INFO - Running joint_tour_composition with 91 joint tours\r\n", + "DEBUG - @inject timetable\r\n", + "DEBUG - joint_tour_composition: estimate.write_spec: output/estimation_data_bundle/joint_tour_composition/joint_tour_composition_SPEC.csv\r\n", + "DEBUG - joint_tour_composition: write_table write: joint_tour_composition_coefficients.csv\r\n", + "DEBUG - joint_tour_composition: write_table cache: choosers\r\n", + "INFO - joint_tour_composition.simple_simulate Running adaptive_chunked_choosers with 91 choosers\r\n", + "INFO - Running chunk 1 of 1 with 91 of 91 choosers\r\n", + "DEBUG - joint_tour_composition: write_table cache: expression_values\r\n", + "DEBUG - joint_tour_composition: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - joint_tour_composition: write_table cache: override_choices\r\n", + "DEBUG - joint_tour_composition: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - joint_tour_composition: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_composition/joint_tour_composition_values_combined.csv\r\n", + "INFO - joint_tour_composition: end estimation\r\n", + "INFO - joint_tour_composition top 10 value counts:\r\n", + "adults 54\r\n", + "mixed 32\r\n", + "children 5\r\n", + "Name: composition, dtype: int64\r\n", + "Time to execute step 'joint_tour_composition': 0.31 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.31 s\r\n", + "INFO - #run_model running step joint_tour_participation\r\n", + "Running step 'joint_tour_participation'\r\n", + "INFO - Running joint_tours_participation with 304 potential participants (candidates)\r\n", + "DEBUG - @inject timetable\r\n", + "INFO - Initialize Estimator for'joint_tour_participation'\r\n", + "DEBUG - joint_tour_participation: estimate.write_spec: output/estimation_data_bundle/joint_tour_participation/joint_tour_participation_SPEC.csv\r\n", + "DEBUG - joint_tour_participation: write_table write: joint_tour_participation_coefficients.csv\r\n", + "DEBUG - joint_tour_participation: write_table cache: choosers\r\n", + "INFO - joint_tour_participation Running chunk 1 of 1 with 90 of 90 choosers\r\n", + "DEBUG - joint_tour_participation: write_table cache: expression_values\r\n", + "INFO - joint_tour_participation.eval_mnl.participants_chooser 91 joint tours to satisfy.\r\n", + "INFO - joint_tour_participation.eval_mnl.participants_chooser 8 iterations to satisfy all joint tours.\r\n", + "DEBUG - joint_tour_participation: write_table cache: choices\r\n", + "DEBUG - joint_tour_participation: write_table cache: override_choices\r\n", + "DEBUG - joint_tour_participation: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - joint_tour_participation: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_participation/joint_tour_participation_values_combined.csv\r\n", + "INFO - joint_tour_participation: end estimation\r\n", + "Time to execute step 'joint_tour_participation': 0.99 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.99 s\r\n", + "INFO - #run_model running step joint_tour_destination\r\n", + "Running step 'joint_tour_destination'\r\n", + "INFO - Initialize Estimator for'joint_tour_destination'\r\n", + "DEBUG - joint_tour_destination: write_table write: non_mandatory_tour_destination_coefficients.csv\r\n", + "DEBUG - joint_tour_destination: estimate.write_spec: output/estimation_data_bundle/joint_tour_destination/joint_tour_destination_SPEC.csv\r\n", + "DEBUG - joint_tour_destination: write_table write: size_terms\r\n", + "DEBUG - joint_tour_destination: write_table write: landuse\r\n", + "INFO - running joint_tour_destination.shopping.sample with 18 tours\r\n", + "INFO - Estimation mode for joint_tour_destination.shopping.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - joint_tour_destination.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 18 choosers\r\n", + "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3420 rows\r\n", + "INFO - Estimation mode for joint_tour_destination.shopping.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running joint_tour_destination.shopping.logsums with 3420 rows\r\n", + "INFO - joint_tour_destination.shopping.logsums.compute_logsums Running adaptive_chunked_choosers with 3420 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3420 of 3420 choosers\r\n", + "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 18 persons\r\n", + "INFO - joint_tour_destination.shopping.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 18 choosers and 3420 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3420 rows\r\n", + "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.shopping.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running joint_tour_destination.othmaint.sample with 26 tours\r\n", + "INFO - Estimation mode for joint_tour_destination.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - joint_tour_destination.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 26 choosers\r\n", + "INFO - Running chunk 1 of 1 with 26 of 26 choosers\r\n", + "INFO - Running eval_interaction_utilities on 4940 rows\r\n", + "INFO - Estimation mode for joint_tour_destination.othmaint.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running joint_tour_destination.othmaint.logsums with 4940 rows\r\n", + "INFO - joint_tour_destination.othmaint.logsums.compute_logsums Running adaptive_chunked_choosers with 4940 choosers\r\n", + "INFO - Running chunk 1 of 1 with 4940 of 4940 choosers\r\n", + "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 26 persons\r\n", + "INFO - joint_tour_destination.othmaint.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 26 choosers and 4940 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 26 of 26 choosers\r\n", + "INFO - Running eval_interaction_utilities on 4940 rows\r\n", + "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.othmaint.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running joint_tour_destination.othdiscr.sample with 20 tours\r\n", + "INFO - Estimation mode for joint_tour_destination.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - joint_tour_destination.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 20 choosers\r\n", + "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3800 rows\r\n", + "INFO - Estimation mode for joint_tour_destination.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running joint_tour_destination.othdiscr.logsums with 3800 rows\r\n", + "INFO - joint_tour_destination.othdiscr.logsums.compute_logsums Running adaptive_chunked_choosers with 3800 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3800 of 3800 choosers\r\n", + "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 20 persons\r\n", + "INFO - joint_tour_destination.othdiscr.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 20 choosers and 3800 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3800 rows\r\n", + "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.othdiscr.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running joint_tour_destination.eatout.sample with 15 tours\r\n", + "INFO - Estimation mode for joint_tour_destination.eatout.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - joint_tour_destination.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 15 choosers\r\n", + "INFO - Running chunk 1 of 1 with 15 of 15 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2850 rows\r\n", + "INFO - Estimation mode for joint_tour_destination.eatout.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running joint_tour_destination.eatout.logsums with 2850 rows\r\n", + "INFO - joint_tour_destination.eatout.logsums.compute_logsums Running adaptive_chunked_choosers with 2850 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2850 of 2850 choosers\r\n", + "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 15 persons\r\n", + "INFO - joint_tour_destination.eatout.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 15 choosers and 2850 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 15 of 15 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2850 rows\r\n", + "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.eatout.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running joint_tour_destination.social.sample with 12 tours\r\n", + "INFO - Estimation mode for joint_tour_destination.social.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - joint_tour_destination.social.sample.interaction_sample Running adaptive_chunked_choosers with 12 choosers\r\n", + "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2280 rows\r\n", + "INFO - Estimation mode for joint_tour_destination.social.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running joint_tour_destination.social.logsums with 2280 rows\r\n", + "INFO - joint_tour_destination.social.logsums.compute_logsums Running adaptive_chunked_choosers with 2280 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2280 of 2280 choosers\r\n", + "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 12 persons\r\n", + "INFO - joint_tour_destination.social.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 12 choosers and 2280 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2280 rows\r\n", + "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.social.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - joint_tour_destination skipping segment escort: no choosers\r\n", + "DEBUG - joint_tour_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - joint_tour_destination: write_table cache: override_choices\r\n", + "DEBUG - joint_tour_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - joint_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_destination/joint_tour_destination_choosers_combined.csv\r\n", + "DEBUG - joint_tour_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "WARNING - joint_tour_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", + "DEBUG - joint_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_destination/joint_tour_destination_alternatives_combined.csv\r\n", + "INFO - joint_tour_destination: end estimation\r\n", + "INFO - destination summary:\r\n", + "count 91.000000\r\n", + "mean 101.439560\r\n", + "std 56.893899\r\n", + "min 4.000000\r\n", + "25% 45.000000\r\n", + "50% 114.000000\r\n", + "75% 149.000000\r\n", + "max 190.000000\r\n", + "Name: destination, dtype: float64\r\n", + "Time to execute step 'joint_tour_destination': 7.54 s\r\n", + "Total time to execute iteration 1 with iteration value None: 7.54 s\r\n", + "INFO - #run_model running step joint_tour_scheduling\r\n", + "Running step 'joint_tour_scheduling'\r\n", + "INFO - Running joint_tour_scheduling with 91 joint tours\r\n", + "DEBUG - @inject timetable\r\n", + "INFO - Initialize Estimator for'joint_tour_scheduling'\r\n", + "DEBUG - joint_tour_scheduling: estimate.write_spec: output/estimation_data_bundle/joint_tour_scheduling/joint_tour_scheduling_SPEC.csv\r\n", + "DEBUG - joint_tour_scheduling: write_table write: tour_scheduling_joint_coefficients.csv\r\n", + "INFO - joint_tour_scheduling: timetable.begin_transaction person_windows\r\n", + "INFO - schedule_tours %s tours not monotonic_increasing - sorting df\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1 schedule_tours running 90 tour choices\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1 Running adaptive_chunked_choosers with 90 choosers\r\n", + "INFO - Running chunk 1 of 1 with 90 of 90 choosers\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1 schedule_tours running 90 tour choices\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: choosers\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 90 choosers and 6458 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 90 of 90 choosers\r\n", + "INFO - Running eval_interaction_utilities on 6458 rows\r\n", + "INFO - joint_tour_scheduling: eval_interaction_utilities write_interaction_expression_values joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: interaction_expression_values\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2 schedule_tours running 1 tour choices\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2 Running adaptive_chunked_choosers with 1 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1 of 1 choosers\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2 schedule_tours running 1 tour choices\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: choosers\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 1 choosers and 73 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 1 of 1 choosers\r\n", + "INFO - Running eval_interaction_utilities on 73 rows\r\n", + "INFO - joint_tour_scheduling: eval_interaction_utilities write_interaction_expression_values joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: interaction_expression_values\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - joint_tour_scheduling: write_table cache: override_choices\r\n", + "DEBUG - joint_tour_scheduling: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - joint_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_scheduling/joint_tour_scheduling_choosers_combined.csv\r\n", + "DEBUG - joint_tour_scheduling: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "DEBUG - joint_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_scheduling/joint_tour_scheduling_alternatives_combined.csv\r\n", + "INFO - joint_tour_scheduling: end estimation\r\n", + "INFO - joint_tour_scheduling: timetable.rollback person_windows\r\n", + "INFO - joint_tour_scheduling: assign timetable for 224 participants in 90 tours with tour_num 1\r\n", + "INFO - joint_tour_scheduling: assign timetable for 4 participants in 1 tours with tour_num 2\r\n", + "Time to execute step 'joint_tour_scheduling': 1.89 s\r\n", + "Total time to execute iteration 1 with iteration value None: 1.89 s\r\n", + "INFO - #run_model running step non_mandatory_tour_frequency\r\n", + "Running step 'non_mandatory_tour_frequency'\r\n", + "DEBUG - @inject timetable\r\n", + "INFO - Running non_mandatory_tour_frequency with 3833 persons\r\n", + "INFO - Running segment 'PTYPE_FULL' of size 1764\r\n", + "INFO - Initialize Estimator for'PTYPE_FULL'\r\n", + "DEBUG - PTYPE_FULL: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_FULL: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_FULL.csv\r\n", + "DEBUG - PTYPE_FULL: write_table cache: choosers\r\n", + "DEBUG - PTYPE_FULL: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_FULL.interaction_simulate Running adaptive_chunked_choosers with 1764 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1764 of 1764 choosers\r\n", + "WARNING - /mnt/c/Users/jan.zill/code/activitysim/activitysim/core/logit.py:358: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", + " alts_sample[c_chooser] = np.repeat(choosers[c].values, sample_size)\r\n", + "\r\n", + "INFO - Running eval_interaction_utilities on 169344 rows\r\n", + "WARNING - /mnt/c/Users/jan.zill/code/activitysim/activitysim/core/interaction_simulate.py:482: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", + " choices = _interaction_simulate(chooser_chunk, alternatives, spec,\r\n", + "\r\n", + "INFO - PTYPE_FULL: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_FULL.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_FULL: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_FULL: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_FULL: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_FULL: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_FULL: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_FULL/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_FULL: end estimation\r\n", + "INFO - Running segment 'PTYPE_PART' of size 449\r\n", + "INFO - Initialize Estimator for'PTYPE_PART'\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_PART: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_PART: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_PART.csv\r\n", + "DEBUG - PTYPE_PART: write_table cache: choosers\r\n", + "DEBUG - PTYPE_PART: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_PART.interaction_simulate Running adaptive_chunked_choosers with 449 choosers\r\n", + "INFO - Running chunk 1 of 1 with 449 of 449 choosers\r\n", + "INFO - Running eval_interaction_utilities on 43104 rows\r\n", + "INFO - PTYPE_PART: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_PART.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_PART: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_PART: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_PART: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_PART: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_PART: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_PART/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_PART: end estimation\r\n", + "INFO - Running segment 'PTYPE_UNIVERSITY' of size 296\r\n", + "INFO - Initialize Estimator for'PTYPE_UNIVERSITY'\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_UNIVERSITY: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_UNIVERSITY.csv\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_table cache: choosers\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_UNIVERSITY.interaction_simulate Running adaptive_chunked_choosers with 296 choosers\r\n", + "INFO - Running chunk 1 of 1 with 296 of 296 choosers\r\n", + "INFO - Running eval_interaction_utilities on 28416 rows\r\n", + "INFO - PTYPE_UNIVERSITY: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_UNIVERSITY.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_UNIVERSITY: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_UNIVERSITY/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_UNIVERSITY: end estimation\r\n", + "INFO - Running segment 'PTYPE_NONWORK' of size 420\r\n", + "INFO - Initialize Estimator for'PTYPE_NONWORK'\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_NONWORK: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_NONWORK: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_NONWORK.csv\r\n", + "DEBUG - PTYPE_NONWORK: write_table cache: choosers\r\n", + "DEBUG - PTYPE_NONWORK: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_NONWORK.interaction_simulate Running adaptive_chunked_choosers with 420 choosers\r\n", + "INFO - Running chunk 1 of 1 with 420 of 420 choosers\r\n", + "INFO - Running eval_interaction_utilities on 40320 rows\r\n", + "INFO - PTYPE_NONWORK: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_NONWORK.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_NONWORK: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_NONWORK: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_NONWORK: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_NONWORK: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_NONWORK: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_NONWORK/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_NONWORK: end estimation\r\n", + "INFO - Running segment 'PTYPE_RETIRED' of size 357\r\n", + "INFO - Initialize Estimator for'PTYPE_RETIRED'\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_RETIRED: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_RETIRED: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_RETIRED.csv\r\n", + "DEBUG - PTYPE_RETIRED: write_table cache: choosers\r\n", + "DEBUG - PTYPE_RETIRED: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_RETIRED.interaction_simulate Running adaptive_chunked_choosers with 357 choosers\r\n", + "INFO - Running chunk 1 of 1 with 357 of 357 choosers\r\n", + "INFO - Running eval_interaction_utilities on 34272 rows\r\n", + "INFO - PTYPE_RETIRED: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_RETIRED.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_RETIRED: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_RETIRED: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_RETIRED: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_RETIRED: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_RETIRED: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_RETIRED/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_RETIRED: end estimation\r\n", + "INFO - Running segment 'PTYPE_DRIVING' of size 50\r\n", + "INFO - Initialize Estimator for'PTYPE_DRIVING'\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_DRIVING: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_DRIVING: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_DRIVING.csv\r\n", + "DEBUG - PTYPE_DRIVING: write_table cache: choosers\r\n", + "DEBUG - PTYPE_DRIVING: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_DRIVING.interaction_simulate Running adaptive_chunked_choosers with 50 choosers\r\n", + "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", + "INFO - Running eval_interaction_utilities on 4800 rows\r\n", + "INFO - PTYPE_DRIVING: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_DRIVING.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_DRIVING: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_DRIVING: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_DRIVING: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_DRIVING: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_DRIVING: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_DRIVING/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_DRIVING: end estimation\r\n", + "INFO - Running segment 'PTYPE_SCHOOL' of size 313\r\n", + "INFO - Initialize Estimator for'PTYPE_SCHOOL'\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_SCHOOL: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_SCHOOL: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_SCHOOL.csv\r\n", + "DEBUG - PTYPE_SCHOOL: write_table cache: choosers\r\n", + "DEBUG - PTYPE_SCHOOL: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_SCHOOL.interaction_simulate Running adaptive_chunked_choosers with 313 choosers\r\n", + "INFO - Running chunk 1 of 1 with 313 of 313 choosers\r\n", + "INFO - Running eval_interaction_utilities on 30048 rows\r\n", + "INFO - PTYPE_SCHOOL: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_SCHOOL.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_SCHOOL: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_SCHOOL: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_SCHOOL: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_SCHOOL: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_SCHOOL: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_SCHOOL/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_SCHOOL: end estimation\r\n", + "INFO - Running segment 'PTYPE_PRESCHOOL' of size 184\r\n", + "INFO - Initialize Estimator for'PTYPE_PRESCHOOL'\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", + "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_PRESCHOOL: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_PRESCHOOL.csv\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_table cache: choosers\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_table write: alternatives\r\n", + "INFO - non_mandatory_tour_frequency.PTYPE_PRESCHOOL.interaction_simulate Running adaptive_chunked_choosers with 184 choosers\r\n", + "INFO - Running chunk 1 of 1 with 184 of 184 choosers\r\n", + "INFO - Running eval_interaction_utilities on 17664 rows\r\n", + "INFO - PTYPE_PRESCHOOL: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_PRESCHOOL.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_table write: interaction_expression_values\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_table cache: override_choices\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - PTYPE_PRESCHOOL: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_PRESCHOOL/non_mandatory_tour_frequency_choosers_combined.csv\r\n", + "INFO - PTYPE_PRESCHOOL: end estimation\r\n", + "INFO - extend_tour_counts increased tour count by 166 from 2324 to 2490\r\n", + "DEBUG - get_survey_values: reindexing using persons.index\r\n", + "INFO - estimation get_survey_values override_tour_counts 0 changed cells\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "INFO - non_mandatory_tour_frequency top 10 value counts:\r\n", + "0 2718\r\n", + "16 374\r\n", + "1 282\r\n", + "8 174\r\n", + "4 154\r\n", + "32 132\r\n", + "2 84\r\n", + "17 61\r\n", + "24 50\r\n", + "9 32\r\n", + "Name: non_mandatory_tour_frequency, dtype: int64\r\n", + "Time to execute step 'non_mandatory_tour_frequency': 188.21 s\r\n", + "Total time to execute iteration 1 with iteration value None: 188.21 s\r\n", + "INFO - #run_model running step non_mandatory_tour_destination\r\n", + "Running step 'non_mandatory_tour_destination'\r\n", + "INFO - Initialize Estimator for'non_mandatory_tour_destination'\r\n", + "DEBUG - non_mandatory_tour_destination: write_table write: non_mandatory_tour_destination_coefficients.csv\r\n", + "DEBUG - non_mandatory_tour_destination: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_destination/non_mandatory_tour_destination_SPEC.csv\r\n", + "DEBUG - non_mandatory_tour_destination: write_table write: size_terms\r\n", + "DEBUG - non_mandatory_tour_destination: write_table write: landuse\r\n", + "INFO - running non_mandatory_tour_destination.shopping.sample with 705 tours\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.shopping.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - non_mandatory_tour_destination.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 705 choosers\r\n", + "INFO - Running chunk 1 of 1 with 705 of 705 choosers\r\n", + "INFO - Running eval_interaction_utilities on 133950 rows\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.shopping.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running non_mandatory_tour_destination.shopping.logsums with 133950 rows\r\n", + "INFO - non_mandatory_tour_destination.shopping.logsums.compute_logsums Running adaptive_chunked_choosers with 133950 choosers\r\n", + "INFO - Running chunk 1 of 1 with 133950 of 133950 choosers\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 705 persons\r\n", + "INFO - non_mandatory_tour_destination.shopping.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 705 choosers and 133950 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 705 of 705 choosers\r\n", + "INFO - Running eval_interaction_utilities on 133950 rows\r\n", + "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.shopping.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running non_mandatory_tour_destination.othmaint.sample with 367 tours\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - non_mandatory_tour_destination.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 367 choosers\r\n", + "INFO - Running chunk 1 of 1 with 367 of 367 choosers\r\n", + "INFO - Running eval_interaction_utilities on 69730 rows\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.othmaint.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running non_mandatory_tour_destination.othmaint.logsums with 69730 rows\r\n", + "INFO - non_mandatory_tour_destination.othmaint.logsums.compute_logsums Running adaptive_chunked_choosers with 69730 choosers\r\n", + "INFO - Running chunk 1 of 1 with 69730 of 69730 choosers\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 367 persons\r\n", + "INFO - non_mandatory_tour_destination.othmaint.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 367 choosers and 69730 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 367 of 367 choosers\r\n", + "INFO - Running eval_interaction_utilities on 69730 rows\r\n", + "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.othmaint.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running non_mandatory_tour_destination.othdiscr.sample with 544 tours\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - non_mandatory_tour_destination.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 544 choosers\r\n", + "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", + "INFO - Running eval_interaction_utilities on 103360 rows\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running non_mandatory_tour_destination.othdiscr.logsums with 103360 rows\r\n", + "INFO - non_mandatory_tour_destination.othdiscr.logsums.compute_logsums Running adaptive_chunked_choosers with 103360 choosers\r\n", + "INFO - Running chunk 1 of 1 with 103360 of 103360 choosers\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 544 persons\r\n", + "INFO - non_mandatory_tour_destination.othdiscr.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 544 choosers and 103360 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", + "INFO - Running eval_interaction_utilities on 103360 rows\r\n", + "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.othdiscr.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running non_mandatory_tour_destination.eatout.sample with 301 tours\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.eatout.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - non_mandatory_tour_destination.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 301 choosers\r\n", + "INFO - Running chunk 1 of 1 with 301 of 301 choosers\r\n", + "INFO - Running eval_interaction_utilities on 57190 rows\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.eatout.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running non_mandatory_tour_destination.eatout.logsums with 57190 rows\r\n", + "INFO - non_mandatory_tour_destination.eatout.logsums.compute_logsums Running adaptive_chunked_choosers with 57190 choosers\r\n", + "INFO - Running chunk 1 of 1 with 57190 of 57190 choosers\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 301 persons\r\n", + "INFO - non_mandatory_tour_destination.eatout.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 301 choosers and 57190 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 301 of 301 choosers\r\n", + "INFO - Running eval_interaction_utilities on 57190 rows\r\n", + "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.eatout.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running non_mandatory_tour_destination.social.sample with 170 tours\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.social.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - non_mandatory_tour_destination.social.sample.interaction_sample Running adaptive_chunked_choosers with 170 choosers\r\n", + "INFO - Running chunk 1 of 1 with 170 of 170 choosers\r\n", + "INFO - Running eval_interaction_utilities on 32300 rows\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.social.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running non_mandatory_tour_destination.social.logsums with 32300 rows\r\n", + "INFO - non_mandatory_tour_destination.social.logsums.compute_logsums Running adaptive_chunked_choosers with 32300 choosers\r\n", + "INFO - Running chunk 1 of 1 with 32300 of 32300 choosers\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 170 persons\r\n", + "INFO - non_mandatory_tour_destination.social.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 170 choosers and 32300 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 170 of 170 choosers\r\n", + "INFO - Running eval_interaction_utilities on 32300 rows\r\n", + "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.social.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", + "INFO - running non_mandatory_tour_destination.escort.sample with 403 tours\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.escort.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - non_mandatory_tour_destination.escort.sample.interaction_sample Running adaptive_chunked_choosers with 403 choosers\r\n", + "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", + "INFO - Running eval_interaction_utilities on 76570 rows\r\n", + "INFO - Estimation mode for non_mandatory_tour_destination.escort.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running non_mandatory_tour_destination.escort.logsums with 76570 rows\r\n", + "INFO - non_mandatory_tour_destination.escort.logsums.compute_logsums Running adaptive_chunked_choosers with 76570 choosers\r\n", + "INFO - Running chunk 1 of 1 with 76570 of 76570 choosers\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 403 persons\r\n", + "INFO - non_mandatory_tour_destination.escort.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 403 choosers and 76570 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", + "INFO - Running eval_interaction_utilities on 76570 rows\r\n", + "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.escort.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - non_mandatory_tour_destination: write_table cache: override_choices\r\n", + "DEBUG - non_mandatory_tour_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - non_mandatory_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_destination/non_mandatory_tour_destination_choosers_combined.csv\r\n", + "DEBUG - non_mandatory_tour_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "WARNING - non_mandatory_tour_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", + "DEBUG - non_mandatory_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_destination/non_mandatory_tour_destination_alternatives_combined.csv\r\n", + "INFO - non_mandatory_tour_destination: end estimation\r\n", + "Time to execute step 'non_mandatory_tour_destination': 38.82 s\r\n", + "Total time to execute iteration 1 with iteration value None: 38.82 s\r\n", + "INFO - #run_model running step non_mandatory_tour_scheduling\r\n", + "Running step 'non_mandatory_tour_scheduling'\r\n", + "DEBUG - @inject timetable\r\n", + "INFO - Initialize Estimator for'non_mandatory_tour_scheduling'\r\n", + "DEBUG - non_mandatory_tour_scheduling: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_scheduling/non_mandatory_tour_scheduling_SPEC.csv\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table write: tour_scheduling_nonmandatory_coefficients.csv\r\n", + "INFO - non_mandatory_tour_scheduling: timetable.begin_transaction person_windows\r\n", + "INFO - Running non_mandatory_tour_scheduling with 2490 tours\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1 schedule_tours running 1687 tour choices\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1 Running adaptive_chunked_choosers with 1687 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1687 of 1687 choosers\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1 schedule_tours running 1687 tour choices\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 1687 choosers and 241779 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 1687 of 1687 choosers\r\n", + "INFO - Running eval_interaction_utilities on 241779 rows\r\n", + "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2 schedule_tours running 571 tour choices\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2 Running adaptive_chunked_choosers with 571 choosers\r\n", + "INFO - Running chunk 1 of 1 with 571 of 571 choosers\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2 schedule_tours running 571 tour choices\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 571 choosers and 47267 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 571 of 571 choosers\r\n", + "INFO - Running eval_interaction_utilities on 47267 rows\r\n", + "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3 schedule_tours running 179 tour choices\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3 Running adaptive_chunked_choosers with 179 choosers\r\n", + "INFO - Running chunk 1 of 1 with 179 of 179 choosers\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3 schedule_tours running 179 tour choices\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 179 choosers and 12032 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 179 of 179 choosers\r\n", + "INFO - Running eval_interaction_utilities on 12032 rows\r\n", + "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4 schedule_tours running 50 tour choices\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4 Running adaptive_chunked_choosers with 50 choosers\r\n", + "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4 schedule_tours running 50 tour choices\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 50 choosers and 2697 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2697 rows\r\n", + "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5 schedule_tours running 3 tour choices\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5 Running adaptive_chunked_choosers with 3 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5 schedule_tours running 3 tour choices\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 3 choosers and 146 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", + "INFO - Running eval_interaction_utilities on 146 rows\r\n", + "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_table cache: override_choices\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_scheduling/non_mandatory_tour_scheduling_choosers_combined.csv\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "DEBUG - non_mandatory_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_scheduling/non_mandatory_tour_scheduling_alternatives_combined.csv\r\n", + "INFO - non_mandatory_tour_scheduling: end estimation\r\n", + "INFO - non_mandatory_tour_scheduling: timetable.rollback person_windows\r\n", + "Time to execute step 'non_mandatory_tour_scheduling': 75.98 s\r\n", + "Total time to execute iteration 1 with iteration value None: 75.98 s\r\n", + "INFO - #run_model running step tour_mode_choice_simulate\r\n", + "Running step 'tour_mode_choice_simulate'\r\n", + "INFO - Running tour_mode_choice with 5314 tours\r\n", + "INFO - tour_types top 10 value counts:\r\n", + "work 2124\r\n", + "shopping 723\r\n", + "school 609\r\n", + "othdiscr 564\r\n", + "escort 403\r\n", + "othmaint 393\r\n", + "eatout 316\r\n", + "social 182\r\n", + "Name: tour_type, dtype: int64\r\n", + "INFO - Initialize Estimator for'tour_mode_choice'\r\n", + "DEBUG - tour_mode_choice: write_table write: tour_mode_choice_coefficients.csv\r\n", + "DEBUG - tour_mode_choice: write_table write: coefficients_template\r\n", + "DEBUG - tour_mode_choice: estimate.write_spec: output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_SPEC.csv\r\n", + "INFO - tour_mode_choice_simulate tour_type 'eatout' (316 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.eatout.simple_simulate Running adaptive_chunked_choosers with 316 choosers\r\n", + "INFO - Running chunk 1 of 1 with 316 of 316 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate eatout choices_df top 10 value counts:\r\n", + "DRIVEALONEFREE 77\r\n", + "WALK 73\r\n", + "SHARED3FREE 53\r\n", + "SHARED2FREE 53\r\n", + "WALK_LOC 24\r\n", + "WALK_LRF 13\r\n", + "WALK_HVY 7\r\n", + "BIKE 6\r\n", + "TNC_SINGLE 5\r\n", + "DRIVE_LOC 3\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'escort' (403 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.escort.simple_simulate Running adaptive_chunked_choosers with 403 choosers\r\n", + "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate escort choices_df top 10 value counts:\r\n", + "SHARED2FREE 175\r\n", + "SHARED3FREE 151\r\n", + "TNC_SINGLE 45\r\n", + "TNC_SHARED 15\r\n", + "TAXI 6\r\n", + "BIKE 4\r\n", + "WALK 4\r\n", + "DRIVE_HVY 1\r\n", + "WALK_HVY 1\r\n", + "WALK_LRF 1\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'othdiscr' (564 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.othdiscr.simple_simulate Running adaptive_chunked_choosers with 564 choosers\r\n", + "INFO - Running chunk 1 of 1 with 564 of 564 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate othdiscr choices_df top 10 value counts:\r\n", + "DRIVEALONEFREE 151\r\n", + "SHARED3FREE 104\r\n", + "SHARED2FREE 83\r\n", + "WALK 73\r\n", + "WALK_LOC 65\r\n", + "WALK_LRF 31\r\n", + "WALK_HVY 19\r\n", + "BIKE 19\r\n", + "TNC_SINGLE 12\r\n", + "TAXI 2\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'othmaint' (393 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.othmaint.simple_simulate Running adaptive_chunked_choosers with 393 choosers\r\n", + "INFO - Running chunk 1 of 1 with 393 of 393 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate othmaint choices_df top 10 value counts:\r\n", + "DRIVEALONEFREE 141\r\n", + "SHARED2FREE 62\r\n", + "WALK_LOC 36\r\n", + "SHARED3FREE 35\r\n", + "BIKE 28\r\n", + "WALK 23\r\n", + "WALK_LRF 23\r\n", + "TNC_SINGLE 21\r\n", + "TNC_SHARED 11\r\n", + "WALK_HVY 9\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'school' (460 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.school.simple_simulate Running adaptive_chunked_choosers with 460 choosers\r\n", + "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate school choices_df top 10 value counts:\r\n", + "SHARED3FREE 126\r\n", + "WALK_LRF 101\r\n", + "WALK_LOC 92\r\n", + "SHARED2FREE 59\r\n", + "WALK 48\r\n", + "WALK_HVY 23\r\n", + "BIKE 7\r\n", + "DRIVEALONEFREE 2\r\n", + "TNC_SHARED 1\r\n", + "TAXI 1\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'shopping' (723 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.shopping.simple_simulate Running adaptive_chunked_choosers with 723 choosers\r\n", + "INFO - Running chunk 1 of 1 with 723 of 723 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate shopping choices_df top 10 value counts:\r\n", + "DRIVEALONEFREE 270\r\n", + "SHARED2FREE 107\r\n", + "WALK 82\r\n", + "SHARED3FREE 75\r\n", + "WALK_LOC 46\r\n", + "TNC_SINGLE 44\r\n", + "WALK_LRF 35\r\n", + "BIKE 27\r\n", + "TNC_SHARED 17\r\n", + "WALK_HVY 9\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'social' (182 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.social.simple_simulate Running adaptive_chunked_choosers with 182 choosers\r\n", + "INFO - Running chunk 1 of 1 with 182 of 182 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate social choices_df top 10 value counts:\r\n", + "SHARED2FREE 43\r\n", + "DRIVEALONEFREE 29\r\n", + "SHARED3FREE 28\r\n", + "WALK 23\r\n", + "WALK_LRF 17\r\n", + "WALK_LOC 16\r\n", + "BIKE 15\r\n", + "TNC_SINGLE 8\r\n", + "WALK_HVY 2\r\n", + "TNC_SHARED 1\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'univ' (149 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.univ.simple_simulate Running adaptive_chunked_choosers with 149 choosers\r\n", + "INFO - Running chunk 1 of 1 with 149 of 149 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate univ choices_df top 10 value counts:\r\n", + "DRIVEALONEFREE 40\r\n", + "WALK_LRF 38\r\n", + "WALK_LOC 38\r\n", + "WALK_HVY 9\r\n", + "SHARED3FREE 7\r\n", + "TNC_SHARED 6\r\n", + "TAXI 4\r\n", + "SHARED2FREE 3\r\n", + "TNC_SINGLE 2\r\n", + "BIKE 1\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - tour_mode_choice_simulate tour_type 'work' (2124 tours)\r\n", + "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", + "INFO - tour_mode_choice.work.simple_simulate Running adaptive_chunked_choosers with 2124 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2124 of 2124 choosers\r\n", + "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", + "INFO - tour_mode_choice_simulate work choices_df top 10 value counts:\r\n", + "DRIVEALONEFREE 665\r\n", + "WALK_LOC 324\r\n", + "WALK_LRF 254\r\n", + "SHARED2FREE 216\r\n", + "TNC_SINGLE 177\r\n", + "SHARED3FREE 138\r\n", + "WALK 137\r\n", + "BIKE 109\r\n", + "WALK_HVY 82\r\n", + "TAXI 10\r\n", + "Name: tour_mode, dtype: int64\r\n", + "DEBUG - tour_mode_choice: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - tour_mode_choice: write_table cache: override_choices\r\n", + "DEBUG - tour_mode_choice: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - tour_mode_choice: write_omnibus_choosers: output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_values_combined.csv\r\n", + "INFO - tour_mode_choice: end estimation\r\n", + "INFO - tour_mode_choice_simulate all tour type choices top 10 value counts:\r\n", + "DRIVEALONEFREE 1357\r\n", + "SHARED2FREE 801\r\n", + "SHARED3FREE 701\r\n", + "WALK_LOC 601\r\n", + "WALK_LRF 593\r\n", + "WALK 456\r\n", + "TNC_SINGLE 312\r\n", + "BIKE 200\r\n", + "WALK_HVY 194\r\n", + "TNC_SHARED 53\r\n", + "Name: tour_mode, dtype: int64\r\n", + "Time to execute step 'tour_mode_choice_simulate': 16.74 s\r\n", + "Total time to execute iteration 1 with iteration value None: 16.74 s\r\n", + "INFO - #run_model running step atwork_subtour_frequency\r\n", + "Running step 'atwork_subtour_frequency'\r\n", + "INFO - Initialize Estimator for'atwork_subtour_frequency'\r\n", + "INFO - Running atwork_subtour_frequency with 2124 work tours\r\n", + "DEBUG - atwork_subtour_frequency: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_frequency/atwork_subtour_frequency_SPEC.csv\r\n", + "DEBUG - atwork_subtour_frequency: write_table write: atwork_subtour_frequency_coefficients.csv\r\n", + "DEBUG - atwork_subtour_frequency: write_table cache: choosers\r\n", + "INFO - atwork_subtour_frequency.simple_simulate Running adaptive_chunked_choosers with 2124 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2124 of 2124 choosers\r\n", + "DEBUG - atwork_subtour_frequency: write_table cache: expression_values\r\n", + "DEBUG - atwork_subtour_frequency: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - atwork_subtour_frequency: write_table cache: override_choices\r\n", + "DEBUG - atwork_subtour_frequency: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - atwork_subtour_frequency: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_frequency/atwork_subtour_frequency_values_combined.csv\r\n", + "INFO - atwork_subtour_frequency: end estimation\r\n", + "INFO - atwork_subtour_frequency top 10 value counts:\r\n", + " 3654\r\n", + "no_subtours 1664\r\n", + "eat 346\r\n", + "maint 62\r\n", + "business1 48\r\n", + "eat_business 3\r\n", + "business2 1\r\n", + "Name: atwork_subtour_frequency, dtype: int64\r\n", + "Time to execute step 'atwork_subtour_frequency': 0.75 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.75 s\r\n", + "INFO - #run_model running step atwork_subtour_destination\r\n", + "Running step 'atwork_subtour_destination'\r\n", + "INFO - Initialize Estimator for'atwork_subtour_destination'\r\n", + "DEBUG - atwork_subtour_destination: write_table write: atwork_subtour_destination_coefficients.csv\r\n", + "DEBUG - atwork_subtour_destination: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_destination/atwork_subtour_destination_SPEC.csv\r\n", + "DEBUG - atwork_subtour_destination: write_table write: size_terms\r\n", + "DEBUG - atwork_subtour_destination: write_table write: landuse\r\n", + "INFO - running atwork_subtour_destination.atwork.sample with 464 tours\r\n", + "INFO - Estimation mode for atwork_subtour_destination.atwork.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - atwork_subtour_destination.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 464 choosers\r\n", + "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", + "INFO - Running eval_interaction_utilities on 88160 rows\r\n", + "INFO - Estimation mode for atwork_subtour_destination.atwork.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Running atwork_subtour_destination.atwork.logsums with 88160 rows\r\n", + "INFO - atwork_subtour_destination.atwork.logsums.compute_logsums Running adaptive_chunked_choosers with 88160 choosers\r\n", + "INFO - Running chunk 1 of 1 with 88160 of 88160 choosers\r\n", + "DEBUG - atwork_subtour_destination: write_table cache: choosers\r\n", + "INFO - Running tour_destination_simulate with 464 persons\r\n", + "INFO - atwork_subtour_destination.atwork.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 464 choosers and 88160 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", + "INFO - Running eval_interaction_utilities on 88160 rows\r\n", + "INFO - atwork_subtour_destination: eval_interaction_utilities write_interaction_expression_values atwork_subtour_destination.atwork.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - atwork_subtour_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - atwork_subtour_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - atwork_subtour_destination: write_table cache: override_choices\r\n", + "DEBUG - atwork_subtour_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - atwork_subtour_destination: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_destination/atwork_subtour_destination_choosers_combined.csv\r\n", + "DEBUG - atwork_subtour_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "WARNING - atwork_subtour_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", + "DEBUG - atwork_subtour_destination: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_destination/atwork_subtour_destination_alternatives_combined.csv\r\n", + "INFO - atwork_subtour_destination: end estimation\r\n", + "INFO - destination summary:\r\n", + "count 464.000000\r\n", + "mean 92.616379\r\n", + "std 54.698856\r\n", + "min 1.000000\r\n", + "25% 44.750000\r\n", + "50% 90.000000\r\n", + "75% 139.250000\r\n", + "max 190.000000\r\n", + "Name: destination, dtype: float64\r\n", + "Time to execute step 'atwork_subtour_destination': 9.38 s\r\n", + "Total time to execute iteration 1 with iteration value None: 9.38 s\r\n", + "INFO - #run_model running step atwork_subtour_scheduling\r\n", + "Running step 'atwork_subtour_scheduling'\r\n", + "INFO - Initialize Estimator for'atwork_subtour_scheduling'\r\n", + "INFO - Running atwork_subtour_scheduling with 464 tours\r\n", + "DEBUG - atwork_subtour_scheduling: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_scheduling/atwork_subtour_scheduling_SPEC.csv\r\n", + "DEBUG - atwork_subtour_scheduling: write_table write: tour_scheduling_atwork_coefficients.csv\r\n", + "INFO - atwork_subtour_scheduling.tour_1 schedule_tours running 460 tour choices\r\n", + "INFO - atwork_subtour_scheduling.tour_1 Running adaptive_chunked_choosers with 460 choosers\r\n", + "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", + "INFO - atwork_subtour_scheduling.tour_1 schedule_tours running 460 tour choices\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: choosers\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - atwork_subtour_scheduling.tour_1.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 460 choosers and 33700 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", + "INFO - Running eval_interaction_utilities on 33700 rows\r\n", + "INFO - atwork_subtour_scheduling: eval_interaction_utilities write_interaction_expression_values atwork_subtour_scheduling.tour_1.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_expression_values\r\n", + "INFO - atwork_subtour_scheduling.tour_2 schedule_tours running 4 tour choices\r\n", + "INFO - atwork_subtour_scheduling.tour_2 Running adaptive_chunked_choosers with 4 choosers\r\n", + "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", + "INFO - atwork_subtour_scheduling.tour_2 schedule_tours running 4 tour choices\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: choosers\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_sample_alternatives\r\n", + "INFO - atwork_subtour_scheduling.tour_2.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 4 choosers and 223 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", + "INFO - Running eval_interaction_utilities on 223 rows\r\n", + "INFO - atwork_subtour_scheduling: eval_interaction_utilities write_interaction_expression_values atwork_subtour_scheduling.tour_2.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_expression_values\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - atwork_subtour_scheduling: write_table cache: override_choices\r\n", + "DEBUG - atwork_subtour_scheduling: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - atwork_subtour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_scheduling/atwork_subtour_scheduling_choosers_combined.csv\r\n", + "DEBUG - atwork_subtour_scheduling: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "DEBUG - atwork_subtour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_scheduling/atwork_subtour_scheduling_alternatives_combined.csv\r\n", + "INFO - atwork_subtour_scheduling: end estimation\r\n", + "Time to execute step 'atwork_subtour_scheduling': 5.79 s\r\n", + "Total time to execute iteration 1 with iteration value None: 5.79 s\r\n", + "INFO - #run_model running step atwork_subtour_mode_choice\r\n", + "Running step 'atwork_subtour_mode_choice'\r\n", + "INFO - Running atwork_subtour_mode_choice with 464 subtours\r\n", + "INFO - atwork_subtour_mode_choice tour_type top 10 value counts:\r\n", + "eat 349\r\n", + "maint 62\r\n", + "business 53\r\n", + "Name: tour_type, dtype: int64\r\n", + "INFO - Initialize Estimator for'atwork_subtour_mode_choice'\r\n", + "DEBUG - atwork_subtour_mode_choice: write_table write: tour_mode_choice_coefficients.csv\r\n", + "DEBUG - atwork_subtour_mode_choice: write_table write: coefficients_template\r\n", + "DEBUG - atwork_subtour_mode_choice: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_mode_choice/atwork_subtour_mode_choice_SPEC.csv\r\n", + "WARNING - /mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/pandas/core/frame.py:3636: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", + " self[k1] = value[k2]\r\n", + "\r\n", + "WARNING - /mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/util/mode.py:129: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", + " choosers[trace_column_names] = choosers.index\r\n", + "\r\n", + "DEBUG - atwork_subtour_mode_choice: write_table cache: choosers\r\n", + "INFO - atwork_subtour_mode_choice.simple_simulate Running adaptive_chunked_choosers with 464 choosers\r\n", + "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", + "DEBUG - atwork_subtour_mode_choice: write_table cache: expression_values\r\n", + "DEBUG - atwork_subtour_mode_choice: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - atwork_subtour_mode_choice: write_table cache: override_choices\r\n", + "DEBUG - atwork_subtour_mode_choice: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - atwork_subtour_mode_choice: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_mode_choice/atwork_subtour_mode_choice_values_combined.csv\r\n", + "INFO - atwork_subtour_mode_choice: end estimation\r\n", + "INFO - atwork_subtour_mode_choice choices top 10 value counts:\r\n", + "DRIVEALONEFREE 133\r\n", + "WALK 99\r\n", + "SHARED2FREE 95\r\n", + "SHARED3FREE 67\r\n", + "TNC_SINGLE 27\r\n", + "WALK_LOC 13\r\n", + "TNC_SHARED 11\r\n", + "BIKE 10\r\n", + "WALK_LRF 5\r\n", + "TAXI 2\r\n", + "Name: tour_mode, dtype: int64\r\n", + "Time to execute step 'atwork_subtour_mode_choice': 2.23 s\r\n", + "Total time to execute iteration 1 with iteration value None: 2.23 s\r\n", + "INFO - #run_model running step stop_frequency\r\n", + "Running step 'stop_frequency'\r\n", + "INFO - stop_frequency segments top 10 value counts:\r\n", + "work 2124\r\n", + "shopping 723\r\n", + "othdiscr 564\r\n", + "atwork 464\r\n", + "school 460\r\n", + "escort 403\r\n", + "othmaint 393\r\n", + "eatout 316\r\n", + "social 182\r\n", + "univ 149\r\n", + "Name: primary_purpose, dtype: int64\r\n", + "INFO - stop_frequency running segment work with 2124 chooser rows\r\n", + "INFO - Initialize Estimator for'work'\r\n", + "DEBUG - work: estimate.write_spec: output/estimation_data_bundle/stop_frequency/work/stop_frequency_SPEC.csv\r\n", + "DEBUG - work: write_table write: stop_frequency_coefficients_work.csv\r\n", + "DEBUG - work: write_table cache: choosers\r\n", + "INFO - stop_frequency.work.simple_simulate Running adaptive_chunked_choosers with 2124 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2124 of 2124 choosers\r\n", + "DEBUG - work: write_table cache: expression_values\r\n", + "DEBUG - work: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - work: write_table cache: override_choices\r\n", + "DEBUG - work: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - work: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/work/stop_frequency_values_combined.csv\r\n", + "INFO - work: end estimation\r\n", + "INFO - stop_frequency running segment school with 460 chooser rows\r\n", + "INFO - Initialize Estimator for'school'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - school: estimate.write_spec: output/estimation_data_bundle/stop_frequency/school/stop_frequency_SPEC.csv\r\n", + "DEBUG - school: write_table write: stop_frequency_coefficients_school.csv\r\n", + "DEBUG - school: write_table cache: choosers\r\n", + "INFO - stop_frequency.school.simple_simulate Running adaptive_chunked_choosers with 460 choosers\r\n", + "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", + "DEBUG - school: write_table cache: expression_values\r\n", + "DEBUG - school: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - school: write_table cache: override_choices\r\n", + "DEBUG - school: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - school: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/school/stop_frequency_values_combined.csv\r\n", + "INFO - school: end estimation\r\n", + "INFO - stop_frequency running segment univ with 149 chooser rows\r\n", + "INFO - Initialize Estimator for'univ'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - univ: estimate.write_spec: output/estimation_data_bundle/stop_frequency/univ/stop_frequency_SPEC.csv\r\n", + "DEBUG - univ: write_table write: stop_frequency_coefficients_univ.csv\r\n", + "DEBUG - univ: write_table cache: choosers\r\n", + "INFO - stop_frequency.univ.simple_simulate Running adaptive_chunked_choosers with 149 choosers\r\n", + "INFO - Running chunk 1 of 1 with 149 of 149 choosers\r\n", + "DEBUG - univ: write_table cache: expression_values\r\n", + "DEBUG - univ: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - univ: write_table cache: override_choices\r\n", + "DEBUG - univ: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - univ: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/univ/stop_frequency_values_combined.csv\r\n", + "INFO - univ: end estimation\r\n", + "INFO - stop_frequency running segment social with 182 chooser rows\r\n", + "INFO - Initialize Estimator for'social'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - social: estimate.write_spec: output/estimation_data_bundle/stop_frequency/social/stop_frequency_SPEC.csv\r\n", + "DEBUG - social: write_table write: stop_frequency_coefficients_social.csv\r\n", + "DEBUG - social: write_table cache: choosers\r\n", + "INFO - stop_frequency.social.simple_simulate Running adaptive_chunked_choosers with 182 choosers\r\n", + "INFO - Running chunk 1 of 1 with 182 of 182 choosers\r\n", + "DEBUG - social: write_table cache: expression_values\r\n", + "DEBUG - social: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - social: write_table cache: override_choices\r\n", + "DEBUG - social: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - social: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/social/stop_frequency_values_combined.csv\r\n", + "INFO - social: end estimation\r\n", + "INFO - stop_frequency running segment shopping with 723 chooser rows\r\n", + "INFO - Initialize Estimator for'shopping'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - shopping: estimate.write_spec: output/estimation_data_bundle/stop_frequency/shopping/stop_frequency_SPEC.csv\r\n", + "DEBUG - shopping: write_table write: stop_frequency_coefficients_shopping.csv\r\n", + "DEBUG - shopping: write_table cache: choosers\r\n", + "INFO - stop_frequency.shopping.simple_simulate Running adaptive_chunked_choosers with 723 choosers\r\n", + "INFO - Running chunk 1 of 1 with 723 of 723 choosers\r\n", + "DEBUG - shopping: write_table cache: expression_values\r\n", + "DEBUG - shopping: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - shopping: write_table cache: override_choices\r\n", + "DEBUG - shopping: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - shopping: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/shopping/stop_frequency_values_combined.csv\r\n", + "INFO - shopping: end estimation\r\n", + "INFO - stop_frequency running segment eatout with 316 chooser rows\r\n", + "INFO - Initialize Estimator for'eatout'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - eatout: estimate.write_spec: output/estimation_data_bundle/stop_frequency/eatout/stop_frequency_SPEC.csv\r\n", + "DEBUG - eatout: write_table write: stop_frequency_coefficients_eatout.csv\r\n", + "DEBUG - eatout: write_table cache: choosers\r\n", + "INFO - stop_frequency.eatout.simple_simulate Running adaptive_chunked_choosers with 316 choosers\r\n", + "INFO - Running chunk 1 of 1 with 316 of 316 choosers\r\n", + "DEBUG - eatout: write_table cache: expression_values\r\n", + "DEBUG - eatout: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - eatout: write_table cache: override_choices\r\n", + "DEBUG - eatout: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - eatout: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/eatout/stop_frequency_values_combined.csv\r\n", + "INFO - eatout: end estimation\r\n", + "INFO - stop_frequency running segment escort with 403 chooser rows\r\n", + "INFO - Initialize Estimator for'escort'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - escort: estimate.write_spec: output/estimation_data_bundle/stop_frequency/escort/stop_frequency_SPEC.csv\r\n", + "DEBUG - escort: write_table write: stop_frequency_coefficients_escort.csv\r\n", + "DEBUG - escort: write_table cache: choosers\r\n", + "INFO - stop_frequency.escort.simple_simulate Running adaptive_chunked_choosers with 403 choosers\r\n", + "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", + "DEBUG - escort: write_table cache: expression_values\r\n", + "DEBUG - escort: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - escort: write_table cache: override_choices\r\n", + "DEBUG - escort: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - escort: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/escort/stop_frequency_values_combined.csv\r\n", + "INFO - escort: end estimation\r\n", + "INFO - stop_frequency running segment othmaint with 393 chooser rows\r\n", + "INFO - Initialize Estimator for'othmaint'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - othmaint: estimate.write_spec: output/estimation_data_bundle/stop_frequency/othmaint/stop_frequency_SPEC.csv\r\n", + "DEBUG - othmaint: write_table write: stop_frequency_coefficients_othmaint.csv\r\n", + "DEBUG - othmaint: write_table cache: choosers\r\n", + "INFO - stop_frequency.othmaint.simple_simulate Running adaptive_chunked_choosers with 393 choosers\r\n", + "INFO - Running chunk 1 of 1 with 393 of 393 choosers\r\n", + "DEBUG - othmaint: write_table cache: expression_values\r\n", + "DEBUG - othmaint: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - othmaint: write_table cache: override_choices\r\n", + "DEBUG - othmaint: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - othmaint: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/othmaint/stop_frequency_values_combined.csv\r\n", + "INFO - othmaint: end estimation\r\n", + "INFO - stop_frequency running segment othdiscr with 564 chooser rows\r\n", + "INFO - Initialize Estimator for'othdiscr'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - othdiscr: estimate.write_spec: output/estimation_data_bundle/stop_frequency/othdiscr/stop_frequency_SPEC.csv\r\n", + "DEBUG - othdiscr: write_table write: stop_frequency_coefficients_othdiscr.csv\r\n", + "DEBUG - othdiscr: write_table cache: choosers\r\n", + "INFO - stop_frequency.othdiscr.simple_simulate Running adaptive_chunked_choosers with 564 choosers\r\n", + "INFO - Running chunk 1 of 1 with 564 of 564 choosers\r\n", + "DEBUG - othdiscr: write_table cache: expression_values\r\n", + "DEBUG - othdiscr: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - othdiscr: write_table cache: override_choices\r\n", + "DEBUG - othdiscr: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - othdiscr: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/othdiscr/stop_frequency_values_combined.csv\r\n", + "INFO - othdiscr: end estimation\r\n", + "INFO - stop_frequency running segment atwork with 464 chooser rows\r\n", + "INFO - Initialize Estimator for'atwork'\r\n", + "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", + "DEBUG - atwork: estimate.write_spec: output/estimation_data_bundle/stop_frequency/atwork/stop_frequency_SPEC.csv\r\n", + "DEBUG - atwork: write_table write: stop_frequency_coefficients_atwork.csv\r\n", + "DEBUG - atwork: write_table cache: choosers\r\n", + "INFO - stop_frequency.atwork.simple_simulate Running adaptive_chunked_choosers with 464 choosers\r\n", + "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", + "DEBUG - atwork: write_table cache: expression_values\r\n", + "DEBUG - atwork: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using tours.index\r\n", + "DEBUG - atwork: write_table cache: override_choices\r\n", + "DEBUG - atwork: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - atwork: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/atwork/stop_frequency_values_combined.csv\r\n", + "INFO - atwork: end estimation\r\n", + "INFO - stop_frequency top 10 value counts:\r\n", + "0out_0in 4040\r\n", + "0out_1in 636\r\n", + "1out_0in 421\r\n", + "0out_2in 178\r\n", + "1out_1in 164\r\n", + "0out_3in 91\r\n", + "2out_0in 62\r\n", + "1out_3in 50\r\n", + "1out_2in 44\r\n", + "2out_1in 31\r\n", + "Name: stop_frequency, dtype: int64\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "Time to execute step 'stop_frequency': 6.05 s\r\n", + "Total time to execute iteration 1 with iteration value None: 6.05 s\r\n", + "INFO - #run_model running step trip_purpose\r\n", + "Running step 'trip_purpose'\r\n", + "INFO - Initialize Estimator for'trip_purpose'\r\n", + "DEBUG - trip_purpose: write_table cache: choosers\r\n", + "DEBUG - trip_purpose: estimate.write_spec: output/estimation_data_bundle/trip_purpose/trip_purpose_PROBS_SPEC.csv\r\n", + "INFO - assign purpose to 5778 last outbound trips\r\n", + "INFO - assign purpose to 5778 last inbound trips\r\n", + "INFO - assign purpose to 2796 intermediate trips\r\n", + "INFO - trip_purpose Running adaptive_chunked_choosers with 2796 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2796 of 2796 choosers\r\n", + " primary_purpose outbound ... social othdiscr\r\n", + "trip_id ... \r\n", + "206561 univ True ... 0.048 0.050\r\n", + "9188185 univ True ... 0.048 0.050\r\n", + "9668377 univ True ... 0.048 0.050\r\n", + "9806797 univ False ... 0.067 0.113\r\n", + "10849837 univ False ... 0.067 0.113\r\n", + "... ... ... ... ... ...\r\n", + "2414259747 atwork True ... 0.004 0.014\r\n", + "2414259749 atwork False ... 0.019 0.018\r\n", + "2414488393 atwork True ... 0.004 0.014\r\n", + "2423389333 atwork False ... 0.019 0.018\r\n", + "2440963725 atwork False ... 0.019 0.018\r\n", + "\r\n", + "[2796 rows x 14 columns]\r\n", + "DEBUG - trip_purpose: write_table cache: probs\r\n", + "DEBUG - trip_purpose: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_purpose: write_table cache: override_choices\r\n", + "DEBUG - trip_purpose: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'choosers', 'probs']\r\n", + "DEBUG - trip_purpose: write_omnibus_choosers: output/estimation_data_bundle/trip_purpose/trip_purpose_values_combined.csv\r\n", + "INFO - trip_purpose: end estimation\r\n", + "Time to execute step 'trip_purpose': 0.33 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.33 s\r\n", + "INFO - #run_model running step trip_destination\r\n", + "Running step 'trip_destination'\r\n", + "INFO - Initialize Estimator for'trip_destination'\r\n", + "DEBUG - trip_destination: write_table write: trip_destination_coefficients.csv\r\n", + "DEBUG - trip_destination: estimate.write_spec: output/estimation_data_bundle/trip_destination/trip_destination_SPEC.csv\r\n", + "DEBUG - trip_destination: write_table write: size_terms\r\n", + "DEBUG - trip_destination: write_table write: landuse\r\n", + "INFO - Running trip_destination with 14352 trips\r\n", + "INFO - Running trip_destination.trip_num_1 with 2061 trips\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.atwork with 124 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.atwork.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 124 choosers\r\n", + "INFO - Running chunk 1 of 1 with 124 of 124 choosers\r\n", + "INFO - Running eval_interaction_utilities on 23560 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.atwork.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.atwork.trip_destination_sample : 0.09 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.atwork.compute_logsums with 23560 samples\r\n", + "INFO - trip_destination.trip_num_1.atwork.compute_logsums.od Running adaptive_chunked_choosers with 23560 choosers\r\n", + "INFO - Running chunk 1 of 1 with 23560 of 23560 choosers\r\n", + "INFO - trip_destination.trip_num_1.atwork.compute_logsums.dp Running adaptive_chunked_choosers with 23560 choosers\r\n", + "INFO - Running chunk 1 of 1 with 23560 of 23560 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.atwork.compute_logsums : 4.409 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 124 trips\r\n", + "INFO - trip_destination.trip_num_1.atwork.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 124 choosers and 23560 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 124 of 124 choosers\r\n", + "INFO - Running eval_interaction_utilities on 23560 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.atwork.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.atwork.trip_destination_simulate : 0.372 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.eatout with 66 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.eatout.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 66 choosers\r\n", + "INFO - Running chunk 1 of 1 with 66 of 66 choosers\r\n", + "INFO - Running eval_interaction_utilities on 12540 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.eatout.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.eatout.trip_destination_sample : 0.086 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.eatout.compute_logsums with 12540 samples\r\n", + "INFO - trip_destination.trip_num_1.eatout.compute_logsums.od Running adaptive_chunked_choosers with 12540 choosers\r\n", + "INFO - Running chunk 1 of 1 with 12540 of 12540 choosers\r\n", + "INFO - trip_destination.trip_num_1.eatout.compute_logsums.dp Running adaptive_chunked_choosers with 12540 choosers\r\n", + "INFO - Running chunk 1 of 1 with 12540 of 12540 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.eatout.compute_logsums : 3.345 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 66 trips\r\n", + "INFO - trip_destination.trip_num_1.eatout.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 66 choosers and 12540 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 66 of 66 choosers\r\n", + "INFO - Running eval_interaction_utilities on 12540 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.eatout.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.eatout.trip_destination_simulate : 0.266 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.escort with 117 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.escort.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.escort.sample.interaction_sample Running adaptive_chunked_choosers with 117 choosers\r\n", + "INFO - Running chunk 1 of 1 with 117 of 117 choosers\r\n", + "INFO - Running eval_interaction_utilities on 22230 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.escort.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.escort.trip_destination_sample : 0.119 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.escort.compute_logsums with 22230 samples\r\n", + "INFO - trip_destination.trip_num_1.escort.compute_logsums.od Running adaptive_chunked_choosers with 22230 choosers\r\n", + "INFO - Running chunk 1 of 1 with 22230 of 22230 choosers\r\n", + "INFO - trip_destination.trip_num_1.escort.compute_logsums.dp Running adaptive_chunked_choosers with 22230 choosers\r\n", + "INFO - Running chunk 1 of 1 with 22230 of 22230 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.escort.compute_logsums : 4.465 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 117 trips\r\n", + "INFO - trip_destination.trip_num_1.escort.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 117 choosers and 22230 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 117 of 117 choosers\r\n", + "INFO - Running eval_interaction_utilities on 22230 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.escort.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.escort.trip_destination_simulate : 0.431 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.othdiscr with 207 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 207 choosers\r\n", + "INFO - Running chunk 1 of 1 with 207 of 207 choosers\r\n", + "INFO - Running eval_interaction_utilities on 39330 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.othdiscr.trip_destination_sample : 0.117 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.othdiscr.compute_logsums with 39330 samples\r\n", + "INFO - trip_destination.trip_num_1.othdiscr.compute_logsums.od Running adaptive_chunked_choosers with 39330 choosers\r\n", + "INFO - Running chunk 1 of 1 with 39330 of 39330 choosers\r\n", + "INFO - trip_destination.trip_num_1.othdiscr.compute_logsums.dp Running adaptive_chunked_choosers with 39330 choosers\r\n", + "INFO - Running chunk 1 of 1 with 39330 of 39330 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.othdiscr.compute_logsums : 6.014 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 207 trips\r\n", + "INFO - trip_destination.trip_num_1.othdiscr.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 207 choosers and 39330 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 207 of 207 choosers\r\n", + "INFO - Running eval_interaction_utilities on 39330 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.othdiscr.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.othdiscr.trip_destination_simulate : 0.567 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.othmaint with 122 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 122 choosers\r\n", + "INFO - Running chunk 1 of 1 with 122 of 122 choosers\r\n", + "INFO - Running eval_interaction_utilities on 23180 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.othmaint.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.othmaint.trip_destination_sample : 0.095 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.othmaint.compute_logsums with 23180 samples\r\n", + "INFO - trip_destination.trip_num_1.othmaint.compute_logsums.od Running adaptive_chunked_choosers with 23180 choosers\r\n", + "INFO - Running chunk 1 of 1 with 23180 of 23180 choosers\r\n", + "INFO - trip_destination.trip_num_1.othmaint.compute_logsums.dp Running adaptive_chunked_choosers with 23180 choosers\r\n", + "INFO - Running chunk 1 of 1 with 23180 of 23180 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.othmaint.compute_logsums : 4.333 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 122 trips\r\n", + "INFO - trip_destination.trip_num_1.othmaint.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 122 choosers and 23180 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 122 of 122 choosers\r\n", + "INFO - Running eval_interaction_utilities on 23180 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.othmaint.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.othmaint.trip_destination_simulate : 0.417 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.school with 119 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.school.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.school.sample.interaction_sample Running adaptive_chunked_choosers with 119 choosers\r\n", + "INFO - Running chunk 1 of 1 with 119 of 119 choosers\r\n", + "INFO - Running eval_interaction_utilities on 22610 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.school.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.school.trip_destination_sample : 0.091 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.school.compute_logsums with 22610 samples\r\n", + "INFO - trip_destination.trip_num_1.school.compute_logsums.od Running adaptive_chunked_choosers with 22610 choosers\r\n", + "INFO - Running chunk 1 of 1 with 22610 of 22610 choosers\r\n", + "INFO - trip_destination.trip_num_1.school.compute_logsums.dp Running adaptive_chunked_choosers with 22610 choosers\r\n", + "INFO - Running chunk 1 of 1 with 22610 of 22610 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.school.compute_logsums : 4.473 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 119 trips\r\n", + "INFO - trip_destination.trip_num_1.school.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 119 choosers and 22610 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 119 of 119 choosers\r\n", + "INFO - Running eval_interaction_utilities on 22610 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.school.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.school.trip_destination_simulate : 0.423 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.shopping with 322 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.shopping.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 322 choosers\r\n", + "INFO - Running chunk 1 of 1 with 322 of 322 choosers\r\n", + "INFO - Running eval_interaction_utilities on 61180 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.shopping.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.shopping.trip_destination_sample : 0.16 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.shopping.compute_logsums with 61180 samples\r\n", + "INFO - trip_destination.trip_num_1.shopping.compute_logsums.od Running adaptive_chunked_choosers with 61180 choosers\r\n", + "INFO - Running chunk 1 of 1 with 61180 of 61180 choosers\r\n", + "INFO - trip_destination.trip_num_1.shopping.compute_logsums.dp Running adaptive_chunked_choosers with 61180 choosers\r\n", + "INFO - Running chunk 1 of 1 with 61180 of 61180 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.shopping.compute_logsums : 8.168 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 322 trips\r\n", + "INFO - trip_destination.trip_num_1.shopping.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 322 choosers and 61180 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 322 of 322 choosers\r\n", + "INFO - Running eval_interaction_utilities on 61180 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.shopping.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.shopping.trip_destination_simulate : 0.892 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.social with 50 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.social.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.social.sample.interaction_sample Running adaptive_chunked_choosers with 50 choosers\r\n", + "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", + "INFO - Running eval_interaction_utilities on 9500 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.social.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.social.trip_destination_sample : 0.074 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.social.compute_logsums with 9500 samples\r\n", + "INFO - trip_destination.trip_num_1.social.compute_logsums.od Running adaptive_chunked_choosers with 9500 choosers\r\n", + "INFO - Running chunk 1 of 1 with 9500 of 9500 choosers\r\n", + "INFO - trip_destination.trip_num_1.social.compute_logsums.dp Running adaptive_chunked_choosers with 9500 choosers\r\n", + "INFO - Running chunk 1 of 1 with 9500 of 9500 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.social.compute_logsums : 2.802 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 50 trips\r\n", + "INFO - trip_destination.trip_num_1.social.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 50 choosers and 9500 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", + "INFO - Running eval_interaction_utilities on 9500 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.social.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.social.trip_destination_simulate : 0.285 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.univ with 75 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.univ.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.univ.sample.interaction_sample Running adaptive_chunked_choosers with 75 choosers\r\n", + "INFO - Running chunk 1 of 1 with 75 of 75 choosers\r\n", + "INFO - Running eval_interaction_utilities on 14250 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.univ.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.univ.trip_destination_sample : 0.084 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.univ.compute_logsums with 14250 samples\r\n", + "INFO - trip_destination.trip_num_1.univ.compute_logsums.od Running adaptive_chunked_choosers with 14250 choosers\r\n", + "INFO - Running chunk 1 of 1 with 14250 of 14250 choosers\r\n", + "INFO - trip_destination.trip_num_1.univ.compute_logsums.dp Running adaptive_chunked_choosers with 14250 choosers\r\n", + "INFO - Running chunk 1 of 1 with 14250 of 14250 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.univ.compute_logsums : 3.17 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 75 trips\r\n", + "INFO - trip_destination.trip_num_1.univ.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 75 choosers and 14250 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 75 of 75 choosers\r\n", + "INFO - Running eval_interaction_utilities on 14250 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.univ.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.univ.trip_destination_simulate : 0.31 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_1.work with 859 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.work.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_1.work.sample.interaction_sample Running adaptive_chunked_choosers with 859 choosers\r\n", + "INFO - Running chunk 1 of 1 with 859 of 859 choosers\r\n", + "INFO - Running eval_interaction_utilities on 163210 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_1.work.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_1.work.trip_destination_sample : 0.278 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_1.work.compute_logsums with 163210 samples\r\n", + "INFO - trip_destination.trip_num_1.work.compute_logsums.od Running adaptive_chunked_choosers with 163210 choosers\r\n", + "INFO - Running chunk 1 of 1 with 163210 of 163210 choosers\r\n", + "INFO - trip_destination.trip_num_1.work.compute_logsums.dp Running adaptive_chunked_choosers with 163210 choosers\r\n", + "INFO - Running chunk 1 of 1 with 163210 of 163210 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_1.work.compute_logsums : 17.277 seconds (0.3 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 859 trips\r\n", + "INFO - trip_destination.trip_num_1.work.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 859 choosers and 163210 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 859 of 859 choosers\r\n", + "INFO - Running eval_interaction_utilities on 163210 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.work.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_1.work.trip_destination_simulate : 3.053 seconds (0.1 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2 with 542 trips\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.atwork with 18 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.atwork.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 18 choosers\r\n", + "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3420 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.atwork.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.atwork.trip_destination_sample : 0.07 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.atwork.compute_logsums with 3420 samples\r\n", + "INFO - trip_destination.trip_num_2.atwork.compute_logsums.od Running adaptive_chunked_choosers with 3420 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3420 of 3420 choosers\r\n", + "INFO - trip_destination.trip_num_2.atwork.compute_logsums.dp Running adaptive_chunked_choosers with 3420 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3420 of 3420 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.atwork.compute_logsums : 2.279 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 18 trips\r\n", + "INFO - trip_destination.trip_num_2.atwork.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 18 choosers and 3420 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3420 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.atwork.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.atwork.trip_destination_simulate : 0.383 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.eatout with 11 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.eatout.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 11 choosers\r\n", + "INFO - Running chunk 1 of 1 with 11 of 11 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2090 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.eatout.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.eatout.trip_destination_sample : 0.084 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.eatout.compute_logsums with 2090 samples\r\n", + "INFO - trip_destination.trip_num_2.eatout.compute_logsums.od Running adaptive_chunked_choosers with 2090 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2090 of 2090 choosers\r\n", + "INFO - trip_destination.trip_num_2.eatout.compute_logsums.dp Running adaptive_chunked_choosers with 2090 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2090 of 2090 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.eatout.compute_logsums : 2.19 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 11 trips\r\n", + "INFO - trip_destination.trip_num_2.eatout.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 11 choosers and 2090 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 11 of 11 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2090 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.eatout.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.eatout.trip_destination_simulate : 0.486 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.escort with 32 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.escort.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.escort.sample.interaction_sample Running adaptive_chunked_choosers with 32 choosers\r\n", + "INFO - Running chunk 1 of 1 with 32 of 32 choosers\r\n", + "INFO - Running eval_interaction_utilities on 6080 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.escort.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.escort.trip_destination_sample : 0.092 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.escort.compute_logsums with 6080 samples\r\n", + "INFO - trip_destination.trip_num_2.escort.compute_logsums.od Running adaptive_chunked_choosers with 6080 choosers\r\n", + "INFO - Running chunk 1 of 1 with 6080 of 6080 choosers\r\n", + "INFO - trip_destination.trip_num_2.escort.compute_logsums.dp Running adaptive_chunked_choosers with 6080 choosers\r\n", + "INFO - Running chunk 1 of 1 with 6080 of 6080 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.escort.compute_logsums : 2.883 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 32 trips\r\n", + "INFO - trip_destination.trip_num_2.escort.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 32 choosers and 6080 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 32 of 32 choosers\r\n", + "INFO - Running eval_interaction_utilities on 6080 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.escort.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.escort.trip_destination_simulate : 0.844 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.othdiscr with 56 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 56 choosers\r\n", + "INFO - Running chunk 1 of 1 with 56 of 56 choosers\r\n", + "INFO - Running eval_interaction_utilities on 10640 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.othdiscr.trip_destination_sample : 0.15 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.othdiscr.compute_logsums with 10640 samples\r\n", + "INFO - trip_destination.trip_num_2.othdiscr.compute_logsums.od Running adaptive_chunked_choosers with 10640 choosers\r\n", + "INFO - Running chunk 1 of 1 with 10640 of 10640 choosers\r\n", + "INFO - trip_destination.trip_num_2.othdiscr.compute_logsums.dp Running adaptive_chunked_choosers with 10640 choosers\r\n", + "INFO - Running chunk 1 of 1 with 10640 of 10640 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.othdiscr.compute_logsums : 4.791 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 56 trips\r\n", + "INFO - trip_destination.trip_num_2.othdiscr.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 56 choosers and 10640 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 56 of 56 choosers\r\n", + "INFO - Running eval_interaction_utilities on 10640 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.othdiscr.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.othdiscr.trip_destination_simulate : 0.442 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.othmaint with 53 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 53 choosers\r\n", + "INFO - Running chunk 1 of 1 with 53 of 53 choosers\r\n", + "INFO - Running eval_interaction_utilities on 10070 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.othmaint.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.othmaint.trip_destination_sample : 0.07 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.othmaint.compute_logsums with 10070 samples\r\n", + "INFO - trip_destination.trip_num_2.othmaint.compute_logsums.od Running adaptive_chunked_choosers with 10070 choosers\r\n", + "INFO - Running chunk 1 of 1 with 10070 of 10070 choosers\r\n", + "INFO - trip_destination.trip_num_2.othmaint.compute_logsums.dp Running adaptive_chunked_choosers with 10070 choosers\r\n", + "INFO - Running chunk 1 of 1 with 10070 of 10070 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.othmaint.compute_logsums : 2.728 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 53 trips\r\n", + "INFO - trip_destination.trip_num_2.othmaint.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 53 choosers and 10070 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 53 of 53 choosers\r\n", + "INFO - Running eval_interaction_utilities on 10070 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.othmaint.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.othmaint.trip_destination_simulate : 0.486 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.school with 29 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.school.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.school.sample.interaction_sample Running adaptive_chunked_choosers with 29 choosers\r\n", + "INFO - Running chunk 1 of 1 with 29 of 29 choosers\r\n", + "INFO - Running eval_interaction_utilities on 5510 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.school.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.school.trip_destination_sample : 0.055 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.school.compute_logsums with 5510 samples\r\n", + "INFO - trip_destination.trip_num_2.school.compute_logsums.od Running adaptive_chunked_choosers with 5510 choosers\r\n", + "INFO - Running chunk 1 of 1 with 5510 of 5510 choosers\r\n", + "INFO - trip_destination.trip_num_2.school.compute_logsums.dp Running adaptive_chunked_choosers with 5510 choosers\r\n", + "INFO - Running chunk 1 of 1 with 5510 of 5510 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.school.compute_logsums : 2.617 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 29 trips\r\n", + "INFO - trip_destination.trip_num_2.school.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 29 choosers and 5510 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 29 of 29 choosers\r\n", + "INFO - Running eval_interaction_utilities on 5510 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.school.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.school.trip_destination_simulate : 0.609 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.shopping with 80 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.shopping.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 80 choosers\r\n", + "INFO - Running chunk 1 of 1 with 80 of 80 choosers\r\n", + "INFO - Running eval_interaction_utilities on 15200 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.shopping.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.shopping.trip_destination_sample : 0.073 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.shopping.compute_logsums with 15200 samples\r\n", + "INFO - trip_destination.trip_num_2.shopping.compute_logsums.od Running adaptive_chunked_choosers with 15200 choosers\r\n", + "INFO - Running chunk 1 of 1 with 15200 of 15200 choosers\r\n", + "INFO - trip_destination.trip_num_2.shopping.compute_logsums.dp Running adaptive_chunked_choosers with 15200 choosers\r\n", + "INFO - Running chunk 1 of 1 with 15200 of 15200 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.shopping.compute_logsums : 3.393 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 80 trips\r\n", + "INFO - trip_destination.trip_num_2.shopping.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 80 choosers and 15200 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 80 of 80 choosers\r\n", + "INFO - Running eval_interaction_utilities on 15200 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.shopping.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.shopping.trip_destination_simulate : 0.622 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.social with 7 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.social.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.social.sample.interaction_sample Running adaptive_chunked_choosers with 7 choosers\r\n", + "INFO - Running chunk 1 of 1 with 7 of 7 choosers\r\n", + "INFO - Running eval_interaction_utilities on 1330 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.social.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.social.trip_destination_sample : 0.076 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.social.compute_logsums with 1330 samples\r\n", + "INFO - trip_destination.trip_num_2.social.compute_logsums.od Running adaptive_chunked_choosers with 1330 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1330 of 1330 choosers\r\n", + "INFO - trip_destination.trip_num_2.social.compute_logsums.dp Running adaptive_chunked_choosers with 1330 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1330 of 1330 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.social.compute_logsums : 2.247 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 7 trips\r\n", + "INFO - trip_destination.trip_num_2.social.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 7 choosers and 1330 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 7 of 7 choosers\r\n", + "INFO - Running eval_interaction_utilities on 1330 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.social.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.social.trip_destination_simulate : 0.414 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.univ with 20 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.univ.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.univ.sample.interaction_sample Running adaptive_chunked_choosers with 20 choosers\r\n", + "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3800 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.univ.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.univ.trip_destination_sample : 0.054 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.univ.compute_logsums with 3800 samples\r\n", + "INFO - trip_destination.trip_num_2.univ.compute_logsums.od Running adaptive_chunked_choosers with 3800 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3800 of 3800 choosers\r\n", + "INFO - trip_destination.trip_num_2.univ.compute_logsums.dp Running adaptive_chunked_choosers with 3800 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3800 of 3800 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.univ.compute_logsums : 2.624 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 20 trips\r\n", + "INFO - trip_destination.trip_num_2.univ.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 20 choosers and 3800 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", + "INFO - Running eval_interaction_utilities on 3800 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.univ.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.univ.trip_destination_simulate : 0.485 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_2.work with 236 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.work.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_2.work.sample.interaction_sample Running adaptive_chunked_choosers with 236 choosers\r\n", + "INFO - Running chunk 1 of 1 with 236 of 236 choosers\r\n", + "INFO - Running eval_interaction_utilities on 44840 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_2.work.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_2.work.trip_destination_sample : 0.121 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_2.work.compute_logsums with 44840 samples\r\n", + "INFO - trip_destination.trip_num_2.work.compute_logsums.od Running adaptive_chunked_choosers with 44840 choosers\r\n", + "INFO - Running chunk 1 of 1 with 44840 of 44840 choosers\r\n", + "INFO - trip_destination.trip_num_2.work.compute_logsums.dp Running adaptive_chunked_choosers with 44840 choosers\r\n", + "INFO - Running chunk 1 of 1 with 44840 of 44840 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_2.work.compute_logsums : 6.123 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 236 trips\r\n", + "INFO - trip_destination.trip_num_2.work.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 236 choosers and 44840 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 236 of 236 choosers\r\n", + "INFO - Running eval_interaction_utilities on 44840 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.work.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_2.work.trip_destination_simulate : 0.961 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3 with 193 trips\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.atwork with 3 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.atwork.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 3 choosers\r\n", + "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", + "INFO - Running eval_interaction_utilities on 570 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.atwork.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.atwork.trip_destination_sample : 0.062 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.atwork.compute_logsums with 570 samples\r\n", + "INFO - trip_destination.trip_num_3.atwork.compute_logsums.od Running adaptive_chunked_choosers with 570 choosers\r\n", + "INFO - Running chunk 1 of 1 with 570 of 570 choosers\r\n", + "INFO - trip_destination.trip_num_3.atwork.compute_logsums.dp Running adaptive_chunked_choosers with 570 choosers\r\n", + "INFO - Running chunk 1 of 1 with 570 of 570 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.atwork.compute_logsums : 1.823 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 3 trips\r\n", + "INFO - trip_destination.trip_num_3.atwork.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 3 choosers and 570 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", + "INFO - Running eval_interaction_utilities on 570 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.atwork.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.atwork.trip_destination_simulate : 0.433 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.eatout with 4 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.eatout.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 4 choosers\r\n", + "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", + "INFO - Running eval_interaction_utilities on 760 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.eatout.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.eatout.trip_destination_sample : 0.069 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.eatout.compute_logsums with 760 samples\r\n", + "INFO - trip_destination.trip_num_3.eatout.compute_logsums.od Running adaptive_chunked_choosers with 760 choosers\r\n", + "INFO - Running chunk 1 of 1 with 760 of 760 choosers\r\n", + "INFO - trip_destination.trip_num_3.eatout.compute_logsums.dp Running adaptive_chunked_choosers with 760 choosers\r\n", + "INFO - Running chunk 1 of 1 with 760 of 760 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.eatout.compute_logsums : 1.92 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 4 trips\r\n", + "INFO - trip_destination.trip_num_3.eatout.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 4 choosers and 760 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", + "INFO - Running eval_interaction_utilities on 760 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.eatout.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.eatout.trip_destination_simulate : 0.447 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.escort with 12 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.escort.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.escort.sample.interaction_sample Running adaptive_chunked_choosers with 12 choosers\r\n", + "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2280 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.escort.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.escort.trip_destination_sample : 0.048 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.escort.compute_logsums with 2280 samples\r\n", + "INFO - trip_destination.trip_num_3.escort.compute_logsums.od Running adaptive_chunked_choosers with 2280 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2280 of 2280 choosers\r\n", + "INFO - trip_destination.trip_num_3.escort.compute_logsums.dp Running adaptive_chunked_choosers with 2280 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2280 of 2280 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.escort.compute_logsums : 2.102 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 12 trips\r\n", + "INFO - trip_destination.trip_num_3.escort.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 12 choosers and 2280 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2280 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.escort.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.escort.trip_destination_simulate : 0.462 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.othdiscr with 22 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 22 choosers\r\n", + "INFO - Running chunk 1 of 1 with 22 of 22 choosers\r\n", + "INFO - Running eval_interaction_utilities on 4180 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.othdiscr.trip_destination_sample : 0.071 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.othdiscr.compute_logsums with 4180 samples\r\n", + "INFO - trip_destination.trip_num_3.othdiscr.compute_logsums.od Running adaptive_chunked_choosers with 4180 choosers\r\n", + "INFO - Running chunk 1 of 1 with 4180 of 4180 choosers\r\n", + "INFO - trip_destination.trip_num_3.othdiscr.compute_logsums.dp Running adaptive_chunked_choosers with 4180 choosers\r\n", + "INFO - Running chunk 1 of 1 with 4180 of 4180 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.othdiscr.compute_logsums : 2.22 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 22 trips\r\n", + "INFO - trip_destination.trip_num_3.othdiscr.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 22 choosers and 4180 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 22 of 22 choosers\r\n", + "INFO - Running eval_interaction_utilities on 4180 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.othdiscr.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.othdiscr.trip_destination_simulate : 0.506 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.othmaint with 14 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 14 choosers\r\n", + "INFO - Running chunk 1 of 1 with 14 of 14 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2660 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.othmaint.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.othmaint.trip_destination_sample : 0.066 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.othmaint.compute_logsums with 2660 samples\r\n", + "INFO - trip_destination.trip_num_3.othmaint.compute_logsums.od Running adaptive_chunked_choosers with 2660 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2660 of 2660 choosers\r\n", + "INFO - trip_destination.trip_num_3.othmaint.compute_logsums.dp Running adaptive_chunked_choosers with 2660 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2660 of 2660 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.othmaint.compute_logsums : 2.088 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 14 trips\r\n", + "INFO - trip_destination.trip_num_3.othmaint.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 14 choosers and 2660 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 14 of 14 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2660 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.othmaint.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.othmaint.trip_destination_simulate : 0.483 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.school with 13 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.school.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.school.sample.interaction_sample Running adaptive_chunked_choosers with 13 choosers\r\n", + "INFO - Running chunk 1 of 1 with 13 of 13 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2470 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.school.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.school.trip_destination_sample : 0.052 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.school.compute_logsums with 2470 samples\r\n", + "INFO - trip_destination.trip_num_3.school.compute_logsums.od Running adaptive_chunked_choosers with 2470 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2470 of 2470 choosers\r\n", + "INFO - trip_destination.trip_num_3.school.compute_logsums.dp Running adaptive_chunked_choosers with 2470 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2470 of 2470 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.school.compute_logsums : 2.017 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 13 trips\r\n", + "INFO - trip_destination.trip_num_3.school.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 13 choosers and 2470 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 13 of 13 choosers\r\n", + "INFO - Running eval_interaction_utilities on 2470 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.school.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.school.trip_destination_simulate : 0.458 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.shopping with 28 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.shopping.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 28 choosers\r\n", + "INFO - Running chunk 1 of 1 with 28 of 28 choosers\r\n", + "INFO - Running eval_interaction_utilities on 5320 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.shopping.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.shopping.trip_destination_sample : 0.078 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.shopping.compute_logsums with 5320 samples\r\n", + "INFO - trip_destination.trip_num_3.shopping.compute_logsums.od Running adaptive_chunked_choosers with 5320 choosers\r\n", + "INFO - Running chunk 1 of 1 with 5320 of 5320 choosers\r\n", + "INFO - trip_destination.trip_num_3.shopping.compute_logsums.dp Running adaptive_chunked_choosers with 5320 choosers\r\n", + "INFO - Running chunk 1 of 1 with 5320 of 5320 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.shopping.compute_logsums : 2.804 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 28 trips\r\n", + "INFO - trip_destination.trip_num_3.shopping.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 28 choosers and 5320 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 28 of 28 choosers\r\n", + "INFO - Running eval_interaction_utilities on 5320 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.shopping.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.shopping.trip_destination_simulate : 0.513 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.social with 2 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.social.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.social.sample.interaction_sample Running adaptive_chunked_choosers with 2 choosers\r\n", + "INFO - Running chunk 1 of 1 with 2 of 2 choosers\r\n", + "INFO - Running eval_interaction_utilities on 380 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.social.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.social.trip_destination_sample : 0.07 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.social.compute_logsums with 380 samples\r\n", + "INFO - trip_destination.trip_num_3.social.compute_logsums.od Running adaptive_chunked_choosers with 380 choosers\r\n", + "INFO - Running chunk 1 of 1 with 380 of 380 choosers\r\n", + "INFO - trip_destination.trip_num_3.social.compute_logsums.dp Running adaptive_chunked_choosers with 380 choosers\r\n", + "INFO - Running chunk 1 of 1 with 380 of 380 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.social.compute_logsums : 1.945 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 2 trips\r\n", + "INFO - trip_destination.trip_num_3.social.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 2 choosers and 380 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 2 of 2 choosers\r\n", + "INFO - Running eval_interaction_utilities on 380 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.social.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.social.trip_destination_simulate : 0.472 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.univ with 10 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.univ.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.univ.sample.interaction_sample Running adaptive_chunked_choosers with 10 choosers\r\n", + "INFO - Running chunk 1 of 1 with 10 of 10 choosers\r\n", + "INFO - Running eval_interaction_utilities on 1900 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.univ.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.univ.trip_destination_sample : 0.075 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.univ.compute_logsums with 1900 samples\r\n", + "INFO - trip_destination.trip_num_3.univ.compute_logsums.od Running adaptive_chunked_choosers with 1900 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1900 of 1900 choosers\r\n", + "INFO - trip_destination.trip_num_3.univ.compute_logsums.dp Running adaptive_chunked_choosers with 1900 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1900 of 1900 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.univ.compute_logsums : 1.925 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 10 trips\r\n", + "INFO - trip_destination.trip_num_3.univ.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 10 choosers and 1900 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 10 of 10 choosers\r\n", + "INFO - Running eval_interaction_utilities on 1900 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.univ.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.univ.trip_destination_simulate : 0.53 seconds (0.0 minutes)\r\n", + "INFO - choose_trip_destination trip_destination.trip_num_3.work with 85 trips\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.work.sample using unsampled alternatives short_circuit_choices\r\n", + "INFO - trip_destination.trip_num_3.work.sample.interaction_sample Running adaptive_chunked_choosers with 85 choosers\r\n", + "INFO - Running chunk 1 of 1 with 85 of 85 choosers\r\n", + "INFO - Running eval_interaction_utilities on 16150 rows\r\n", + "INFO - Estimation mode for trip_destination.trip_num_3.work.sample.interaction_sample using unsampled alternatives\r\n", + "INFO - Time to execute trip_destination.trip_num_3.work.trip_destination_sample : 0.083 seconds (0.0 minutes)\r\n", + "INFO - Running trip_destination.trip_num_3.work.compute_logsums with 16150 samples\r\n", + "INFO - trip_destination.trip_num_3.work.compute_logsums.od Running adaptive_chunked_choosers with 16150 choosers\r\n", + "INFO - Running chunk 1 of 1 with 16150 of 16150 choosers\r\n", + "INFO - trip_destination.trip_num_3.work.compute_logsums.dp Running adaptive_chunked_choosers with 16150 choosers\r\n", + "INFO - Running chunk 1 of 1 with 16150 of 16150 choosers\r\n", + "INFO - Time to execute trip_destination.trip_num_3.work.compute_logsums : 3.316 seconds (0.1 minutes)\r\n", + "DEBUG - trip_destination: write_table cache: choosers\r\n", + "INFO - Running trip_destination_simulate with 85 trips\r\n", + "INFO - trip_destination.trip_num_3.work.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 85 choosers and 16150 alternatives\r\n", + "INFO - Running chunk 1 of 1 with 85 of 85 choosers\r\n", + "INFO - Running eval_interaction_utilities on 16150 rows\r\n", + "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.work.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", + "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", + "DEBUG - trip_destination: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_destination: write_table cache: override_choices\r\n", + "INFO - Time to execute trip_destination.trip_num_3.work.trip_destination_simulate : 0.63 seconds (0.0 minutes)\r\n", + "DEBUG - trip_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", + "DEBUG - trip_destination: write_omnibus_choosers: output/estimation_data_bundle/trip_destination/trip_destination_choosers_combined.csv\r\n", + "DEBUG - trip_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", + "WARNING - trip_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", + "DEBUG - trip_destination: write_omnibus_choosers: output/estimation_data_bundle/trip_destination/trip_destination_alternatives_combined.csv\r\n", + "INFO - trip_destination: end estimation\r\n", + "Time to execute step 'trip_destination': 141.72 s\r\n", + "Total time to execute iteration 1 with iteration value None: 141.72 s\r\n", + "INFO - #run_model running step trip_scheduling\r\n", + "Running step 'trip_scheduling'\r\n", + "INFO - Initialize Estimator for'trip_scheduling'\r\n", + "DEBUG - trip_scheduling: estimate.write_spec: output/estimation_data_bundle/trip_scheduling/trip_scheduling_PROBS_SPEC.csv\r\n", + "DEBUG - trip_scheduling: write_table cache: choosers\r\n", + "INFO - trip_scheduling Running chunk 1 of 1 with 5778 of 5778 choosers\r\n", + "INFO - trip_scheduling.i1 scheduling 14352 trips within chunk 1\r\n", + "INFO - trip_scheduling.i1 236 failed\r\n", + "INFO - trip_scheduling.i2 scheduling 686 trips within chunk 1\r\n", + "INFO - trip_scheduling.i2 388 failed\r\n", + "INFO - trip_scheduling.i3 scheduling 622 trips within chunk 1\r\n", + "INFO - trip_scheduling.i3 376 failed\r\n", + "INFO - trip_scheduling.i4 scheduling 585 trips within chunk 1\r\n", + "INFO - trip_scheduling.i4 373 failed\r\n", + "INFO - trip_scheduling.i5 scheduling 563 trips within chunk 1\r\n", + "INFO - trip_scheduling.i5 370 failed\r\n", + "INFO - trip_scheduling.i6 scheduling 553 trips within chunk 1\r\n", + "INFO - trip_scheduling.i6 367 failed\r\n", + "INFO - trip_scheduling.i7 scheduling 546 trips within chunk 1\r\n", + "INFO - trip_scheduling.i7 367 failed\r\n", + "INFO - trip_scheduling.i8 scheduling 546 trips within chunk 1\r\n", + "INFO - trip_scheduling.i8 366 failed\r\n", + "INFO - trip_scheduling.i9 scheduling 542 trips within chunk 1\r\n", + "INFO - trip_scheduling.i9 365 failed\r\n", + "INFO - trip_scheduling.i10 scheduling 542 trips within chunk 1\r\n", + "INFO - trip_scheduling.i10 366 failed\r\n", + "INFO - trip_scheduling.i11 scheduling 542 trips within chunk 1\r\n", + "INFO - trip_scheduling.i11 363 failed\r\n", + "INFO - trip_scheduling.i12 scheduling 539 trips within chunk 1\r\n", + "INFO - trip_scheduling.i12 366 failed\r\n", + "INFO - trip_scheduling.i13 scheduling 539 trips within chunk 1\r\n", + "INFO - trip_scheduling.i13 364 failed\r\n", + "INFO - trip_scheduling.i14 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i14 361 failed\r\n", + "INFO - trip_scheduling.i15 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i15 363 failed\r\n", + "INFO - trip_scheduling.i16 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i16 364 failed\r\n", + "INFO - trip_scheduling.i17 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i17 365 failed\r\n", + "INFO - trip_scheduling.i18 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i18 363 failed\r\n", + "INFO - trip_scheduling.i19 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i19 363 failed\r\n", + "INFO - trip_scheduling.i20 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i20 362 failed\r\n", + "INFO - trip_scheduling.i21 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i21 365 failed\r\n", + "INFO - trip_scheduling.i22 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i22 363 failed\r\n", + "INFO - trip_scheduling.i23 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i23 365 failed\r\n", + "INFO - trip_scheduling.i24 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i24 365 failed\r\n", + "INFO - trip_scheduling.i25 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i25 363 failed\r\n", + "INFO - trip_scheduling.i26 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i26 364 failed\r\n", + "INFO - trip_scheduling.i27 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i27 365 failed\r\n", + "INFO - trip_scheduling.i28 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i28 365 failed\r\n", + "INFO - trip_scheduling.i29 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i29 363 failed\r\n", + "INFO - trip_scheduling.i30 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i30 363 failed\r\n", + "INFO - trip_scheduling.i31 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i31 365 failed\r\n", + "INFO - trip_scheduling.i32 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i32 364 failed\r\n", + "INFO - trip_scheduling.i33 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i33 364 failed\r\n", + "INFO - trip_scheduling.i34 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i34 364 failed\r\n", + "INFO - trip_scheduling.i35 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i35 363 failed\r\n", + "INFO - trip_scheduling.i36 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i36 363 failed\r\n", + "INFO - trip_scheduling.i37 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i37 364 failed\r\n", + "INFO - trip_scheduling.i38 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i38 364 failed\r\n", + "INFO - trip_scheduling.i39 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i39 364 failed\r\n", + "INFO - trip_scheduling.i40 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i40 365 failed\r\n", + "INFO - trip_scheduling.i41 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i41 365 failed\r\n", + "INFO - trip_scheduling.i42 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i42 365 failed\r\n", + "INFO - trip_scheduling.i43 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i43 366 failed\r\n", + "INFO - trip_scheduling.i44 scheduling 536 trips within chunk 1\r\n", + "INFO - trip_scheduling.i44 363 failed\r\n", + "INFO - trip_scheduling.i45 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i45 363 failed\r\n", + "INFO - trip_scheduling.i46 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i46 362 failed\r\n", + "INFO - trip_scheduling.i47 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i47 363 failed\r\n", + "INFO - trip_scheduling.i48 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i48 364 failed\r\n", + "INFO - trip_scheduling.i49 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i49 362 failed\r\n", + "INFO - trip_scheduling.i50 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i50 363 failed\r\n", + "INFO - trip_scheduling.i51 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i51 363 failed\r\n", + "INFO - trip_scheduling.i52 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i52 363 failed\r\n", + "INFO - trip_scheduling.i53 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i53 363 failed\r\n", + "INFO - trip_scheduling.i54 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i54 362 failed\r\n", + "INFO - trip_scheduling.i55 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i55 363 failed\r\n", + "INFO - trip_scheduling.i56 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i56 364 failed\r\n", + "INFO - trip_scheduling.i57 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i57 364 failed\r\n", + "INFO - trip_scheduling.i58 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i58 363 failed\r\n", + "INFO - trip_scheduling.i59 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i59 364 failed\r\n", + "INFO - trip_scheduling.i60 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i60 362 failed\r\n", + "INFO - trip_scheduling.i61 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i61 365 failed\r\n", + "INFO - trip_scheduling.i62 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i62 361 failed\r\n", + "INFO - trip_scheduling.i63 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i63 362 failed\r\n", + "INFO - trip_scheduling.i64 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i64 364 failed\r\n", + "INFO - trip_scheduling.i65 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i65 363 failed\r\n", + "INFO - trip_scheduling.i66 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i66 364 failed\r\n", + "INFO - trip_scheduling.i67 scheduling 533 trips within chunk 1\r\n", + "INFO - trip_scheduling.i67 363 failed\r\n", + "INFO - trip_scheduling.i68 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i68 364 failed\r\n", + "INFO - trip_scheduling.i69 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i69 362 failed\r\n", + "INFO - trip_scheduling.i70 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i70 360 failed\r\n", + "INFO - trip_scheduling.i71 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i71 361 failed\r\n", + "INFO - trip_scheduling.i72 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i72 363 failed\r\n", + "INFO - trip_scheduling.i73 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i73 363 failed\r\n", + "INFO - trip_scheduling.i74 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i74 363 failed\r\n", + "INFO - trip_scheduling.i75 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i75 362 failed\r\n", + "INFO - trip_scheduling.i76 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i76 362 failed\r\n", + "INFO - trip_scheduling.i77 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i77 363 failed\r\n", + "INFO - trip_scheduling.i78 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i78 361 failed\r\n", + "INFO - trip_scheduling.i79 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i79 363 failed\r\n", + "INFO - trip_scheduling.i80 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i80 361 failed\r\n", + "INFO - trip_scheduling.i81 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i81 363 failed\r\n", + "INFO - trip_scheduling.i82 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i82 362 failed\r\n", + "INFO - trip_scheduling.i83 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i83 363 failed\r\n", + "INFO - trip_scheduling.i84 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i84 363 failed\r\n", + "INFO - trip_scheduling.i85 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i85 362 failed\r\n", + "INFO - trip_scheduling.i86 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i86 358 failed\r\n", + "INFO - trip_scheduling.i87 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i87 361 failed\r\n", + "INFO - trip_scheduling.i88 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i88 361 failed\r\n", + "INFO - trip_scheduling.i89 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i89 362 failed\r\n", + "INFO - trip_scheduling.i90 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i90 362 failed\r\n", + "INFO - trip_scheduling.i91 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i91 362 failed\r\n", + "INFO - trip_scheduling.i92 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i92 362 failed\r\n", + "INFO - trip_scheduling.i93 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i93 362 failed\r\n", + "INFO - trip_scheduling.i94 scheduling 530 trips within chunk 1\r\n", + "INFO - trip_scheduling.i94 361 failed\r\n", + "INFO - trip_scheduling.i95 scheduling 526 trips within chunk 1\r\n", + "INFO - trip_scheduling.i95 360 failed\r\n", + "INFO - trip_scheduling.i96 scheduling 526 trips within chunk 1\r\n", + "INFO - trip_scheduling.i96 359 failed\r\n", + "INFO - trip_scheduling.i97 scheduling 526 trips within chunk 1\r\n", + "INFO - trip_scheduling.i97 359 failed\r\n", + "INFO - trip_scheduling.i98 scheduling 526 trips within chunk 1\r\n", + "INFO - trip_scheduling.i98 359 failed\r\n", + "INFO - trip_scheduling.i99 scheduling 526 trips within chunk 1\r\n", + "INFO - trip_scheduling.i99 359 failed\r\n", + "INFO - trip_scheduling.i100 scheduling 526 trips within chunk 1\r\n", + "INFO - dumping trip_scheduling.i100.outbound.num_2.failed_choosers\r\n", + "WARNING - trip_scheduling.i100.outbound.num_2 coercing 2 depart choices to most initial\r\n", + "INFO - dumping trip_scheduling.i100.outbound.num_3.failed_choosers\r\n", + "WARNING - trip_scheduling.i100.outbound.num_3 coercing 11 depart choices to most initial\r\n", + "INFO - dumping trip_scheduling.i100.outbound.num_4.failed_choosers\r\n", + "WARNING - trip_scheduling.i100.outbound.num_4 coercing 9 depart choices to most initial\r\n", + "INFO - dumping trip_scheduling.i100.inbound.num_1.failed_choosers\r\n", + "WARNING - trip_scheduling.i100.inbound.num_1 coercing 133 depart choices to most initial\r\n", + "INFO - dumping trip_scheduling.i100.inbound.num_2.failed_choosers\r\n", + "WARNING - trip_scheduling.i100.inbound.num_2 coercing 133 depart choices to most initial\r\n", + "INFO - dumping trip_scheduling.i100.inbound.num_3.failed_choosers\r\n", + "WARNING - trip_scheduling.i100.inbound.num_3 coercing 73 depart choices to most initial\r\n", + "INFO - trip_scheduling.i100 0 failed\r\n", + "DEBUG - trip_scheduling: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_scheduling: write_table cache: override_choices\r\n", + "DEBUG - trip_scheduling: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'choosers', 'probs']\r\n", + "WARNING - trip_scheduling: write_omnibus_table: values_combined table 'probs' not found\r\n", + "DEBUG - trip_scheduling: write_omnibus_choosers: output/estimation_data_bundle/trip_scheduling/trip_scheduling_values_combined.csv\r\n", + "INFO - trip_scheduling: end estimation\r\n", + "Time to execute step 'trip_scheduling': 19.54 s\r\n", + "Total time to execute iteration 1 with iteration value None: 19.54 s\r\n", + "INFO - #run_model running step trip_mode_choice\r\n", + "Running step 'trip_mode_choice'\r\n", + "INFO - Running trip_mode_choice with 14352 trips\r\n", + "INFO - primary_purpose top 10 value counts:\r\n", + "work 5428\r\n", + "shopping 1876\r\n", + "othdiscr 1413\r\n", + "school 1081\r\n", + "atwork 1073\r\n", + "othmaint 975\r\n", + "escort 967\r\n", + "eatout 713\r\n", + "social 423\r\n", + "univ 403\r\n", + "Name: primary_purpose, dtype: int64\r\n", + "INFO - Initialize Estimator for'trip_mode_choice'\r\n", + "DEBUG - trip_mode_choice: write_table write: trip_mode_choice_coefficients.csv\r\n", + "DEBUG - trip_mode_choice: write_table write: coefficients_template\r\n", + "DEBUG - trip_mode_choice: estimate.write_spec: output/estimation_data_bundle/trip_mode_choice/trip_mode_choice_SPEC.csv\r\n", + "INFO - trip_mode_choice tour_type 'atwork' (1073 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.atwork.simple_simulate Running adaptive_chunked_choosers with 1073 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1073 of 1073 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'eatout' (713 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.eatout.simple_simulate Running adaptive_chunked_choosers with 713 choosers\r\n", + "INFO - Running chunk 1 of 1 with 713 of 713 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'escort' (967 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.escort.simple_simulate Running adaptive_chunked_choosers with 967 choosers\r\n", + "INFO - Running chunk 1 of 1 with 967 of 967 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'othdiscr' (1413 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.othdiscr.simple_simulate Running adaptive_chunked_choosers with 1413 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1413 of 1413 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'othmaint' (975 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.othmaint.simple_simulate Running adaptive_chunked_choosers with 975 choosers\r\n", + "INFO - Running chunk 1 of 1 with 975 of 975 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'school' (1081 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.school.simple_simulate Running adaptive_chunked_choosers with 1081 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1081 of 1081 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'shopping' (1876 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.shopping.simple_simulate Running adaptive_chunked_choosers with 1876 choosers\r\n", + "INFO - Running chunk 1 of 1 with 1876 of 1876 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'social' (423 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.social.simple_simulate Running adaptive_chunked_choosers with 423 choosers\r\n", + "INFO - Running chunk 1 of 1 with 423 of 423 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'univ' (403 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.univ.simple_simulate Running adaptive_chunked_choosers with 403 choosers\r\n", + "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "INFO - trip_mode_choice tour_type 'work' (5428 trips)\r\n", + "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", + "INFO - trip_mode_choice.work.simple_simulate Running adaptive_chunked_choosers with 5428 choosers\r\n", + "INFO - Running chunk 1 of 1 with 5428 of 5428 choosers\r\n", + "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", + "DEBUG - trip_mode_choice: write_table cache: choices\r\n", + "DEBUG - get_survey_values: reindexing using trips.index\r\n", + "DEBUG - trip_mode_choice: write_table cache: override_choices\r\n", + "DEBUG - trip_mode_choice: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", + "DEBUG - trip_mode_choice: write_omnibus_choosers: output/estimation_data_bundle/trip_mode_choice/trip_mode_choice_values_combined.csv\r\n", + "INFO - trip_mode_choice: end estimation\r\n", + "INFO - trip_modes top 10 value counts:\r\n", + "DRIVEALONEFREE 3864\r\n", + "SHARED2FREE 2283\r\n", + "SHARED3FREE 1921\r\n", + "WALK_LOC 1524\r\n", + "WALK_LRF 1427\r\n", + "WALK 1259\r\n", + "TNC_SINGLE 871\r\n", + "WALK_HVY 473\r\n", + "BIKE 469\r\n", + "TNC_SHARED 149\r\n", + "Name: tour_mode, dtype: int64\r\n", + "INFO - trip_mode_choice choices top 10 value counts:\r\n", + "DRIVEALONEFREE 4317\r\n", + "SHARED2FREE 1872\r\n", + "WALK_LOC 1828\r\n", + "WALK 1621\r\n", + "SHARED3FREE 1345\r\n", + "WALK_LRF 1189\r\n", + "TNC_SINGLE 1038\r\n", + "BIKE 434\r\n", + "TNC_SHARED 361\r\n", + "WALK_HVY 242\r\n", + "Name: trip_mode, dtype: int64\r\n", + "Time to execute step 'trip_mode_choice': 18.31 s\r\n", + "Total time to execute iteration 1 with iteration value None: 18.31 s\r\n", + "INFO - #run_model running step write_tables\r\n", + "Running step 'write_tables'\r\n", + "Time to execute step 'write_tables': 0.41 s\r\n", + "Total time to execute iteration 1 with iteration value None: 0.41 s\r\n", + "INFO - Time to execute run_model (29 models) : 713.015 seconds (11.9 minutes)\r\n", + "INFO - MainProcess high water mark rss: 1_529_487_360 (1.5 GB) timestamp: 09/05/2022 14:57:08 label:pipeline.run_model trip_destination finished\r\n", + "INFO - MainProcess high water mark uss: 0 (0 B) timestamp: 09/05/2022 14:45:55 label:pipeline.run before preload_injectables\r\n", + "INFO - Time to execute all models : 713.145 seconds (11.9 minutes)\r\n" + ] + } + ], + "source": [ + "# run estimation mode TODO: smaller data set?\n", + "!activitysim run -c configs_estimation/configs -c configs -o output -d data_sf" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 22, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "req_data does not request avail_ca or avail_co but it is set and being provided\n", + "problem: chosen-but-not-available (2 issues)\n", + "problem: low-variance-data-co (1 issues)\n", + "problem: chosen-but-not-available (2 issues)\n", + "problem: low-variance-data-co (1 issues)\n", + "problem: chosen-but-not-available (1 issues)\n", + "problem: low-variance-data-co (1 issues)\n", + "problem: chosen-but-not-available (2 issues)\n", + "problem: low-variance-data-co (1 issues)\n", + "problem: chosen-but-not-available (1 issues)\n", + "problem: low-variance-data-co (1 issues)\n", + "problem: chosen-but-not-available (2 issues)\n", + "problem: low-variance-data-co (1 issues)\n", + "problem: chosen-but-not-available (1 issues)\n", + "problem: low-variance-data-co (1 issues)\n", + "problem: chosen-but-not-available (2 issues)\n", + "problem: low-variance-data-co (1 issues)\n" + ] + }, + { + "data": { + "text/plain": "-10094.898223413013" + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "modelname = \"trip_mode_choice\"\n", + "\n", + "from activitysim.estimation.larch import component_model\n", + "model, data = component_model(modelname, return_data=True)\n", + "# data.coefficients\n", + "# data.spec\n", + "# data.chooser_data\n", + "model.load_data()\n", + "model.doctor(repair_ch_av='-')\n", + "model.loglike()\n", + "# DO NOT ESTIMATE just apply probs\n", + "# model.maximize_loglike(method='SLSQP', options={\"maxiter\": 1000})\n", + "probs = [m.probability() for m in model._k_models]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 124, + "outputs": [], + "source": [ + "probs_0 = pd.DataFrame(probs[0])\n", + "m_0 = model._k_models[0]\n", + "\n", + "probs_0.index = m_0.dataframes.data_ch.index" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 110, + "outputs": [ + { + "data": { + "text/plain": "(2124,)" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": "(2124,)" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(m_0.dataframes.data_ch.index.unique().shape)\n", + "display(np.intersect1d(data.chooser_data.index.values, m_0.dataframes.data_ch.index.values).shape)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 111, + "outputs": [], + "source": [ + "#data.chooser_data[['override_choice', 'override_choice_code']].drop_duplicates().reset_index(drop=True).set_index\\\n", + "# ('override_choice_code').to_dict()['override_choice']\n", + "mode_map = pd.DataFrame.from_dict(data.settings[\"CONSTANTS\"][\"I_MODE_MAP\"], orient='index', columns=[\"mode_code\"])\\\n", + " .reset_index().rename(columns={\"index\": \"mode_name\"})" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 127, + "outputs": [ + { + "data": { + "text/plain": " trip_id model_choice override_choice \\\ntour_id \n3021985 24175881 DRIVEALONEFREE DRIVEALONEFREE \n3021985 24175885 DRIVEALONEFREE DRIVEALONEFREE \n\n util_DRIVEALONEFREE_Unavailable \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_zero_auto_households \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_persons_less_than_16 \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_joint_tours \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_work \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_In_vehicle_time \\\ntour_id \n3021985 11.50 \n3021985 11.81 \n\n util_DRIVEALONEFREE_Terminal_time ... \\\ntour_id ... \n3021985 4.14944 ... \n3021985 4.14944 ... \n\n drive_heavyrail_available_outbound \\\ntour_id \n3021985 False \n3021985 False \n\n drive_heavyrail_available_inbound drive_commuter_available_outbound \\\ntour_id \n3021985 False False \n3021985 False False \n\n drive_commuter_available_inbound walk_ferry_available \\\ntour_id \n3021985 False False \n3021985 False False \n\n drive_ferry_available distance distance_walk_od distance_bike_od \\\ntour_id \n3021985 False 5.96 5.99 5.99 \n3021985 False 5.99 5.99 5.99 \n\n override_choice_code \ntour_id \n3021985 1 \n3021985 1 \n\n[2 rows x 478 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_idmodel_choiceoverride_choiceutil_DRIVEALONEFREE_Unavailableutil_DRIVEALONEFREE_Unavailable_for_zero_auto_householdsutil_DRIVEALONEFREE_Unavailable_for_persons_less_than_16util_DRIVEALONEFREE_Unavailable_for_joint_toursutil_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_workutil_DRIVEALONEFREE_In_vehicle_timeutil_DRIVEALONEFREE_Terminal_time...drive_heavyrail_available_outbounddrive_heavyrail_available_inbounddrive_commuter_available_outbounddrive_commuter_available_inboundwalk_ferry_availabledrive_ferry_availabledistancedistance_walk_oddistance_bike_odoverride_choice_code
tour_id
302198524175881DRIVEALONEFREEDRIVEALONEFREE0.00.00.00.00.011.504.14944...FalseFalseFalseFalseFalseFalse5.965.995.991
302198524175885DRIVEALONEFREEDRIVEALONEFREE0.00.00.00.00.011.814.14944...FalseFalseFalseFalseFalseFalse5.995.995.991
\n

2 rows × 478 columns

\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": " 1 2 3 4 5 6 7 8 9 10 ... 12 13 \\\n_caseid_ ... \n3021985 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 \n3021985 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 \n\n 14 15 16 17 18 19 20 21 \n_caseid_ \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n\n[2 rows x 21 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
12345678910...12131415161718192021
_caseid_
30219851.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
30219851.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
\n

2 rows × 21 columns

\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": " 0 1 2 3 4 5 6 7 8 9 ... \\\n_caseid_ ... \n3021985 0.999871 0.0 0.0 0.0 0.0 0.0 0.000006 0.0 0.0 0.0 ... \n3021985 0.999886 0.0 0.0 0.0 0.0 0.0 0.000006 0.0 0.0 0.0 ... \n\n 11 12 13 14 15 16 17 18 19 20 \n_caseid_ \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.368530e-06 0.000021 0.000100 \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 3.692194e-07 0.000011 0.000096 \n\n[2 rows x 21 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
0123456789...11121314151617181920
_caseid_
30219850.9998710.00.00.00.00.00.0000060.00.00.0...0.00.00.00.00.00.00.01.368530e-060.0000210.000100
30219850.9998860.00.00.00.00.00.0000060.00.00.0...0.00.00.00.00.00.00.03.692194e-070.0000110.000096
\n

2 rows × 21 columns

\n
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tour_id_to_check = 3021985\n", + "\n", + "display(data.chooser_data.loc[data.chooser_data.index == tour_id_to_check])\n", + "display(m_0.dataframes.data_ch.loc[m_0.dataframes.data_ch.index == tour_id_to_check])\n", + "display(probs_0.loc[probs_0.index == tour_id_to_check])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## now use my code to calculate probabilities" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 130f805135..84605dcd55 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -5,7 +5,11 @@ "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", "metadata": {}, "source": [ - "# validate results" + "# validate results\n", + "\n", + "## TODO\n", + "what happened to tracing when I fixed probability calcs - it seems like all trip ids are attached when I add one by\n", + "hand below - why?" ] }, { @@ -424,7 +428,7 @@ "\n", "choose_individual_max_utility = True\n", "ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", - "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" + "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" ], "metadata": { "collapsed": false, @@ -449,8 +453,7 @@ "cell_type": "markdown", "source": [ "## try to improve runtime of apply\n", - "\n", - "try caching {nest_name: nest alternatives}\n" + "\n" ], "metadata": { "collapsed": false, @@ -564,6 +567,7 @@ "execution_count": 242, "outputs": [], "source": [ + "%%prun\n", "all_alternatives = set(nest.name for nest in logit.each_nest(nest_spec, type='leaf'))\n", "logit_nest_groups = group_nests_by_name(nest_spec)\n", "nest_alternatives_by_name = {n.name: n.alternatives for n in logit.each_nest(nest_spec)}\n", From e02b46a8c93451edab1c3d5c44bdb6b5258b38e8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 10 May 2022 00:35:23 +1000 Subject: [PATCH 023/298] compare to larch probs --- notebooks/check_sf_probs.ipynb | 281 ++ notebooks/fru_utils.py | 72 +- .../larch_probabilities_via_estimation.ipynb | 2848 +---------------- notebooks/validate_frozen_impl.ipynb | 173 +- 4 files changed, 530 insertions(+), 2844 deletions(-) create mode 100644 notebooks/check_sf_probs.ipynb diff --git a/notebooks/check_sf_probs.ipynb b/notebooks/check_sf_probs.ipynb new file mode 100644 index 0000000000..de5d82d521 --- /dev/null +++ b/notebooks/check_sf_probs.ipynb @@ -0,0 +1,281 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", + "metadata": {}, + "source": [ + "# Compare results against larch\n", + "\n", + "We use estimation data as prepared by larch and draw the Asim way and our way, then compare to larch probabilities.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.385153Z", + "iopub.status.busy": "2022-05-01T10:17:57.384881Z", + "iopub.status.idle": "2022-05-01T10:17:57.534433Z", + "shell.execute_reply": "2022-05-01T10:17:57.533096Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%load_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3dba451-1e10-403e-8614-35d57e6577f4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.536623Z", + "iopub.status.busy": "2022-05-01T10:17:57.536012Z", + "iopub.status.idle": "2022-05-01T10:17:57.542755Z", + "shell.execute_reply": "2022-05-01T10:17:57.541685Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:00.350944Z", + "iopub.status.busy": "2022-05-01T10:18:00.350730Z", + "iopub.status.idle": "2022-05-01T10:18:12.760977Z", + "shell.execute_reply": "2022-05-01T10:18:12.760013Z", + "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import argparse\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from activitysim.cli import run\n", + "from activitysim.core import inject\n", + "\n", + "from fru_utils import mode_choice_for_trip\n", + "\n", + "pd.set_option(\"max_columns\", 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:17.839947Z", + "iopub.status.busy": "2022-05-01T10:18:17.839070Z", + "iopub.status.idle": "2022-05-01T10:18:18.019676Z", + "shell.execute_reply": "2022-05-01T10:18:18.018689Z", + "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_example_sf\")\n", + "os.chdir(example_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "outputs": [], + "source": [ + "#!activitysim run -c configs -o output -d data_sf" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:22.111723Z", + "iopub.status.busy": "2022-05-01T10:18:22.111490Z", + "iopub.status.idle": "2022-05-01T10:18:22.297437Z", + "shell.execute_reply": "2022-05-01T10:18:22.296501Z", + "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "run.add_run_args(parser)\n", + "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data_sf'])\n", + "if not inject.is_injectable('preload_injectables'):\n", + " from activitysim import abm # register abm steps and other abm-specific injectables\n", + "run.handle_standard_args(args) # possibly update injectables" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "outputs": [], + "source": [ + "larch_chooser_data = pd.read_csv(os.path.join(root_dir, \"test_estimation_sf\", \"choosers_larch.csv\"))\n", + "larch_probs = pd.read_csv(os.path.join(root_dir, \"test_estimation_sf\", \"probabilities_larch.csv\"), index_col=0)\n", + "# __caseids__ are tour ids, with trips in same order\n", + "assert (larch_probs.index.values == larch_chooser_data.tour_id).all()\n", + "larch_probs.index = larch_chooser_data.trip_id" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 50, + "outputs": [], + "source": [ + "larch_tours = pd.read_csv(os.path.join(root_dir, \"test_estimation_sf\", \"output\", \"estimation_data_bundle\",\n", + " \"trip_mode_choice\", \"trip_mode_choice_values_combined.csv\"))\n", + "trips_merged_cols = ['person_id', 'household_id', 'primary_purpose', 'trip_num', 'outbound',\n", + " 'trip_count', 'destination', 'origin', 'tour_id', 'purpose',\n", + " 'destination_logsum', 'depart', 'hhsize', 'age', 'auto_ownership',\n", + " 'number_of_participants', 'tour_category', 'parent_tour_id',\n", + " 'tour_mode', 'duration', 'value_of_time', 'tour_type',\n", + " 'free_parking_at_work', 'trip_period', \"trip_id\"]\n", + "\n", + "larch_trips_merged = larch_tours[trips_merged_cols].set_index(\"trip_id\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 87, + "outputs": [], + "source": [ + "def compare_mode_share_simulation(trip_id_to_check, num_samples, larch_trips_merged, larch_probs):\n", + " t_, c_, ns_ = mode_choice_for_trip(choose_individual_max_utility=True, trip_id_to_check=trip_id_to_check,\n", + " num_samples=num_samples, trips_merged=larch_trips_merged)\n", + " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", + "\n", + " t_asim, c_asim, _ = mode_choice_for_trip(choose_individual_max_utility=False, trip_id_to_check=trip_id_to_check,\n", + " num_samples=num_samples, trips_merged=larch_trips_merged)\n", + " asim_mode_shares = c_asim.trip_mode.value_counts() / c_asim.shape[0]\n", + "\n", + " larch_mode_shares = larch_probs.loc[larch_probs.index == trip_id_to_check].T\n", + " larch_mode_shares.columns = [\"larch\"]\n", + "\n", + " comp = sim_mode_shares.to_frame(\"me\")\\\n", + " .merge(asim_mode_shares.to_frame(\"asim\"), left_index=True, right_index=True,how=\"outer\")\\\n", + " .merge(larch_mode_shares,left_index=True, right_index=True, how=\"outer\").fillna(0)\n", + "\n", + " return comp" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'work' (10000 trips)\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "trip_id_to_check = 23695361\n", + "num_samples = 10000\n", + "\n", + "comp = compare_mode_share_simulation(trip_id_to_check, num_samples, larch_trips_merged, larch_probs)\n", + "comp.loc[(comp != 0).any(axis=1), :].applymap(lambda x: f\"{x:.3%}\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n", + "is_executing": true + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py index 0f13679b52..d466184af0 100644 --- a/notebooks/fru_utils.py +++ b/notebooks/fru_utils.py @@ -17,7 +17,9 @@ -def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples): + + +def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples, trips_merged=None): """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" resume_after = "trip_scheduling" model_name = "trip_mode_choice" @@ -32,8 +34,6 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa #checkpoint = pipeline.intermediate_checkpoint(model_name) inject.set_step_args(args) - trips = inject.get_table('trips') - tours_merged = inject.get_table('tours_merged') network_los = inject.get_injectable('network_los') trace_label = 'trip_mode_choice' @@ -43,24 +43,21 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME') mode_column_name = 'trip_mode' - trips_df = trips.to_frame() - print("Running with %d trips", trips_df.shape[0]) - - tours_merged = tours_merged.to_frame() - tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] - - # - trips_merged - merge trips and tours_merged - trips_merged = pd.merge( - trips_df, - tours_merged, - left_on='tour_id', - right_index=True, - how="left") - assert trips_merged.index.equals(trips.index) - - # setup skim keys - assert ('trip_period' not in trips_merged) - trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) + if trips_merged is None: + trips = inject.get_table('trips') + tours_merged = inject.get_table('tours_merged') + trips_df = trips.to_frame() + tours_merged = tours_merged.to_frame() + tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] + trips_merged = pd.merge( + trips_df, + tours_merged, + left_on='tour_id', + right_index=True, + how="left") + assert trips_merged.index.equals(trips.index) + assert ('trip_period' not in trips_merged) + trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) orig_col = 'origin' dest_col = 'destination' @@ -91,9 +88,6 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa estimator = estimation.manager.begin_estimation('trip_mode_choice') - #choices_list = [] - - # grab one, duplicate num_samples times trips_segment = trips_merged.loc[trips_merged.index == trip_id_to_check].copy() primary_purpose = trips_segment['primary_purpose'].values[0] @@ -103,23 +97,12 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa existing_indexes = pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values num_new_indexes = trips_segment.shape[0] new_indexes = np.arange(existing_indexes.max()+1, existing_indexes.max() + num_new_indexes + 1) - - trips_segment.index = new_indexes #+= np.arange(num_samples) + trips_segment.index = new_indexes # name index so tracing knows how to slice trips_segment.index.name = 'trip_id' - - # #logger.warning("Change seeding back when done with testing") - # pipeline._PIPELINE.rng.row_states = pd.DataFrame(columns=['row_seed', 'offset'], index=trips_segment.index) - # pipeline._PIPELINE.rng.row_states["row_seed"] = trips_segment.index.values - # pipeline._PIPELINE.rng.row_states["offset"] = 0 pipeline._PIPELINE.rng().add_channel("trips", trips_segment) - - #for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'): - #if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes): - # continue - print("trip_mode_choice tour_type '%s' (%s trips)" % (primary_purpose, len(trips_segment.index), )) @@ -163,18 +146,6 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa if not choose_individual_max_utility: alts = spec.columns choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) - ################ - #choices_list.append(choices) - #choices_df_asim = pd.concat(choices_list) - - # update trips table with choices (and potionally logssums) - #trips_df = trips_merged.copy() # trips.to_frame() - - #if (do_these_purposes is not None): - # trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)] - - #assign_in_place(trips_df, choices) - #assert not trips_df[mode_column_name].isnull().any() finalise = True if finalise: @@ -188,11 +159,11 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa pipeline.close_pipeline() print("Done") - return trips_merged, choices + return trips_merged, choices, nest_spec def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check): - t_, c_ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, + t_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, trip_id_to_check=trip_id_to_check, num_samples=num_samples) sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0] @@ -208,7 +179,6 @@ def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, tri return ms_comp - def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=True): """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" diff --git a/notebooks/larch_probabilities_via_estimation.ipynb b/notebooks/larch_probabilities_via_estimation.ipynb index 0bb5f0a04d..db362e994c 100644 --- a/notebooks/larch_probabilities_via_estimation.ipynb +++ b/notebooks/larch_probabilities_via_estimation.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 1, "outputs": [], "source": [ "import os\n", @@ -47,7 +47,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 2, "outputs": [], "source": [ "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 3, "outputs": [], "source": [ "# create estimation test example\n", @@ -78,2745 +78,11 @@ }, { "cell_type": "code", - "execution_count": 13, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Configured logging using basicConfig\r\n", - "INFO:activitysim:Configured logging using basicConfig\r\n", - "INFO - Read logging configuration from: configs_estimation/configs/logging.yaml\r\n", - "INFO - SETTING configs_dir: ['configs_estimation/configs', 'configs']\r\n", - "INFO - SETTING settings_file_name: settings.yaml\r\n", - "INFO - SETTING data_dir: ['data_sf']\r\n", - "INFO - SETTING output_dir: output\r\n", - "INFO - SETTING households_sample_size: 0\r\n", - "INFO - SETTING chunk_size: 0\r\n", - "INFO - SETTING chunk_method: hybrid_uss\r\n", - "INFO - SETTING chunk_training_mode: disabled\r\n", - "INFO - SETTING multiprocess: None\r\n", - "INFO - SETTING num_processes: None\r\n", - "INFO - SETTING resume_after: None\r\n", - "INFO - SETTING trace_hh_id: None\r\n", - "INFO - ENV MKL_NUM_THREADS: None\r\n", - "INFO - ENV OMP_NUM_THREADS: None\r\n", - "INFO - ENV OPENBLAS_NUM_THREADS: None\r\n", - "INFO - NUMPY blas_opt_info libraries: ['openblas', 'openblas']\r\n", - "INFO - NUMPY lapack_opt_info libraries: ['openblas', 'openblas']\r\n", - "INFO - run single process simulation\r\n", - "INFO - Time to execute open_pipeline : 0.029 seconds (0.0 minutes)\r\n", - "INFO - preload_injectables\r\n", - "INFO - Time to execute preload_injectables : 0.036 seconds (0.0 minutes)\r\n", - "INFO - #run_model running step initialize_landuse\r\n", - "Running step 'initialize_landuse'\r\n", - "INFO - Reading CSV file data_sf/land_use.csv\r\n", - "INFO - loaded land_use (190, 24)\r\n", - "INFO - initialize_landuse.annotate_tables - annotating land_use SPEC annotate_landuse\r\n", - "INFO - Network_LOS using skim_dict_factory: NumpyArraySkimFactory\r\n", - "INFO - allocate_skim_buffer shared False taz shape (826, 190, 190) total size: 119_274_400 (119.3 MB)\r\n", - "INFO - _read_skims_from_omx data_sf/skims.omx\r\n", - "INFO - _read_skims_from_omx loaded 826 skims from data_sf/skims.omx\r\n", - "INFO - writing skim cache taz (826, 190, 190) to output/cache/cached_taz.mmap\r\n", - "INFO - load_skims_to_buffer taz shape (826, 190, 190)\r\n", - "INFO - get_skim_data taz SkimData shape (826, 190, 190)\r\n", - "INFO - SkimDict init taz\r\n", - "INFO - SkimDict.build_3d_skim_block_offset_table registered 167 3d keys\r\n", - "Time to execute step 'initialize_landuse': 3.69 s\r\n", - "Total time to execute iteration 1 with iteration value None: 3.69 s\r\n", - "INFO - #run_model running step initialize_households\r\n", - "Running step 'initialize_households'\r\n", - "INFO - Reading CSV file data_sf/survey_data/override_households.csv\r\n", - "INFO - full household list contains 2000 households\r\n", - "INFO - loaded households (2000, 7)\r\n", - "INFO - Reading CSV file data_sf/survey_data/override_persons.csv\r\n", - "INFO - loaded persons (4405, 7)\r\n", - "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons\r\n", - "INFO - initialize_households.annotate_tables - annotating households SPEC annotate_households\r\n", - "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons_after_hh\r\n", - "Time to execute step 'initialize_households': 0.39 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.39 s\r\n", - "INFO - #run_model running step compute_accessibility\r\n", - "Running step 'compute_accessibility'\r\n", - "INFO - Running compute_accessibility with 190 orig zones 190 dest zones\r\n", - "INFO - compute_accessibility Running adaptive_chunked_choosers with 190 choosers\r\n", - "INFO - Running chunk 1 of 1 with 190 of 190 choosers\r\n", - "INFO - Running compute_accessibility with 190 orig zones 190 dest zones\r\n", - "INFO - compute_accessibility computed accessibilities (190, 10)\r\n", - "Time to execute step 'compute_accessibility': 0.12 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.12 s\r\n", - "INFO - #run_model running step school_location\r\n", - "Running step 'school_location'\r\n", - "INFO - Initialize Estimator for'school_location'\r\n", - "DEBUG - school_location: estimate.write_spec: output/estimation_data_bundle/school_location/school_location_SPEC.csv\r\n", - "DEBUG - school_location: write_table write: school_location_coefficients.csv\r\n", - "DEBUG - school_location: write_table write: size_terms\r\n", - "DEBUG - school_location: write_table write: landuse\r\n", - "INFO - Running school_location.i1.sample.university with 329 persons\r\n", - "INFO - Estimation mode for school_location.i1.sample.university using unsampled alternatives short_circuit_choices\r\n", - "INFO - school_location.i1.sample.university.interaction_sample Running adaptive_chunked_choosers with 329 choosers\r\n", - "INFO - Running chunk 1 of 1 with 329 of 329 choosers\r\n", - "INFO - Running eval_interaction_utilities on 5593 rows\r\n", - "INFO - Estimation mode for school_location.i1.sample.university.interaction_sample using unsampled alternatives\r\n", - "INFO - Running school_location.i1.logsums.university with 5593 rows\r\n", - "INFO - school_location.i1.logsums.university.compute_logsums Running adaptive_chunked_choosers with 5593 choosers\r\n", - "INFO - Running chunk 1 of 1 with 5593 of 5593 choosers\r\n", - "INFO - Running school_location.i1.simulate.university with 329 persons\r\n", - "DEBUG - school_location: write_table cache: choosers\r\n", - "DEBUG - school_location: write_table cache: interaction_sample_alternatives\r\n", - "INFO - school_location.i1.simulate.university.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 329 choosers and 5593 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 329 of 329 choosers\r\n", - "INFO - Running eval_interaction_utilities on 5593 rows\r\n", - "INFO - school_location: eval_interaction_utilities write_interaction_expression_values school_location.i1.simulate.university.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - school_location: write_table cache: interaction_expression_values\r\n", - "DEBUG - school_location: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - school_location: write_table cache: override_choices\r\n", - "INFO - Running school_location.i1.sample.highschool with 111 persons\r\n", - "INFO - Estimation mode for school_location.i1.sample.highschool using unsampled alternatives short_circuit_choices\r\n", - "INFO - school_location.i1.sample.highschool.interaction_sample Running adaptive_chunked_choosers with 111 choosers\r\n", - "INFO - Running chunk 1 of 1 with 111 of 111 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2775 rows\r\n", - "INFO - Estimation mode for school_location.i1.sample.highschool.interaction_sample using unsampled alternatives\r\n", - "INFO - Running school_location.i1.logsums.highschool with 2775 rows\r\n", - "INFO - school_location.i1.logsums.highschool.compute_logsums Running adaptive_chunked_choosers with 2775 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2775 of 2775 choosers\r\n", - "INFO - Running school_location.i1.simulate.highschool with 111 persons\r\n", - "DEBUG - school_location: write_table cache: choosers\r\n", - "DEBUG - school_location: write_table cache: interaction_sample_alternatives\r\n", - "INFO - school_location.i1.simulate.highschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 111 choosers and 2775 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 111 of 111 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2775 rows\r\n", - "INFO - school_location: eval_interaction_utilities write_interaction_expression_values school_location.i1.simulate.highschool.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - school_location: write_table cache: interaction_expression_values\r\n", - "DEBUG - school_location: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - school_location: write_table cache: override_choices\r\n", - "INFO - Running school_location.i1.sample.gradeschool with 544 persons\r\n", - "INFO - Estimation mode for school_location.i1.sample.gradeschool using unsampled alternatives short_circuit_choices\r\n", - "INFO - school_location.i1.sample.gradeschool.interaction_sample Running adaptive_chunked_choosers with 544 choosers\r\n", - "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", - "INFO - Running eval_interaction_utilities on 103360 rows\r\n", - "INFO - Estimation mode for school_location.i1.sample.gradeschool.interaction_sample using unsampled alternatives\r\n", - "INFO - Running school_location.i1.logsums.gradeschool with 103360 rows\r\n", - "INFO - school_location.i1.logsums.gradeschool.compute_logsums Running adaptive_chunked_choosers with 103360 choosers\r\n", - "INFO - Running chunk 1 of 1 with 103360 of 103360 choosers\r\n", - "INFO - Running school_location.i1.simulate.gradeschool with 544 persons\r\n", - "DEBUG - school_location: write_table cache: choosers\r\n", - "DEBUG - school_location: write_table cache: interaction_sample_alternatives\r\n", - "INFO - school_location.i1.simulate.gradeschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 544 choosers and 103360 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", - "INFO - Running eval_interaction_utilities on 103360 rows\r\n", - "INFO - school_location: eval_interaction_utilities write_interaction_expression_values school_location.i1.simulate.gradeschool.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - school_location: write_table cache: interaction_expression_values\r\n", - "DEBUG - school_location: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - school_location: write_table cache: override_choices\r\n", - "INFO - write_trace_files iteration 1\r\n", - "INFO - school_location_logsum top 10 value counts:\r\n", - "-1.824183 2\r\n", - "-0.909839 2\r\n", - "-1.366984 2\r\n", - "-1.278060 1\r\n", - "-1.180527 1\r\n", - "-1.674889 1\r\n", - "-1.291101 1\r\n", - "-1.813244 1\r\n", - "-1.069701 1\r\n", - " 0.053658 1\r\n", - "Name: logsum, dtype: int64\r\n", - "DEBUG - school_location: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - school_location: write_omnibus_choosers: output/estimation_data_bundle/school_location/school_location_choosers_combined.csv\r\n", - "DEBUG - school_location: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "DEBUG - school_location: write_omnibus_choosers: output/estimation_data_bundle/school_location/school_location_alternatives_combined.csv\r\n", - "INFO - school_location: end estimation\r\n", - "Time to execute step 'school_location': 15.20 s\r\n", - "Total time to execute iteration 1 with iteration value None: 15.21 s\r\n", - "INFO - #run_model running step workplace_location\r\n", - "Running step 'workplace_location'\r\n", - "INFO - Initialize Estimator for'workplace_location'\r\n", - "DEBUG - workplace_location: estimate.write_spec: output/estimation_data_bundle/workplace_location/workplace_location_SPEC.csv\r\n", - "DEBUG - workplace_location: write_table write: workplace_location_coefficients.csv\r\n", - "DEBUG - workplace_location: write_table write: size_terms\r\n", - "DEBUG - workplace_location: write_table write: landuse\r\n", - "INFO - Running workplace_location.i1.sample.work_low with 518 persons\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_low using unsampled alternatives short_circuit_choices\r\n", - "INFO - workplace_location.i1.sample.work_low.interaction_sample Running adaptive_chunked_choosers with 518 choosers\r\n", - "INFO - Running chunk 1 of 1 with 518 of 518 choosers\r\n", - "INFO - Running eval_interaction_utilities on 98420 rows\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_low.interaction_sample using unsampled alternatives\r\n", - "INFO - Running workplace_location.i1.logsums.work_low with 98420 rows\r\n", - "INFO - workplace_location.i1.logsums.work_low.compute_logsums Running adaptive_chunked_choosers with 98420 choosers\r\n", - "INFO - Running chunk 1 of 1 with 98420 of 98420 choosers\r\n", - "INFO - Running workplace_location.i1.simulate.work_low with 518 persons\r\n", - "DEBUG - workplace_location: write_table cache: choosers\r\n", - "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", - "INFO - workplace_location.i1.simulate.work_low.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 518 choosers and 98420 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 518 of 518 choosers\r\n", - "INFO - Running eval_interaction_utilities on 98420 rows\r\n", - "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_low.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", - "DEBUG - workplace_location: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - workplace_location: write_table cache: override_choices\r\n", - "INFO - Running workplace_location.i1.sample.work_med with 545 persons\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_med using unsampled alternatives short_circuit_choices\r\n", - "INFO - workplace_location.i1.sample.work_med.interaction_sample Running adaptive_chunked_choosers with 545 choosers\r\n", - "INFO - Running chunk 1 of 1 with 545 of 545 choosers\r\n", - "INFO - Running eval_interaction_utilities on 103550 rows\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_med.interaction_sample using unsampled alternatives\r\n", - "INFO - Running workplace_location.i1.logsums.work_med with 103550 rows\r\n", - "INFO - workplace_location.i1.logsums.work_med.compute_logsums Running adaptive_chunked_choosers with 103550 choosers\r\n", - "INFO - Running chunk 1 of 1 with 103550 of 103550 choosers\r\n", - "INFO - Running workplace_location.i1.simulate.work_med with 545 persons\r\n", - "DEBUG - workplace_location: write_table cache: choosers\r\n", - "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", - "INFO - workplace_location.i1.simulate.work_med.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 545 choosers and 103550 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 545 of 545 choosers\r\n", - "INFO - Running eval_interaction_utilities on 103550 rows\r\n", - "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_med.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", - "DEBUG - workplace_location: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - workplace_location: write_table cache: override_choices\r\n", - "INFO - Running workplace_location.i1.sample.work_high with 614 persons\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_high using unsampled alternatives short_circuit_choices\r\n", - "INFO - workplace_location.i1.sample.work_high.interaction_sample Running adaptive_chunked_choosers with 614 choosers\r\n", - "INFO - Running chunk 1 of 1 with 614 of 614 choosers\r\n", - "INFO - Running eval_interaction_utilities on 116660 rows\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_high.interaction_sample using unsampled alternatives\r\n", - "INFO - Running workplace_location.i1.logsums.work_high with 116660 rows\r\n", - "INFO - workplace_location.i1.logsums.work_high.compute_logsums Running adaptive_chunked_choosers with 116660 choosers\r\n", - "INFO - Running chunk 1 of 1 with 116660 of 116660 choosers\r\n", - "INFO - Running workplace_location.i1.simulate.work_high with 614 persons\r\n", - "DEBUG - workplace_location: write_table cache: choosers\r\n", - "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", - "INFO - workplace_location.i1.simulate.work_high.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 614 choosers and 116660 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 614 of 614 choosers\r\n", - "INFO - Running eval_interaction_utilities on 116660 rows\r\n", - "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_high.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", - "DEBUG - workplace_location: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - workplace_location: write_table cache: override_choices\r\n", - "INFO - Running workplace_location.i1.sample.work_veryhigh with 905 persons\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_veryhigh using unsampled alternatives short_circuit_choices\r\n", - "INFO - workplace_location.i1.sample.work_veryhigh.interaction_sample Running adaptive_chunked_choosers with 905 choosers\r\n", - "INFO - Running chunk 1 of 1 with 905 of 905 choosers\r\n", - "INFO - Running eval_interaction_utilities on 171950 rows\r\n", - "INFO - Estimation mode for workplace_location.i1.sample.work_veryhigh.interaction_sample using unsampled alternatives\r\n", - "INFO - Running workplace_location.i1.logsums.work_veryhigh with 171950 rows\r\n", - "INFO - workplace_location.i1.logsums.work_veryhigh.compute_logsums Running adaptive_chunked_choosers with 171950 choosers\r\n", - "INFO - Running chunk 1 of 1 with 171950 of 171950 choosers\r\n", - "INFO - Running workplace_location.i1.simulate.work_veryhigh with 905 persons\r\n", - "DEBUG - workplace_location: write_table cache: choosers\r\n", - "DEBUG - workplace_location: write_table cache: interaction_sample_alternatives\r\n", - "INFO - workplace_location.i1.simulate.work_veryhigh.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 905 choosers and 171950 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 905 of 905 choosers\r\n", - "INFO - Running eval_interaction_utilities on 171950 rows\r\n", - "INFO - workplace_location: eval_interaction_utilities write_interaction_expression_values workplace_location.i1.simulate.work_veryhigh.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - workplace_location: write_table cache: interaction_expression_values\r\n", - "DEBUG - workplace_location: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - workplace_location: write_table cache: override_choices\r\n", - "INFO - write_trace_files iteration 1\r\n", - "INFO - workplace_location_logsum top 10 value counts:\r\n", - "-0.007794 2\r\n", - " 1.586426 2\r\n", - "-1.938350 2\r\n", - " 0.491470 2\r\n", - " 0.267120 2\r\n", - " 0.047726 2\r\n", - " 0.657742 1\r\n", - "-0.051882 1\r\n", - " 6.017921 1\r\n", - "-0.325755 1\r\n", - "Name: logsum, dtype: int64\r\n", - "DEBUG - workplace_location: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - workplace_location: write_omnibus_choosers: output/estimation_data_bundle/workplace_location/workplace_location_choosers_combined.csv\r\n", - "DEBUG - workplace_location: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "DEBUG - workplace_location: write_omnibus_choosers: output/estimation_data_bundle/workplace_location/workplace_location_alternatives_combined.csv\r\n", - "INFO - workplace_location: end estimation\r\n", - "Time to execute step 'workplace_location': 51.96 s\r\n", - "Total time to execute iteration 1 with iteration value None: 51.96 s\r\n", - "INFO - #run_model running step auto_ownership_simulate\r\n", - "Running step 'auto_ownership_simulate'\r\n", - "INFO - Initialize Estimator for'auto_ownership'\r\n", - "INFO - Running auto_ownership_simulate with 2000 households\r\n", - "DEBUG - auto_ownership: estimate.write_spec: output/estimation_data_bundle/auto_ownership/auto_ownership_SPEC.csv\r\n", - "DEBUG - auto_ownership: write_table write: auto_ownership_coefficients.csv\r\n", - "DEBUG - auto_ownership: write_table cache: choosers\r\n", - "INFO - auto_ownership_simulate.simple_simulate Running adaptive_chunked_choosers with 2000 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2000 of 2000 choosers\r\n", - "DEBUG - auto_ownership: write_table cache: expression_values\r\n", - "DEBUG - auto_ownership: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using households.index\r\n", - "DEBUG - auto_ownership: write_table cache: override_choices\r\n", - "DEBUG - auto_ownership: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - auto_ownership: write_omnibus_choosers: output/estimation_data_bundle/auto_ownership/auto_ownership_values_combined.csv\r\n", - "INFO - auto_ownership: end estimation\r\n", - "INFO - auto_ownership top 10 value counts:\r\n", - "1 883\r\n", - "0 615\r\n", - "2 390\r\n", - "3 74\r\n", - "4 38\r\n", - "Name: auto_ownership, dtype: int64\r\n", - "Time to execute step 'auto_ownership_simulate': 0.41 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.41 s\r\n", - "INFO - #run_model running step free_parking\r\n", - "Running step 'free_parking'\r\n", - "INFO - Running free_parking with 2582 persons\r\n", - "INFO - Initialize Estimator for'free_parking'\r\n", - "DEBUG - free_parking: estimate.write_spec: output/estimation_data_bundle/free_parking/free_parking_SPEC.csv\r\n", - "DEBUG - free_parking: write_table write: free_parking_coefficients.csv\r\n", - "DEBUG - free_parking: write_table cache: choosers\r\n", - "INFO - free_parking.simple_simulate Running adaptive_chunked_choosers with 2582 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2582 of 2582 choosers\r\n", - "DEBUG - free_parking: write_table cache: expression_values\r\n", - "DEBUG - free_parking: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - free_parking: write_table cache: override_choices\r\n", - "DEBUG - free_parking: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - free_parking: write_omnibus_choosers: output/estimation_data_bundle/free_parking/free_parking_values_combined.csv\r\n", - "INFO - free_parking: end estimation\r\n", - "INFO - free_parking top 10 value counts:\r\n", - "False 4272\r\n", - "True 133\r\n", - "Name: free_parking_at_work, dtype: int64\r\n", - "Time to execute step 'free_parking': 0.31 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.31 s\r\n", - "INFO - #run_model running step cdap_simulate\r\n", - "Running step 'cdap_simulate'\r\n", - "INFO - Initialize Estimator for'cdap'\r\n", - "INFO - Pre-building cdap specs\r\n", - "INFO - Time to execute build_cdap_spec hh_size 2 : 0.214 seconds (0.0 minutes)\r\n", - "INFO - Time to execute build_cdap_spec hh_size 3 : 0.5 seconds (0.0 minutes)\r\n", - "INFO - Time to execute build_cdap_spec hh_size 4 : 1.268 seconds (0.0 minutes)\r\n", - "INFO - Time to execute build_cdap_spec hh_size 5 : 3.119 seconds (0.1 minutes)\r\n", - "DEBUG - cdap: estimate.write_spec: output/estimation_data_bundle/cdap/cdap_INDIV_AND_HHSIZE1_SPEC.csv\r\n", - "DEBUG - cdap: estimate.write_spec: output/estimation_data_bundle/cdap/cdap_FIXED_RELATIVE_PROPORTIONS_SPEC.csv\r\n", - "DEBUG - cdap: write_table write: cdap_coefficients.csv\r\n", - "DEBUG - cdap: write_table write: interaction_coefficients\r\n", - "DEBUG - cdap: write_table cache: choosers\r\n", - "DEBUG - cdap: write_table write: spec_2\r\n", - "DEBUG - cdap: write_table write: spec_3\r\n", - "DEBUG - cdap: write_table write: spec_4\r\n", - "DEBUG - cdap: write_table write: spec_5\r\n", - "INFO - Running cdap_simulate with 4405 persons\r\n", - "INFO - cdap.cdap Running chunk 1 of 1 with 2000 of 2000 choosers\r\n", - "DEBUG - cdap: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - cdap: write_table cache: override_choices\r\n", - "DEBUG - cdap: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - cdap: write_omnibus_choosers: output/estimation_data_bundle/cdap/cdap_values_combined.csv\r\n", - "INFO - cdap: end estimation\r\n", - "INFO - cdap_activity top 10 value counts:\r\n", - "M 2620\r\n", - "N 1213\r\n", - "H 572\r\n", - "Name: cdap_activity, dtype: int64\r\n", - "INFO - cdap crosstabs:\r\n", - "cdap_activity H M N All\r\n", - "ptype \r\n", - "1 156 1606 158 1920\r\n", - "2 47 331 118 496\r\n", - "3 33 230 66 329\r\n", - "4 114 0 420 534\r\n", - "5 114 0 357 471\r\n", - "6 22 42 8 72\r\n", - "7 39 274 39 352\r\n", - "8 47 137 47 231\r\n", - "All 572 2620 1213 4405\r\n", - "Time to execute step 'cdap_simulate': 9.84 s\r\n", - "Total time to execute iteration 1 with iteration value None: 9.84 s\r\n", - "INFO - #run_model running step mandatory_tour_frequency\r\n", - "Running step 'mandatory_tour_frequency'\r\n", - "INFO - Running mandatory_tour_frequency with 2620 persons\r\n", - "INFO - Initialize Estimator for'mandatory_tour_frequency'\r\n", - "DEBUG - mandatory_tour_frequency: estimate.write_spec: output/estimation_data_bundle/mandatory_tour_frequency/mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - mandatory_tour_frequency: write_table write: mandatory_tour_frequency_coefficients.csv\r\n", - "DEBUG - mandatory_tour_frequency: write_table cache: choosers\r\n", - "INFO - mandatory_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 2620 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2620 of 2620 choosers\r\n", - "DEBUG - mandatory_tour_frequency: write_table cache: expression_values\r\n", - "DEBUG - mandatory_tour_frequency: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - mandatory_tour_frequency: write_table cache: override_choices\r\n", - "DEBUG - mandatory_tour_frequency: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - mandatory_tour_frequency: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_frequency/mandatory_tour_frequency_values_combined.csv\r\n", - "INFO - mandatory_tour_frequency: end estimation\r\n", - "INFO - mandatory_tour_frequency top 10 value counts:\r\n", - "work1 1941\r\n", - " 1785\r\n", - "school1 566\r\n", - "work2 81\r\n", - "work_and_school 21\r\n", - "school2 11\r\n", - "Name: mandatory_tour_frequency, dtype: int64\r\n", - "Time to execute step 'mandatory_tour_frequency': 1.21 s\r\n", - "Total time to execute iteration 1 with iteration value None: 1.21 s\r\n", - "INFO - #run_model running step mandatory_tour_scheduling\r\n", - "Running step 'mandatory_tour_scheduling'\r\n", - "DEBUG - @inject timetable\r\n", - "INFO - Initialize Estimator for'mandatory_tour_scheduling_work'\r\n", - "DEBUG - mandatory_tour_scheduling_work: estimate.write_spec: output/estimation_data_bundle/mandatory_tour_scheduling_work/mandatory_tour_scheduling_work_SPEC.csv\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table write: tour_scheduling_work_coefficients.csv\r\n", - "INFO - Initialize Estimator for'mandatory_tour_scheduling_school'\r\n", - "DEBUG - mandatory_tour_scheduling_school: estimate.write_spec: output/estimation_data_bundle/mandatory_tour_scheduling_school/mandatory_tour_scheduling_school_SPEC.csv\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table write: tour_scheduling_school_coefficients.csv\r\n", - "INFO - mandatory_tour_scheduling_work: timetable.begin_transaction person_windows\r\n", - "INFO - mandatory_tour_scheduling_school: timetable.begin_transaction person_windows\r\n", - "INFO - Running mandatory_tour_scheduling with 2733 tours\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 2043 tour choices\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work Running adaptive_chunked_choosers with 2043 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2043 of 2043 choosers\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 2043 tour choices\r\n", - "INFO - tdd_alt_segments specified for representative logsums\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 388170 to 30645 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums compute_logsums for 30645 choosers 30645 alts\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums Running adaptive_chunked_choosers with 30645 choosers\r\n", - "INFO - Running chunk 1 of 1 with 30645 of 30645 choosers\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: choosers\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_sample_alternatives\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 2043 choosers and 388170 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 2043 of 2043 choosers\r\n", - "INFO - Running eval_interaction_utilities on 388170 rows\r\n", - "INFO - mandatory_tour_scheduling_work: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_expression_values\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 451 tour choices\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school Running adaptive_chunked_choosers with 451 choosers\r\n", - "INFO - Running chunk 1 of 1 with 451 of 451 choosers\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 451 tour choices\r\n", - "INFO - tdd_alt_segments specified for representative logsums\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 85690 to 6765 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums compute_logsums for 6765 choosers 6765 alts\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums Running adaptive_chunked_choosers with 6765 choosers\r\n", - "INFO - Running chunk 1 of 1 with 6765 of 6765 choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 451 choosers and 85690 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 451 of 451 choosers\r\n", - "INFO - Running eval_interaction_utilities on 85690 rows\r\n", - "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 126 tour choices\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ Running adaptive_chunked_choosers with 126 choosers\r\n", - "INFO - Running chunk 1 of 1 with 126 of 126 choosers\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 126 tour choices\r\n", - "INFO - tdd_alt_segments specified for representative logsums\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 23940 to 1890 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums compute_logsums for 1890 choosers 1890 alts\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums Running adaptive_chunked_choosers with 1890 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1890 of 1890 choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 126 choosers and 23940 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 126 of 126 choosers\r\n", - "INFO - Running eval_interaction_utilities on 23940 rows\r\n", - "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work schedule_tours running 81 tour choices\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work Running adaptive_chunked_choosers with 81 choosers\r\n", - "INFO - Running chunk 1 of 1 with 81 of 81 choosers\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work schedule_tours running 81 tour choices\r\n", - "INFO - tdd_alt_segments specified for representative logsums\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 89.13% from 6074 to 660 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.compute_logsums.logsums compute_logsums for 660 choosers 660 alts\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.compute_logsums.logsums Running adaptive_chunked_choosers with 660 choosers\r\n", - "INFO - Running chunk 1 of 1 with 660 of 660 choosers\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: choosers\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_sample_alternatives\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 81 choosers and 6074 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 81 of 81 choosers\r\n", - "INFO - Running eval_interaction_utilities on 6074 rows\r\n", - "INFO - mandatory_tour_scheduling_work: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.work.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: interaction_expression_values\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school schedule_tours running 9 tour choices\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school Running adaptive_chunked_choosers with 9 choosers\r\n", - "INFO - Running chunk 1 of 1 with 9 of 9 choosers\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school schedule_tours running 9 tour choices\r\n", - "INFO - tdd_alt_segments specified for representative logsums\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 89.84% from 817 to 83 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.compute_logsums.logsums compute_logsums for 83 choosers 83 alts\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.compute_logsums.logsums Running adaptive_chunked_choosers with 83 choosers\r\n", - "INFO - Running chunk 1 of 1 with 83 of 83 choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 9 choosers and 817 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 9 of 9 choosers\r\n", - "INFO - Running eval_interaction_utilities on 817 rows\r\n", - "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.school.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ schedule_tours running 23 tour choices\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ Running adaptive_chunked_choosers with 23 choosers\r\n", - "INFO - Running chunk 1 of 1 with 23 of 23 choosers\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ schedule_tours running 23 tour choices\r\n", - "INFO - tdd_alt_segments specified for representative logsums\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 90.26% from 2176 to 212 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.compute_logsums.logsums compute_logsums for 212 choosers 212 alts\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.compute_logsums.logsums Running adaptive_chunked_choosers with 212 choosers\r\n", - "INFO - Running chunk 1 of 1 with 212 of 212 choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: choosers\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_sample_alternatives\r\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 23 choosers and 2176 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 23 of 23 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2176 rows\r\n", - "INFO - mandatory_tour_scheduling_school: eval_interaction_utilities write_interaction_expression_values mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.univ.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: interaction_expression_values\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_table cache: override_choices\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_work/mandatory_tour_scheduling_work_choosers_combined.csv\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "DEBUG - mandatory_tour_scheduling_work: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_work/mandatory_tour_scheduling_work_alternatives_combined.csv\r\n", - "INFO - mandatory_tour_scheduling_work: end estimation\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_table cache: override_choices\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_school/mandatory_tour_scheduling_school_choosers_combined.csv\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "DEBUG - mandatory_tour_scheduling_school: write_omnibus_choosers: output/estimation_data_bundle/mandatory_tour_scheduling_school/mandatory_tour_scheduling_school_alternatives_combined.csv\r\n", - "INFO - mandatory_tour_scheduling_school: end estimation\r\n", - "INFO - mandatory_tour_scheduling_work: timetable.rollback person_windows\r\n", - "INFO - mandatory_tour_scheduling_school: timetable.rollback person_windows\r\n", - "Time to execute step 'mandatory_tour_scheduling': 84.87 s\r\n", - "Total time to execute iteration 1 with iteration value None: 84.87 s\r\n", - "INFO - #run_model running step joint_tour_frequency\r\n", - "Running step 'joint_tour_frequency'\r\n", - "INFO - Initialize Estimator for'joint_tour_frequency'\r\n", - "INFO - Running joint_tour_frequency with 1028 multi-person households\r\n", - "DEBUG - @inject timetable\r\n", - "DEBUG - joint_tour_frequency: estimate.write_spec: output/estimation_data_bundle/joint_tour_frequency/joint_tour_frequency_SPEC.csv\r\n", - "DEBUG - joint_tour_frequency: write_table write: joint_tour_frequency_coefficients.csv\r\n", - "DEBUG - joint_tour_frequency: write_table cache: choosers\r\n", - "INFO - joint_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 1028 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1028 of 1028 choosers\r\n", - "DEBUG - joint_tour_frequency: write_table cache: expression_values\r\n", - "DEBUG - joint_tour_frequency: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using households.index\r\n", - "DEBUG - joint_tour_frequency: write_table cache: override_choices\r\n", - "DEBUG - joint_tour_frequency: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - joint_tour_frequency: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_frequency/joint_tour_frequency_values_combined.csv\r\n", - "INFO - joint_tour_frequency: end estimation\r\n", - "INFO - joint_tour_frequency top 10 value counts:\r\n", - "0_tours 1910\r\n", - "1_Main 26\r\n", - "1_Disc 20\r\n", - "1_Shop 16\r\n", - "1_Eat 15\r\n", - "1_Visit 12\r\n", - "2_SS 1\r\n", - "Name: joint_tour_frequency, dtype: int64\r\n", - "len(survey_tours) 91\r\n", - "len(joint_tours) 91\r\n", - "Time to execute step 'joint_tour_frequency': 0.68 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.68 s\r\n", - "INFO - #run_model running step joint_tour_composition\r\n", - "Running step 'joint_tour_composition'\r\n", - "INFO - Initialize Estimator for'joint_tour_composition'\r\n", - "INFO - Running joint_tour_composition with 91 joint tours\r\n", - "DEBUG - @inject timetable\r\n", - "DEBUG - joint_tour_composition: estimate.write_spec: output/estimation_data_bundle/joint_tour_composition/joint_tour_composition_SPEC.csv\r\n", - "DEBUG - joint_tour_composition: write_table write: joint_tour_composition_coefficients.csv\r\n", - "DEBUG - joint_tour_composition: write_table cache: choosers\r\n", - "INFO - joint_tour_composition.simple_simulate Running adaptive_chunked_choosers with 91 choosers\r\n", - "INFO - Running chunk 1 of 1 with 91 of 91 choosers\r\n", - "DEBUG - joint_tour_composition: write_table cache: expression_values\r\n", - "DEBUG - joint_tour_composition: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - joint_tour_composition: write_table cache: override_choices\r\n", - "DEBUG - joint_tour_composition: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - joint_tour_composition: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_composition/joint_tour_composition_values_combined.csv\r\n", - "INFO - joint_tour_composition: end estimation\r\n", - "INFO - joint_tour_composition top 10 value counts:\r\n", - "adults 54\r\n", - "mixed 32\r\n", - "children 5\r\n", - "Name: composition, dtype: int64\r\n", - "Time to execute step 'joint_tour_composition': 0.31 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.31 s\r\n", - "INFO - #run_model running step joint_tour_participation\r\n", - "Running step 'joint_tour_participation'\r\n", - "INFO - Running joint_tours_participation with 304 potential participants (candidates)\r\n", - "DEBUG - @inject timetable\r\n", - "INFO - Initialize Estimator for'joint_tour_participation'\r\n", - "DEBUG - joint_tour_participation: estimate.write_spec: output/estimation_data_bundle/joint_tour_participation/joint_tour_participation_SPEC.csv\r\n", - "DEBUG - joint_tour_participation: write_table write: joint_tour_participation_coefficients.csv\r\n", - "DEBUG - joint_tour_participation: write_table cache: choosers\r\n", - "INFO - joint_tour_participation Running chunk 1 of 1 with 90 of 90 choosers\r\n", - "DEBUG - joint_tour_participation: write_table cache: expression_values\r\n", - "INFO - joint_tour_participation.eval_mnl.participants_chooser 91 joint tours to satisfy.\r\n", - "INFO - joint_tour_participation.eval_mnl.participants_chooser 8 iterations to satisfy all joint tours.\r\n", - "DEBUG - joint_tour_participation: write_table cache: choices\r\n", - "DEBUG - joint_tour_participation: write_table cache: override_choices\r\n", - "DEBUG - joint_tour_participation: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - joint_tour_participation: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_participation/joint_tour_participation_values_combined.csv\r\n", - "INFO - joint_tour_participation: end estimation\r\n", - "Time to execute step 'joint_tour_participation': 0.99 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.99 s\r\n", - "INFO - #run_model running step joint_tour_destination\r\n", - "Running step 'joint_tour_destination'\r\n", - "INFO - Initialize Estimator for'joint_tour_destination'\r\n", - "DEBUG - joint_tour_destination: write_table write: non_mandatory_tour_destination_coefficients.csv\r\n", - "DEBUG - joint_tour_destination: estimate.write_spec: output/estimation_data_bundle/joint_tour_destination/joint_tour_destination_SPEC.csv\r\n", - "DEBUG - joint_tour_destination: write_table write: size_terms\r\n", - "DEBUG - joint_tour_destination: write_table write: landuse\r\n", - "INFO - running joint_tour_destination.shopping.sample with 18 tours\r\n", - "INFO - Estimation mode for joint_tour_destination.shopping.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - joint_tour_destination.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 18 choosers\r\n", - "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3420 rows\r\n", - "INFO - Estimation mode for joint_tour_destination.shopping.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running joint_tour_destination.shopping.logsums with 3420 rows\r\n", - "INFO - joint_tour_destination.shopping.logsums.compute_logsums Running adaptive_chunked_choosers with 3420 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3420 of 3420 choosers\r\n", - "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 18 persons\r\n", - "INFO - joint_tour_destination.shopping.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 18 choosers and 3420 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3420 rows\r\n", - "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.shopping.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running joint_tour_destination.othmaint.sample with 26 tours\r\n", - "INFO - Estimation mode for joint_tour_destination.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - joint_tour_destination.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 26 choosers\r\n", - "INFO - Running chunk 1 of 1 with 26 of 26 choosers\r\n", - "INFO - Running eval_interaction_utilities on 4940 rows\r\n", - "INFO - Estimation mode for joint_tour_destination.othmaint.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running joint_tour_destination.othmaint.logsums with 4940 rows\r\n", - "INFO - joint_tour_destination.othmaint.logsums.compute_logsums Running adaptive_chunked_choosers with 4940 choosers\r\n", - "INFO - Running chunk 1 of 1 with 4940 of 4940 choosers\r\n", - "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 26 persons\r\n", - "INFO - joint_tour_destination.othmaint.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 26 choosers and 4940 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 26 of 26 choosers\r\n", - "INFO - Running eval_interaction_utilities on 4940 rows\r\n", - "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.othmaint.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running joint_tour_destination.othdiscr.sample with 20 tours\r\n", - "INFO - Estimation mode for joint_tour_destination.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - joint_tour_destination.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 20 choosers\r\n", - "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3800 rows\r\n", - "INFO - Estimation mode for joint_tour_destination.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running joint_tour_destination.othdiscr.logsums with 3800 rows\r\n", - "INFO - joint_tour_destination.othdiscr.logsums.compute_logsums Running adaptive_chunked_choosers with 3800 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3800 of 3800 choosers\r\n", - "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 20 persons\r\n", - "INFO - joint_tour_destination.othdiscr.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 20 choosers and 3800 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3800 rows\r\n", - "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.othdiscr.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running joint_tour_destination.eatout.sample with 15 tours\r\n", - "INFO - Estimation mode for joint_tour_destination.eatout.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - joint_tour_destination.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 15 choosers\r\n", - "INFO - Running chunk 1 of 1 with 15 of 15 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2850 rows\r\n", - "INFO - Estimation mode for joint_tour_destination.eatout.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running joint_tour_destination.eatout.logsums with 2850 rows\r\n", - "INFO - joint_tour_destination.eatout.logsums.compute_logsums Running adaptive_chunked_choosers with 2850 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2850 of 2850 choosers\r\n", - "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 15 persons\r\n", - "INFO - joint_tour_destination.eatout.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 15 choosers and 2850 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 15 of 15 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2850 rows\r\n", - "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.eatout.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running joint_tour_destination.social.sample with 12 tours\r\n", - "INFO - Estimation mode for joint_tour_destination.social.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - joint_tour_destination.social.sample.interaction_sample Running adaptive_chunked_choosers with 12 choosers\r\n", - "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2280 rows\r\n", - "INFO - Estimation mode for joint_tour_destination.social.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running joint_tour_destination.social.logsums with 2280 rows\r\n", - "INFO - joint_tour_destination.social.logsums.compute_logsums Running adaptive_chunked_choosers with 2280 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2280 of 2280 choosers\r\n", - "DEBUG - joint_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 12 persons\r\n", - "INFO - joint_tour_destination.social.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 12 choosers and 2280 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2280 rows\r\n", - "INFO - joint_tour_destination: eval_interaction_utilities write_interaction_expression_values joint_tour_destination.social.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - joint_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - joint_tour_destination skipping segment escort: no choosers\r\n", - "DEBUG - joint_tour_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - joint_tour_destination: write_table cache: override_choices\r\n", - "DEBUG - joint_tour_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - joint_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_destination/joint_tour_destination_choosers_combined.csv\r\n", - "DEBUG - joint_tour_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "WARNING - joint_tour_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", - "DEBUG - joint_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_destination/joint_tour_destination_alternatives_combined.csv\r\n", - "INFO - joint_tour_destination: end estimation\r\n", - "INFO - destination summary:\r\n", - "count 91.000000\r\n", - "mean 101.439560\r\n", - "std 56.893899\r\n", - "min 4.000000\r\n", - "25% 45.000000\r\n", - "50% 114.000000\r\n", - "75% 149.000000\r\n", - "max 190.000000\r\n", - "Name: destination, dtype: float64\r\n", - "Time to execute step 'joint_tour_destination': 7.54 s\r\n", - "Total time to execute iteration 1 with iteration value None: 7.54 s\r\n", - "INFO - #run_model running step joint_tour_scheduling\r\n", - "Running step 'joint_tour_scheduling'\r\n", - "INFO - Running joint_tour_scheduling with 91 joint tours\r\n", - "DEBUG - @inject timetable\r\n", - "INFO - Initialize Estimator for'joint_tour_scheduling'\r\n", - "DEBUG - joint_tour_scheduling: estimate.write_spec: output/estimation_data_bundle/joint_tour_scheduling/joint_tour_scheduling_SPEC.csv\r\n", - "DEBUG - joint_tour_scheduling: write_table write: tour_scheduling_joint_coefficients.csv\r\n", - "INFO - joint_tour_scheduling: timetable.begin_transaction person_windows\r\n", - "INFO - schedule_tours %s tours not monotonic_increasing - sorting df\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1 schedule_tours running 90 tour choices\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1 Running adaptive_chunked_choosers with 90 choosers\r\n", - "INFO - Running chunk 1 of 1 with 90 of 90 choosers\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1 schedule_tours running 90 tour choices\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: choosers\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 90 choosers and 6458 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 90 of 90 choosers\r\n", - "INFO - Running eval_interaction_utilities on 6458 rows\r\n", - "INFO - joint_tour_scheduling: eval_interaction_utilities write_interaction_expression_values joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_1.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: interaction_expression_values\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2 schedule_tours running 1 tour choices\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2 Running adaptive_chunked_choosers with 1 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1 of 1 choosers\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2 schedule_tours running 1 tour choices\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: choosers\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 1 choosers and 73 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 1 of 1 choosers\r\n", - "INFO - Running eval_interaction_utilities on 73 rows\r\n", - "INFO - joint_tour_scheduling: eval_interaction_utilities write_interaction_expression_values joint_tour_scheduling.vectorize_joint_tour_scheduling.tour_2.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: interaction_expression_values\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - joint_tour_scheduling: write_table cache: override_choices\r\n", - "DEBUG - joint_tour_scheduling: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - joint_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_scheduling/joint_tour_scheduling_choosers_combined.csv\r\n", - "DEBUG - joint_tour_scheduling: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "DEBUG - joint_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/joint_tour_scheduling/joint_tour_scheduling_alternatives_combined.csv\r\n", - "INFO - joint_tour_scheduling: end estimation\r\n", - "INFO - joint_tour_scheduling: timetable.rollback person_windows\r\n", - "INFO - joint_tour_scheduling: assign timetable for 224 participants in 90 tours with tour_num 1\r\n", - "INFO - joint_tour_scheduling: assign timetable for 4 participants in 1 tours with tour_num 2\r\n", - "Time to execute step 'joint_tour_scheduling': 1.89 s\r\n", - "Total time to execute iteration 1 with iteration value None: 1.89 s\r\n", - "INFO - #run_model running step non_mandatory_tour_frequency\r\n", - "Running step 'non_mandatory_tour_frequency'\r\n", - "DEBUG - @inject timetable\r\n", - "INFO - Running non_mandatory_tour_frequency with 3833 persons\r\n", - "INFO - Running segment 'PTYPE_FULL' of size 1764\r\n", - "INFO - Initialize Estimator for'PTYPE_FULL'\r\n", - "DEBUG - PTYPE_FULL: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_FULL: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_FULL.csv\r\n", - "DEBUG - PTYPE_FULL: write_table cache: choosers\r\n", - "DEBUG - PTYPE_FULL: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_FULL.interaction_simulate Running adaptive_chunked_choosers with 1764 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1764 of 1764 choosers\r\n", - "WARNING - /mnt/c/Users/jan.zill/code/activitysim/activitysim/core/logit.py:358: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", - " alts_sample[c_chooser] = np.repeat(choosers[c].values, sample_size)\r\n", - "\r\n", - "INFO - Running eval_interaction_utilities on 169344 rows\r\n", - "WARNING - /mnt/c/Users/jan.zill/code/activitysim/activitysim/core/interaction_simulate.py:482: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", - " choices = _interaction_simulate(chooser_chunk, alternatives, spec,\r\n", - "\r\n", - "INFO - PTYPE_FULL: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_FULL.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_FULL: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_FULL: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_FULL: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_FULL: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_FULL: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_FULL/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_FULL: end estimation\r\n", - "INFO - Running segment 'PTYPE_PART' of size 449\r\n", - "INFO - Initialize Estimator for'PTYPE_PART'\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_PART: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_PART: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_PART.csv\r\n", - "DEBUG - PTYPE_PART: write_table cache: choosers\r\n", - "DEBUG - PTYPE_PART: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_PART.interaction_simulate Running adaptive_chunked_choosers with 449 choosers\r\n", - "INFO - Running chunk 1 of 1 with 449 of 449 choosers\r\n", - "INFO - Running eval_interaction_utilities on 43104 rows\r\n", - "INFO - PTYPE_PART: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_PART.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_PART: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_PART: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_PART: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_PART: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_PART: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_PART/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_PART: end estimation\r\n", - "INFO - Running segment 'PTYPE_UNIVERSITY' of size 296\r\n", - "INFO - Initialize Estimator for'PTYPE_UNIVERSITY'\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_UNIVERSITY: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_UNIVERSITY.csv\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_table cache: choosers\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_UNIVERSITY.interaction_simulate Running adaptive_chunked_choosers with 296 choosers\r\n", - "INFO - Running chunk 1 of 1 with 296 of 296 choosers\r\n", - "INFO - Running eval_interaction_utilities on 28416 rows\r\n", - "INFO - PTYPE_UNIVERSITY: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_UNIVERSITY.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_UNIVERSITY: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_UNIVERSITY/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_UNIVERSITY: end estimation\r\n", - "INFO - Running segment 'PTYPE_NONWORK' of size 420\r\n", - "INFO - Initialize Estimator for'PTYPE_NONWORK'\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_NONWORK: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_NONWORK: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_NONWORK.csv\r\n", - "DEBUG - PTYPE_NONWORK: write_table cache: choosers\r\n", - "DEBUG - PTYPE_NONWORK: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_NONWORK.interaction_simulate Running adaptive_chunked_choosers with 420 choosers\r\n", - "INFO - Running chunk 1 of 1 with 420 of 420 choosers\r\n", - "INFO - Running eval_interaction_utilities on 40320 rows\r\n", - "INFO - PTYPE_NONWORK: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_NONWORK.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_NONWORK: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_NONWORK: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_NONWORK: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_NONWORK: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_NONWORK: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_NONWORK/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_NONWORK: end estimation\r\n", - "INFO - Running segment 'PTYPE_RETIRED' of size 357\r\n", - "INFO - Initialize Estimator for'PTYPE_RETIRED'\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_RETIRED: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_RETIRED: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_RETIRED.csv\r\n", - "DEBUG - PTYPE_RETIRED: write_table cache: choosers\r\n", - "DEBUG - PTYPE_RETIRED: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_RETIRED.interaction_simulate Running adaptive_chunked_choosers with 357 choosers\r\n", - "INFO - Running chunk 1 of 1 with 357 of 357 choosers\r\n", - "INFO - Running eval_interaction_utilities on 34272 rows\r\n", - "INFO - PTYPE_RETIRED: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_RETIRED.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_RETIRED: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_RETIRED: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_RETIRED: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_RETIRED: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_RETIRED: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_RETIRED/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_RETIRED: end estimation\r\n", - "INFO - Running segment 'PTYPE_DRIVING' of size 50\r\n", - "INFO - Initialize Estimator for'PTYPE_DRIVING'\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_DRIVING: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_DRIVING: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_DRIVING.csv\r\n", - "DEBUG - PTYPE_DRIVING: write_table cache: choosers\r\n", - "DEBUG - PTYPE_DRIVING: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_DRIVING.interaction_simulate Running adaptive_chunked_choosers with 50 choosers\r\n", - "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", - "INFO - Running eval_interaction_utilities on 4800 rows\r\n", - "INFO - PTYPE_DRIVING: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_DRIVING.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_DRIVING: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_DRIVING: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_DRIVING: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_DRIVING: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_DRIVING: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_DRIVING/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_DRIVING: end estimation\r\n", - "INFO - Running segment 'PTYPE_SCHOOL' of size 313\r\n", - "INFO - Initialize Estimator for'PTYPE_SCHOOL'\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_SCHOOL: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_SCHOOL: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_SCHOOL.csv\r\n", - "DEBUG - PTYPE_SCHOOL: write_table cache: choosers\r\n", - "DEBUG - PTYPE_SCHOOL: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_SCHOOL.interaction_simulate Running adaptive_chunked_choosers with 313 choosers\r\n", - "INFO - Running chunk 1 of 1 with 313 of 313 choosers\r\n", - "INFO - Running eval_interaction_utilities on 30048 rows\r\n", - "INFO - PTYPE_SCHOOL: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_SCHOOL.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_SCHOOL: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_SCHOOL: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_SCHOOL: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_SCHOOL: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_SCHOOL: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_SCHOOL/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_SCHOOL: end estimation\r\n", - "INFO - Running segment 'PTYPE_PRESCHOOL' of size 184\r\n", - "INFO - Initialize Estimator for'PTYPE_PRESCHOOL'\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_alternatives.csv\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_model_settings.yaml\r\n", - "deleted output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_PRESCHOOL: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_frequency/non_mandatory_tour_frequency_SPEC.csv\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_table write: non_mandatory_tour_frequency_coefficients_PTYPE_PRESCHOOL.csv\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_table cache: choosers\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_table write: alternatives\r\n", - "INFO - non_mandatory_tour_frequency.PTYPE_PRESCHOOL.interaction_simulate Running adaptive_chunked_choosers with 184 choosers\r\n", - "INFO - Running chunk 1 of 1 with 184 of 184 choosers\r\n", - "INFO - Running eval_interaction_utilities on 17664 rows\r\n", - "INFO - PTYPE_PRESCHOOL: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_frequency.PTYPE_PRESCHOOL.interaction_simulate.interaction_simulate.eval_interaction_utils\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_table write: interaction_expression_values\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_table cache: override_choices\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - PTYPE_PRESCHOOL: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_frequency/PTYPE_PRESCHOOL/non_mandatory_tour_frequency_choosers_combined.csv\r\n", - "INFO - PTYPE_PRESCHOOL: end estimation\r\n", - "INFO - extend_tour_counts increased tour count by 166 from 2324 to 2490\r\n", - "DEBUG - get_survey_values: reindexing using persons.index\r\n", - "INFO - estimation get_survey_values override_tour_counts 0 changed cells\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "INFO - non_mandatory_tour_frequency top 10 value counts:\r\n", - "0 2718\r\n", - "16 374\r\n", - "1 282\r\n", - "8 174\r\n", - "4 154\r\n", - "32 132\r\n", - "2 84\r\n", - "17 61\r\n", - "24 50\r\n", - "9 32\r\n", - "Name: non_mandatory_tour_frequency, dtype: int64\r\n", - "Time to execute step 'non_mandatory_tour_frequency': 188.21 s\r\n", - "Total time to execute iteration 1 with iteration value None: 188.21 s\r\n", - "INFO - #run_model running step non_mandatory_tour_destination\r\n", - "Running step 'non_mandatory_tour_destination'\r\n", - "INFO - Initialize Estimator for'non_mandatory_tour_destination'\r\n", - "DEBUG - non_mandatory_tour_destination: write_table write: non_mandatory_tour_destination_coefficients.csv\r\n", - "DEBUG - non_mandatory_tour_destination: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_destination/non_mandatory_tour_destination_SPEC.csv\r\n", - "DEBUG - non_mandatory_tour_destination: write_table write: size_terms\r\n", - "DEBUG - non_mandatory_tour_destination: write_table write: landuse\r\n", - "INFO - running non_mandatory_tour_destination.shopping.sample with 705 tours\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.shopping.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - non_mandatory_tour_destination.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 705 choosers\r\n", - "INFO - Running chunk 1 of 1 with 705 of 705 choosers\r\n", - "INFO - Running eval_interaction_utilities on 133950 rows\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.shopping.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running non_mandatory_tour_destination.shopping.logsums with 133950 rows\r\n", - "INFO - non_mandatory_tour_destination.shopping.logsums.compute_logsums Running adaptive_chunked_choosers with 133950 choosers\r\n", - "INFO - Running chunk 1 of 1 with 133950 of 133950 choosers\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 705 persons\r\n", - "INFO - non_mandatory_tour_destination.shopping.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 705 choosers and 133950 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 705 of 705 choosers\r\n", - "INFO - Running eval_interaction_utilities on 133950 rows\r\n", - "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.shopping.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running non_mandatory_tour_destination.othmaint.sample with 367 tours\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - non_mandatory_tour_destination.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 367 choosers\r\n", - "INFO - Running chunk 1 of 1 with 367 of 367 choosers\r\n", - "INFO - Running eval_interaction_utilities on 69730 rows\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.othmaint.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running non_mandatory_tour_destination.othmaint.logsums with 69730 rows\r\n", - "INFO - non_mandatory_tour_destination.othmaint.logsums.compute_logsums Running adaptive_chunked_choosers with 69730 choosers\r\n", - "INFO - Running chunk 1 of 1 with 69730 of 69730 choosers\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 367 persons\r\n", - "INFO - non_mandatory_tour_destination.othmaint.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 367 choosers and 69730 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 367 of 367 choosers\r\n", - "INFO - Running eval_interaction_utilities on 69730 rows\r\n", - "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.othmaint.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running non_mandatory_tour_destination.othdiscr.sample with 544 tours\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - non_mandatory_tour_destination.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 544 choosers\r\n", - "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", - "INFO - Running eval_interaction_utilities on 103360 rows\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running non_mandatory_tour_destination.othdiscr.logsums with 103360 rows\r\n", - "INFO - non_mandatory_tour_destination.othdiscr.logsums.compute_logsums Running adaptive_chunked_choosers with 103360 choosers\r\n", - "INFO - Running chunk 1 of 1 with 103360 of 103360 choosers\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 544 persons\r\n", - "INFO - non_mandatory_tour_destination.othdiscr.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 544 choosers and 103360 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 544 of 544 choosers\r\n", - "INFO - Running eval_interaction_utilities on 103360 rows\r\n", - "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.othdiscr.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running non_mandatory_tour_destination.eatout.sample with 301 tours\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.eatout.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - non_mandatory_tour_destination.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 301 choosers\r\n", - "INFO - Running chunk 1 of 1 with 301 of 301 choosers\r\n", - "INFO - Running eval_interaction_utilities on 57190 rows\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.eatout.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running non_mandatory_tour_destination.eatout.logsums with 57190 rows\r\n", - "INFO - non_mandatory_tour_destination.eatout.logsums.compute_logsums Running adaptive_chunked_choosers with 57190 choosers\r\n", - "INFO - Running chunk 1 of 1 with 57190 of 57190 choosers\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 301 persons\r\n", - "INFO - non_mandatory_tour_destination.eatout.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 301 choosers and 57190 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 301 of 301 choosers\r\n", - "INFO - Running eval_interaction_utilities on 57190 rows\r\n", - "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.eatout.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running non_mandatory_tour_destination.social.sample with 170 tours\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.social.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - non_mandatory_tour_destination.social.sample.interaction_sample Running adaptive_chunked_choosers with 170 choosers\r\n", - "INFO - Running chunk 1 of 1 with 170 of 170 choosers\r\n", - "INFO - Running eval_interaction_utilities on 32300 rows\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.social.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running non_mandatory_tour_destination.social.logsums with 32300 rows\r\n", - "INFO - non_mandatory_tour_destination.social.logsums.compute_logsums Running adaptive_chunked_choosers with 32300 choosers\r\n", - "INFO - Running chunk 1 of 1 with 32300 of 32300 choosers\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 170 persons\r\n", - "INFO - non_mandatory_tour_destination.social.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 170 choosers and 32300 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 170 of 170 choosers\r\n", - "INFO - Running eval_interaction_utilities on 32300 rows\r\n", - "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.social.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", - "INFO - running non_mandatory_tour_destination.escort.sample with 403 tours\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.escort.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - non_mandatory_tour_destination.escort.sample.interaction_sample Running adaptive_chunked_choosers with 403 choosers\r\n", - "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", - "INFO - Running eval_interaction_utilities on 76570 rows\r\n", - "INFO - Estimation mode for non_mandatory_tour_destination.escort.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running non_mandatory_tour_destination.escort.logsums with 76570 rows\r\n", - "INFO - non_mandatory_tour_destination.escort.logsums.compute_logsums Running adaptive_chunked_choosers with 76570 choosers\r\n", - "INFO - Running chunk 1 of 1 with 76570 of 76570 choosers\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 403 persons\r\n", - "INFO - non_mandatory_tour_destination.escort.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 403 choosers and 76570 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", - "INFO - Running eval_interaction_utilities on 76570 rows\r\n", - "INFO - non_mandatory_tour_destination: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_destination.escort.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - non_mandatory_tour_destination: write_table cache: override_choices\r\n", - "DEBUG - non_mandatory_tour_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - non_mandatory_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_destination/non_mandatory_tour_destination_choosers_combined.csv\r\n", - "DEBUG - non_mandatory_tour_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "WARNING - non_mandatory_tour_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", - "DEBUG - non_mandatory_tour_destination: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_destination/non_mandatory_tour_destination_alternatives_combined.csv\r\n", - "INFO - non_mandatory_tour_destination: end estimation\r\n", - "Time to execute step 'non_mandatory_tour_destination': 38.82 s\r\n", - "Total time to execute iteration 1 with iteration value None: 38.82 s\r\n", - "INFO - #run_model running step non_mandatory_tour_scheduling\r\n", - "Running step 'non_mandatory_tour_scheduling'\r\n", - "DEBUG - @inject timetable\r\n", - "INFO - Initialize Estimator for'non_mandatory_tour_scheduling'\r\n", - "DEBUG - non_mandatory_tour_scheduling: estimate.write_spec: output/estimation_data_bundle/non_mandatory_tour_scheduling/non_mandatory_tour_scheduling_SPEC.csv\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table write: tour_scheduling_nonmandatory_coefficients.csv\r\n", - "INFO - non_mandatory_tour_scheduling: timetable.begin_transaction person_windows\r\n", - "INFO - Running non_mandatory_tour_scheduling with 2490 tours\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1 schedule_tours running 1687 tour choices\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1 Running adaptive_chunked_choosers with 1687 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1687 of 1687 choosers\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1 schedule_tours running 1687 tour choices\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 1687 choosers and 241779 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 1687 of 1687 choosers\r\n", - "INFO - Running eval_interaction_utilities on 241779 rows\r\n", - "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2 schedule_tours running 571 tour choices\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2 Running adaptive_chunked_choosers with 571 choosers\r\n", - "INFO - Running chunk 1 of 1 with 571 of 571 choosers\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2 schedule_tours running 571 tour choices\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 571 choosers and 47267 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 571 of 571 choosers\r\n", - "INFO - Running eval_interaction_utilities on 47267 rows\r\n", - "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_2.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3 schedule_tours running 179 tour choices\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3 Running adaptive_chunked_choosers with 179 choosers\r\n", - "INFO - Running chunk 1 of 1 with 179 of 179 choosers\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3 schedule_tours running 179 tour choices\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 179 choosers and 12032 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 179 of 179 choosers\r\n", - "INFO - Running eval_interaction_utilities on 12032 rows\r\n", - "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_3.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4 schedule_tours running 50 tour choices\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4 Running adaptive_chunked_choosers with 50 choosers\r\n", - "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4 schedule_tours running 50 tour choices\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 50 choosers and 2697 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2697 rows\r\n", - "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_4.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5 schedule_tours running 3 tour choices\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5 Running adaptive_chunked_choosers with 3 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5 schedule_tours running 3 tour choices\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: choosers\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 3 choosers and 146 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", - "INFO - Running eval_interaction_utilities on 146 rows\r\n", - "INFO - non_mandatory_tour_scheduling: eval_interaction_utilities write_interaction_expression_values non_mandatory_tour_scheduling.vectorize_tour_scheduling.tour_5.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: interaction_expression_values\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_table cache: override_choices\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_scheduling/non_mandatory_tour_scheduling_choosers_combined.csv\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "DEBUG - non_mandatory_tour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/non_mandatory_tour_scheduling/non_mandatory_tour_scheduling_alternatives_combined.csv\r\n", - "INFO - non_mandatory_tour_scheduling: end estimation\r\n", - "INFO - non_mandatory_tour_scheduling: timetable.rollback person_windows\r\n", - "Time to execute step 'non_mandatory_tour_scheduling': 75.98 s\r\n", - "Total time to execute iteration 1 with iteration value None: 75.98 s\r\n", - "INFO - #run_model running step tour_mode_choice_simulate\r\n", - "Running step 'tour_mode_choice_simulate'\r\n", - "INFO - Running tour_mode_choice with 5314 tours\r\n", - "INFO - tour_types top 10 value counts:\r\n", - "work 2124\r\n", - "shopping 723\r\n", - "school 609\r\n", - "othdiscr 564\r\n", - "escort 403\r\n", - "othmaint 393\r\n", - "eatout 316\r\n", - "social 182\r\n", - "Name: tour_type, dtype: int64\r\n", - "INFO - Initialize Estimator for'tour_mode_choice'\r\n", - "DEBUG - tour_mode_choice: write_table write: tour_mode_choice_coefficients.csv\r\n", - "DEBUG - tour_mode_choice: write_table write: coefficients_template\r\n", - "DEBUG - tour_mode_choice: estimate.write_spec: output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_SPEC.csv\r\n", - "INFO - tour_mode_choice_simulate tour_type 'eatout' (316 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.eatout.simple_simulate Running adaptive_chunked_choosers with 316 choosers\r\n", - "INFO - Running chunk 1 of 1 with 316 of 316 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate eatout choices_df top 10 value counts:\r\n", - "DRIVEALONEFREE 77\r\n", - "WALK 73\r\n", - "SHARED3FREE 53\r\n", - "SHARED2FREE 53\r\n", - "WALK_LOC 24\r\n", - "WALK_LRF 13\r\n", - "WALK_HVY 7\r\n", - "BIKE 6\r\n", - "TNC_SINGLE 5\r\n", - "DRIVE_LOC 3\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'escort' (403 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.escort.simple_simulate Running adaptive_chunked_choosers with 403 choosers\r\n", - "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate escort choices_df top 10 value counts:\r\n", - "SHARED2FREE 175\r\n", - "SHARED3FREE 151\r\n", - "TNC_SINGLE 45\r\n", - "TNC_SHARED 15\r\n", - "TAXI 6\r\n", - "BIKE 4\r\n", - "WALK 4\r\n", - "DRIVE_HVY 1\r\n", - "WALK_HVY 1\r\n", - "WALK_LRF 1\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'othdiscr' (564 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.othdiscr.simple_simulate Running adaptive_chunked_choosers with 564 choosers\r\n", - "INFO - Running chunk 1 of 1 with 564 of 564 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate othdiscr choices_df top 10 value counts:\r\n", - "DRIVEALONEFREE 151\r\n", - "SHARED3FREE 104\r\n", - "SHARED2FREE 83\r\n", - "WALK 73\r\n", - "WALK_LOC 65\r\n", - "WALK_LRF 31\r\n", - "WALK_HVY 19\r\n", - "BIKE 19\r\n", - "TNC_SINGLE 12\r\n", - "TAXI 2\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'othmaint' (393 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.othmaint.simple_simulate Running adaptive_chunked_choosers with 393 choosers\r\n", - "INFO - Running chunk 1 of 1 with 393 of 393 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate othmaint choices_df top 10 value counts:\r\n", - "DRIVEALONEFREE 141\r\n", - "SHARED2FREE 62\r\n", - "WALK_LOC 36\r\n", - "SHARED3FREE 35\r\n", - "BIKE 28\r\n", - "WALK 23\r\n", - "WALK_LRF 23\r\n", - "TNC_SINGLE 21\r\n", - "TNC_SHARED 11\r\n", - "WALK_HVY 9\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'school' (460 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.school.simple_simulate Running adaptive_chunked_choosers with 460 choosers\r\n", - "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate school choices_df top 10 value counts:\r\n", - "SHARED3FREE 126\r\n", - "WALK_LRF 101\r\n", - "WALK_LOC 92\r\n", - "SHARED2FREE 59\r\n", - "WALK 48\r\n", - "WALK_HVY 23\r\n", - "BIKE 7\r\n", - "DRIVEALONEFREE 2\r\n", - "TNC_SHARED 1\r\n", - "TAXI 1\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'shopping' (723 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.shopping.simple_simulate Running adaptive_chunked_choosers with 723 choosers\r\n", - "INFO - Running chunk 1 of 1 with 723 of 723 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate shopping choices_df top 10 value counts:\r\n", - "DRIVEALONEFREE 270\r\n", - "SHARED2FREE 107\r\n", - "WALK 82\r\n", - "SHARED3FREE 75\r\n", - "WALK_LOC 46\r\n", - "TNC_SINGLE 44\r\n", - "WALK_LRF 35\r\n", - "BIKE 27\r\n", - "TNC_SHARED 17\r\n", - "WALK_HVY 9\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'social' (182 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.social.simple_simulate Running adaptive_chunked_choosers with 182 choosers\r\n", - "INFO - Running chunk 1 of 1 with 182 of 182 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate social choices_df top 10 value counts:\r\n", - "SHARED2FREE 43\r\n", - "DRIVEALONEFREE 29\r\n", - "SHARED3FREE 28\r\n", - "WALK 23\r\n", - "WALK_LRF 17\r\n", - "WALK_LOC 16\r\n", - "BIKE 15\r\n", - "TNC_SINGLE 8\r\n", - "WALK_HVY 2\r\n", - "TNC_SHARED 1\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'univ' (149 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.univ.simple_simulate Running adaptive_chunked_choosers with 149 choosers\r\n", - "INFO - Running chunk 1 of 1 with 149 of 149 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate univ choices_df top 10 value counts:\r\n", - "DRIVEALONEFREE 40\r\n", - "WALK_LRF 38\r\n", - "WALK_LOC 38\r\n", - "WALK_HVY 9\r\n", - "SHARED3FREE 7\r\n", - "TNC_SHARED 6\r\n", - "TAXI 4\r\n", - "SHARED2FREE 3\r\n", - "TNC_SINGLE 2\r\n", - "BIKE 1\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - tour_mode_choice_simulate tour_type 'work' (2124 tours)\r\n", - "DEBUG - tour_mode_choice: write_table cache: choosers\r\n", - "INFO - tour_mode_choice.work.simple_simulate Running adaptive_chunked_choosers with 2124 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2124 of 2124 choosers\r\n", - "DEBUG - tour_mode_choice: write_table cache: expression_values\r\n", - "INFO - tour_mode_choice_simulate work choices_df top 10 value counts:\r\n", - "DRIVEALONEFREE 665\r\n", - "WALK_LOC 324\r\n", - "WALK_LRF 254\r\n", - "SHARED2FREE 216\r\n", - "TNC_SINGLE 177\r\n", - "SHARED3FREE 138\r\n", - "WALK 137\r\n", - "BIKE 109\r\n", - "WALK_HVY 82\r\n", - "TAXI 10\r\n", - "Name: tour_mode, dtype: int64\r\n", - "DEBUG - tour_mode_choice: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - tour_mode_choice: write_table cache: override_choices\r\n", - "DEBUG - tour_mode_choice: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - tour_mode_choice: write_omnibus_choosers: output/estimation_data_bundle/tour_mode_choice/tour_mode_choice_values_combined.csv\r\n", - "INFO - tour_mode_choice: end estimation\r\n", - "INFO - tour_mode_choice_simulate all tour type choices top 10 value counts:\r\n", - "DRIVEALONEFREE 1357\r\n", - "SHARED2FREE 801\r\n", - "SHARED3FREE 701\r\n", - "WALK_LOC 601\r\n", - "WALK_LRF 593\r\n", - "WALK 456\r\n", - "TNC_SINGLE 312\r\n", - "BIKE 200\r\n", - "WALK_HVY 194\r\n", - "TNC_SHARED 53\r\n", - "Name: tour_mode, dtype: int64\r\n", - "Time to execute step 'tour_mode_choice_simulate': 16.74 s\r\n", - "Total time to execute iteration 1 with iteration value None: 16.74 s\r\n", - "INFO - #run_model running step atwork_subtour_frequency\r\n", - "Running step 'atwork_subtour_frequency'\r\n", - "INFO - Initialize Estimator for'atwork_subtour_frequency'\r\n", - "INFO - Running atwork_subtour_frequency with 2124 work tours\r\n", - "DEBUG - atwork_subtour_frequency: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_frequency/atwork_subtour_frequency_SPEC.csv\r\n", - "DEBUG - atwork_subtour_frequency: write_table write: atwork_subtour_frequency_coefficients.csv\r\n", - "DEBUG - atwork_subtour_frequency: write_table cache: choosers\r\n", - "INFO - atwork_subtour_frequency.simple_simulate Running adaptive_chunked_choosers with 2124 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2124 of 2124 choosers\r\n", - "DEBUG - atwork_subtour_frequency: write_table cache: expression_values\r\n", - "DEBUG - atwork_subtour_frequency: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - atwork_subtour_frequency: write_table cache: override_choices\r\n", - "DEBUG - atwork_subtour_frequency: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - atwork_subtour_frequency: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_frequency/atwork_subtour_frequency_values_combined.csv\r\n", - "INFO - atwork_subtour_frequency: end estimation\r\n", - "INFO - atwork_subtour_frequency top 10 value counts:\r\n", - " 3654\r\n", - "no_subtours 1664\r\n", - "eat 346\r\n", - "maint 62\r\n", - "business1 48\r\n", - "eat_business 3\r\n", - "business2 1\r\n", - "Name: atwork_subtour_frequency, dtype: int64\r\n", - "Time to execute step 'atwork_subtour_frequency': 0.75 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.75 s\r\n", - "INFO - #run_model running step atwork_subtour_destination\r\n", - "Running step 'atwork_subtour_destination'\r\n", - "INFO - Initialize Estimator for'atwork_subtour_destination'\r\n", - "DEBUG - atwork_subtour_destination: write_table write: atwork_subtour_destination_coefficients.csv\r\n", - "DEBUG - atwork_subtour_destination: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_destination/atwork_subtour_destination_SPEC.csv\r\n", - "DEBUG - atwork_subtour_destination: write_table write: size_terms\r\n", - "DEBUG - atwork_subtour_destination: write_table write: landuse\r\n", - "INFO - running atwork_subtour_destination.atwork.sample with 464 tours\r\n", - "INFO - Estimation mode for atwork_subtour_destination.atwork.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - atwork_subtour_destination.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 464 choosers\r\n", - "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", - "INFO - Running eval_interaction_utilities on 88160 rows\r\n", - "INFO - Estimation mode for atwork_subtour_destination.atwork.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Running atwork_subtour_destination.atwork.logsums with 88160 rows\r\n", - "INFO - atwork_subtour_destination.atwork.logsums.compute_logsums Running adaptive_chunked_choosers with 88160 choosers\r\n", - "INFO - Running chunk 1 of 1 with 88160 of 88160 choosers\r\n", - "DEBUG - atwork_subtour_destination: write_table cache: choosers\r\n", - "INFO - Running tour_destination_simulate with 464 persons\r\n", - "INFO - atwork_subtour_destination.atwork.simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 464 choosers and 88160 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", - "INFO - Running eval_interaction_utilities on 88160 rows\r\n", - "INFO - atwork_subtour_destination: eval_interaction_utilities write_interaction_expression_values atwork_subtour_destination.atwork.simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - atwork_subtour_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - atwork_subtour_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - atwork_subtour_destination: write_table cache: override_choices\r\n", - "DEBUG - atwork_subtour_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - atwork_subtour_destination: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_destination/atwork_subtour_destination_choosers_combined.csv\r\n", - "DEBUG - atwork_subtour_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "WARNING - atwork_subtour_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", - "DEBUG - atwork_subtour_destination: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_destination/atwork_subtour_destination_alternatives_combined.csv\r\n", - "INFO - atwork_subtour_destination: end estimation\r\n", - "INFO - destination summary:\r\n", - "count 464.000000\r\n", - "mean 92.616379\r\n", - "std 54.698856\r\n", - "min 1.000000\r\n", - "25% 44.750000\r\n", - "50% 90.000000\r\n", - "75% 139.250000\r\n", - "max 190.000000\r\n", - "Name: destination, dtype: float64\r\n", - "Time to execute step 'atwork_subtour_destination': 9.38 s\r\n", - "Total time to execute iteration 1 with iteration value None: 9.38 s\r\n", - "INFO - #run_model running step atwork_subtour_scheduling\r\n", - "Running step 'atwork_subtour_scheduling'\r\n", - "INFO - Initialize Estimator for'atwork_subtour_scheduling'\r\n", - "INFO - Running atwork_subtour_scheduling with 464 tours\r\n", - "DEBUG - atwork_subtour_scheduling: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_scheduling/atwork_subtour_scheduling_SPEC.csv\r\n", - "DEBUG - atwork_subtour_scheduling: write_table write: tour_scheduling_atwork_coefficients.csv\r\n", - "INFO - atwork_subtour_scheduling.tour_1 schedule_tours running 460 tour choices\r\n", - "INFO - atwork_subtour_scheduling.tour_1 Running adaptive_chunked_choosers with 460 choosers\r\n", - "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", - "INFO - atwork_subtour_scheduling.tour_1 schedule_tours running 460 tour choices\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: choosers\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - atwork_subtour_scheduling.tour_1.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 460 choosers and 33700 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", - "INFO - Running eval_interaction_utilities on 33700 rows\r\n", - "INFO - atwork_subtour_scheduling: eval_interaction_utilities write_interaction_expression_values atwork_subtour_scheduling.tour_1.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_expression_values\r\n", - "INFO - atwork_subtour_scheduling.tour_2 schedule_tours running 4 tour choices\r\n", - "INFO - atwork_subtour_scheduling.tour_2 Running adaptive_chunked_choosers with 4 choosers\r\n", - "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", - "INFO - atwork_subtour_scheduling.tour_2 schedule_tours running 4 tour choices\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: choosers\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_sample_alternatives\r\n", - "INFO - atwork_subtour_scheduling.tour_2.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 4 choosers and 223 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", - "INFO - Running eval_interaction_utilities on 223 rows\r\n", - "INFO - atwork_subtour_scheduling: eval_interaction_utilities write_interaction_expression_values atwork_subtour_scheduling.tour_2.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: interaction_expression_values\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - atwork_subtour_scheduling: write_table cache: override_choices\r\n", - "DEBUG - atwork_subtour_scheduling: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - atwork_subtour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_scheduling/atwork_subtour_scheduling_choosers_combined.csv\r\n", - "DEBUG - atwork_subtour_scheduling: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "DEBUG - atwork_subtour_scheduling: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_scheduling/atwork_subtour_scheduling_alternatives_combined.csv\r\n", - "INFO - atwork_subtour_scheduling: end estimation\r\n", - "Time to execute step 'atwork_subtour_scheduling': 5.79 s\r\n", - "Total time to execute iteration 1 with iteration value None: 5.79 s\r\n", - "INFO - #run_model running step atwork_subtour_mode_choice\r\n", - "Running step 'atwork_subtour_mode_choice'\r\n", - "INFO - Running atwork_subtour_mode_choice with 464 subtours\r\n", - "INFO - atwork_subtour_mode_choice tour_type top 10 value counts:\r\n", - "eat 349\r\n", - "maint 62\r\n", - "business 53\r\n", - "Name: tour_type, dtype: int64\r\n", - "INFO - Initialize Estimator for'atwork_subtour_mode_choice'\r\n", - "DEBUG - atwork_subtour_mode_choice: write_table write: tour_mode_choice_coefficients.csv\r\n", - "DEBUG - atwork_subtour_mode_choice: write_table write: coefficients_template\r\n", - "DEBUG - atwork_subtour_mode_choice: estimate.write_spec: output/estimation_data_bundle/atwork_subtour_mode_choice/atwork_subtour_mode_choice_SPEC.csv\r\n", - "WARNING - /mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/pandas/core/frame.py:3636: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", - " self[k1] = value[k2]\r\n", - "\r\n", - "WARNING - /mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/util/mode.py:129: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\r\n", - " choosers[trace_column_names] = choosers.index\r\n", - "\r\n", - "DEBUG - atwork_subtour_mode_choice: write_table cache: choosers\r\n", - "INFO - atwork_subtour_mode_choice.simple_simulate Running adaptive_chunked_choosers with 464 choosers\r\n", - "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", - "DEBUG - atwork_subtour_mode_choice: write_table cache: expression_values\r\n", - "DEBUG - atwork_subtour_mode_choice: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - atwork_subtour_mode_choice: write_table cache: override_choices\r\n", - "DEBUG - atwork_subtour_mode_choice: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - atwork_subtour_mode_choice: write_omnibus_choosers: output/estimation_data_bundle/atwork_subtour_mode_choice/atwork_subtour_mode_choice_values_combined.csv\r\n", - "INFO - atwork_subtour_mode_choice: end estimation\r\n", - "INFO - atwork_subtour_mode_choice choices top 10 value counts:\r\n", - "DRIVEALONEFREE 133\r\n", - "WALK 99\r\n", - "SHARED2FREE 95\r\n", - "SHARED3FREE 67\r\n", - "TNC_SINGLE 27\r\n", - "WALK_LOC 13\r\n", - "TNC_SHARED 11\r\n", - "BIKE 10\r\n", - "WALK_LRF 5\r\n", - "TAXI 2\r\n", - "Name: tour_mode, dtype: int64\r\n", - "Time to execute step 'atwork_subtour_mode_choice': 2.23 s\r\n", - "Total time to execute iteration 1 with iteration value None: 2.23 s\r\n", - "INFO - #run_model running step stop_frequency\r\n", - "Running step 'stop_frequency'\r\n", - "INFO - stop_frequency segments top 10 value counts:\r\n", - "work 2124\r\n", - "shopping 723\r\n", - "othdiscr 564\r\n", - "atwork 464\r\n", - "school 460\r\n", - "escort 403\r\n", - "othmaint 393\r\n", - "eatout 316\r\n", - "social 182\r\n", - "univ 149\r\n", - "Name: primary_purpose, dtype: int64\r\n", - "INFO - stop_frequency running segment work with 2124 chooser rows\r\n", - "INFO - Initialize Estimator for'work'\r\n", - "DEBUG - work: estimate.write_spec: output/estimation_data_bundle/stop_frequency/work/stop_frequency_SPEC.csv\r\n", - "DEBUG - work: write_table write: stop_frequency_coefficients_work.csv\r\n", - "DEBUG - work: write_table cache: choosers\r\n", - "INFO - stop_frequency.work.simple_simulate Running adaptive_chunked_choosers with 2124 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2124 of 2124 choosers\r\n", - "DEBUG - work: write_table cache: expression_values\r\n", - "DEBUG - work: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - work: write_table cache: override_choices\r\n", - "DEBUG - work: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - work: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/work/stop_frequency_values_combined.csv\r\n", - "INFO - work: end estimation\r\n", - "INFO - stop_frequency running segment school with 460 chooser rows\r\n", - "INFO - Initialize Estimator for'school'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - school: estimate.write_spec: output/estimation_data_bundle/stop_frequency/school/stop_frequency_SPEC.csv\r\n", - "DEBUG - school: write_table write: stop_frequency_coefficients_school.csv\r\n", - "DEBUG - school: write_table cache: choosers\r\n", - "INFO - stop_frequency.school.simple_simulate Running adaptive_chunked_choosers with 460 choosers\r\n", - "INFO - Running chunk 1 of 1 with 460 of 460 choosers\r\n", - "DEBUG - school: write_table cache: expression_values\r\n", - "DEBUG - school: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - school: write_table cache: override_choices\r\n", - "DEBUG - school: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - school: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/school/stop_frequency_values_combined.csv\r\n", - "INFO - school: end estimation\r\n", - "INFO - stop_frequency running segment univ with 149 chooser rows\r\n", - "INFO - Initialize Estimator for'univ'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - univ: estimate.write_spec: output/estimation_data_bundle/stop_frequency/univ/stop_frequency_SPEC.csv\r\n", - "DEBUG - univ: write_table write: stop_frequency_coefficients_univ.csv\r\n", - "DEBUG - univ: write_table cache: choosers\r\n", - "INFO - stop_frequency.univ.simple_simulate Running adaptive_chunked_choosers with 149 choosers\r\n", - "INFO - Running chunk 1 of 1 with 149 of 149 choosers\r\n", - "DEBUG - univ: write_table cache: expression_values\r\n", - "DEBUG - univ: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - univ: write_table cache: override_choices\r\n", - "DEBUG - univ: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - univ: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/univ/stop_frequency_values_combined.csv\r\n", - "INFO - univ: end estimation\r\n", - "INFO - stop_frequency running segment social with 182 chooser rows\r\n", - "INFO - Initialize Estimator for'social'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - social: estimate.write_spec: output/estimation_data_bundle/stop_frequency/social/stop_frequency_SPEC.csv\r\n", - "DEBUG - social: write_table write: stop_frequency_coefficients_social.csv\r\n", - "DEBUG - social: write_table cache: choosers\r\n", - "INFO - stop_frequency.social.simple_simulate Running adaptive_chunked_choosers with 182 choosers\r\n", - "INFO - Running chunk 1 of 1 with 182 of 182 choosers\r\n", - "DEBUG - social: write_table cache: expression_values\r\n", - "DEBUG - social: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - social: write_table cache: override_choices\r\n", - "DEBUG - social: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - social: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/social/stop_frequency_values_combined.csv\r\n", - "INFO - social: end estimation\r\n", - "INFO - stop_frequency running segment shopping with 723 chooser rows\r\n", - "INFO - Initialize Estimator for'shopping'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - shopping: estimate.write_spec: output/estimation_data_bundle/stop_frequency/shopping/stop_frequency_SPEC.csv\r\n", - "DEBUG - shopping: write_table write: stop_frequency_coefficients_shopping.csv\r\n", - "DEBUG - shopping: write_table cache: choosers\r\n", - "INFO - stop_frequency.shopping.simple_simulate Running adaptive_chunked_choosers with 723 choosers\r\n", - "INFO - Running chunk 1 of 1 with 723 of 723 choosers\r\n", - "DEBUG - shopping: write_table cache: expression_values\r\n", - "DEBUG - shopping: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - shopping: write_table cache: override_choices\r\n", - "DEBUG - shopping: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - shopping: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/shopping/stop_frequency_values_combined.csv\r\n", - "INFO - shopping: end estimation\r\n", - "INFO - stop_frequency running segment eatout with 316 chooser rows\r\n", - "INFO - Initialize Estimator for'eatout'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - eatout: estimate.write_spec: output/estimation_data_bundle/stop_frequency/eatout/stop_frequency_SPEC.csv\r\n", - "DEBUG - eatout: write_table write: stop_frequency_coefficients_eatout.csv\r\n", - "DEBUG - eatout: write_table cache: choosers\r\n", - "INFO - stop_frequency.eatout.simple_simulate Running adaptive_chunked_choosers with 316 choosers\r\n", - "INFO - Running chunk 1 of 1 with 316 of 316 choosers\r\n", - "DEBUG - eatout: write_table cache: expression_values\r\n", - "DEBUG - eatout: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - eatout: write_table cache: override_choices\r\n", - "DEBUG - eatout: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - eatout: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/eatout/stop_frequency_values_combined.csv\r\n", - "INFO - eatout: end estimation\r\n", - "INFO - stop_frequency running segment escort with 403 chooser rows\r\n", - "INFO - Initialize Estimator for'escort'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - escort: estimate.write_spec: output/estimation_data_bundle/stop_frequency/escort/stop_frequency_SPEC.csv\r\n", - "DEBUG - escort: write_table write: stop_frequency_coefficients_escort.csv\r\n", - "DEBUG - escort: write_table cache: choosers\r\n", - "INFO - stop_frequency.escort.simple_simulate Running adaptive_chunked_choosers with 403 choosers\r\n", - "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", - "DEBUG - escort: write_table cache: expression_values\r\n", - "DEBUG - escort: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - escort: write_table cache: override_choices\r\n", - "DEBUG - escort: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - escort: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/escort/stop_frequency_values_combined.csv\r\n", - "INFO - escort: end estimation\r\n", - "INFO - stop_frequency running segment othmaint with 393 chooser rows\r\n", - "INFO - Initialize Estimator for'othmaint'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - othmaint: estimate.write_spec: output/estimation_data_bundle/stop_frequency/othmaint/stop_frequency_SPEC.csv\r\n", - "DEBUG - othmaint: write_table write: stop_frequency_coefficients_othmaint.csv\r\n", - "DEBUG - othmaint: write_table cache: choosers\r\n", - "INFO - stop_frequency.othmaint.simple_simulate Running adaptive_chunked_choosers with 393 choosers\r\n", - "INFO - Running chunk 1 of 1 with 393 of 393 choosers\r\n", - "DEBUG - othmaint: write_table cache: expression_values\r\n", - "DEBUG - othmaint: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - othmaint: write_table cache: override_choices\r\n", - "DEBUG - othmaint: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - othmaint: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/othmaint/stop_frequency_values_combined.csv\r\n", - "INFO - othmaint: end estimation\r\n", - "INFO - stop_frequency running segment othdiscr with 564 chooser rows\r\n", - "INFO - Initialize Estimator for'othdiscr'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - othdiscr: estimate.write_spec: output/estimation_data_bundle/stop_frequency/othdiscr/stop_frequency_SPEC.csv\r\n", - "DEBUG - othdiscr: write_table write: stop_frequency_coefficients_othdiscr.csv\r\n", - "DEBUG - othdiscr: write_table cache: choosers\r\n", - "INFO - stop_frequency.othdiscr.simple_simulate Running adaptive_chunked_choosers with 564 choosers\r\n", - "INFO - Running chunk 1 of 1 with 564 of 564 choosers\r\n", - "DEBUG - othdiscr: write_table cache: expression_values\r\n", - "DEBUG - othdiscr: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - othdiscr: write_table cache: override_choices\r\n", - "DEBUG - othdiscr: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - othdiscr: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/othdiscr/stop_frequency_values_combined.csv\r\n", - "INFO - othdiscr: end estimation\r\n", - "INFO - stop_frequency running segment atwork with 464 chooser rows\r\n", - "INFO - Initialize Estimator for'atwork'\r\n", - "deleted output/estimation_data_bundle/stop_frequency/stop_frequency_model_settings.yaml\r\n", - "DEBUG - atwork: estimate.write_spec: output/estimation_data_bundle/stop_frequency/atwork/stop_frequency_SPEC.csv\r\n", - "DEBUG - atwork: write_table write: stop_frequency_coefficients_atwork.csv\r\n", - "DEBUG - atwork: write_table cache: choosers\r\n", - "INFO - stop_frequency.atwork.simple_simulate Running adaptive_chunked_choosers with 464 choosers\r\n", - "INFO - Running chunk 1 of 1 with 464 of 464 choosers\r\n", - "DEBUG - atwork: write_table cache: expression_values\r\n", - "DEBUG - atwork: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using tours.index\r\n", - "DEBUG - atwork: write_table cache: override_choices\r\n", - "DEBUG - atwork: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - atwork: write_omnibus_choosers: output/estimation_data_bundle/stop_frequency/atwork/stop_frequency_values_combined.csv\r\n", - "INFO - atwork: end estimation\r\n", - "INFO - stop_frequency top 10 value counts:\r\n", - "0out_0in 4040\r\n", - "0out_1in 636\r\n", - "1out_0in 421\r\n", - "0out_2in 178\r\n", - "1out_1in 164\r\n", - "0out_3in 91\r\n", - "2out_0in 62\r\n", - "1out_3in 50\r\n", - "1out_2in 44\r\n", - "2out_1in 31\r\n", - "Name: stop_frequency, dtype: int64\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "Time to execute step 'stop_frequency': 6.05 s\r\n", - "Total time to execute iteration 1 with iteration value None: 6.05 s\r\n", - "INFO - #run_model running step trip_purpose\r\n", - "Running step 'trip_purpose'\r\n", - "INFO - Initialize Estimator for'trip_purpose'\r\n", - "DEBUG - trip_purpose: write_table cache: choosers\r\n", - "DEBUG - trip_purpose: estimate.write_spec: output/estimation_data_bundle/trip_purpose/trip_purpose_PROBS_SPEC.csv\r\n", - "INFO - assign purpose to 5778 last outbound trips\r\n", - "INFO - assign purpose to 5778 last inbound trips\r\n", - "INFO - assign purpose to 2796 intermediate trips\r\n", - "INFO - trip_purpose Running adaptive_chunked_choosers with 2796 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2796 of 2796 choosers\r\n", - " primary_purpose outbound ... social othdiscr\r\n", - "trip_id ... \r\n", - "206561 univ True ... 0.048 0.050\r\n", - "9188185 univ True ... 0.048 0.050\r\n", - "9668377 univ True ... 0.048 0.050\r\n", - "9806797 univ False ... 0.067 0.113\r\n", - "10849837 univ False ... 0.067 0.113\r\n", - "... ... ... ... ... ...\r\n", - "2414259747 atwork True ... 0.004 0.014\r\n", - "2414259749 atwork False ... 0.019 0.018\r\n", - "2414488393 atwork True ... 0.004 0.014\r\n", - "2423389333 atwork False ... 0.019 0.018\r\n", - "2440963725 atwork False ... 0.019 0.018\r\n", - "\r\n", - "[2796 rows x 14 columns]\r\n", - "DEBUG - trip_purpose: write_table cache: probs\r\n", - "DEBUG - trip_purpose: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_purpose: write_table cache: override_choices\r\n", - "DEBUG - trip_purpose: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'choosers', 'probs']\r\n", - "DEBUG - trip_purpose: write_omnibus_choosers: output/estimation_data_bundle/trip_purpose/trip_purpose_values_combined.csv\r\n", - "INFO - trip_purpose: end estimation\r\n", - "Time to execute step 'trip_purpose': 0.33 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.33 s\r\n", - "INFO - #run_model running step trip_destination\r\n", - "Running step 'trip_destination'\r\n", - "INFO - Initialize Estimator for'trip_destination'\r\n", - "DEBUG - trip_destination: write_table write: trip_destination_coefficients.csv\r\n", - "DEBUG - trip_destination: estimate.write_spec: output/estimation_data_bundle/trip_destination/trip_destination_SPEC.csv\r\n", - "DEBUG - trip_destination: write_table write: size_terms\r\n", - "DEBUG - trip_destination: write_table write: landuse\r\n", - "INFO - Running trip_destination with 14352 trips\r\n", - "INFO - Running trip_destination.trip_num_1 with 2061 trips\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.atwork with 124 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.atwork.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 124 choosers\r\n", - "INFO - Running chunk 1 of 1 with 124 of 124 choosers\r\n", - "INFO - Running eval_interaction_utilities on 23560 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.atwork.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.atwork.trip_destination_sample : 0.09 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.atwork.compute_logsums with 23560 samples\r\n", - "INFO - trip_destination.trip_num_1.atwork.compute_logsums.od Running adaptive_chunked_choosers with 23560 choosers\r\n", - "INFO - Running chunk 1 of 1 with 23560 of 23560 choosers\r\n", - "INFO - trip_destination.trip_num_1.atwork.compute_logsums.dp Running adaptive_chunked_choosers with 23560 choosers\r\n", - "INFO - Running chunk 1 of 1 with 23560 of 23560 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.atwork.compute_logsums : 4.409 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 124 trips\r\n", - "INFO - trip_destination.trip_num_1.atwork.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 124 choosers and 23560 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 124 of 124 choosers\r\n", - "INFO - Running eval_interaction_utilities on 23560 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.atwork.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.atwork.trip_destination_simulate : 0.372 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.eatout with 66 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.eatout.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 66 choosers\r\n", - "INFO - Running chunk 1 of 1 with 66 of 66 choosers\r\n", - "INFO - Running eval_interaction_utilities on 12540 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.eatout.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.eatout.trip_destination_sample : 0.086 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.eatout.compute_logsums with 12540 samples\r\n", - "INFO - trip_destination.trip_num_1.eatout.compute_logsums.od Running adaptive_chunked_choosers with 12540 choosers\r\n", - "INFO - Running chunk 1 of 1 with 12540 of 12540 choosers\r\n", - "INFO - trip_destination.trip_num_1.eatout.compute_logsums.dp Running adaptive_chunked_choosers with 12540 choosers\r\n", - "INFO - Running chunk 1 of 1 with 12540 of 12540 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.eatout.compute_logsums : 3.345 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 66 trips\r\n", - "INFO - trip_destination.trip_num_1.eatout.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 66 choosers and 12540 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 66 of 66 choosers\r\n", - "INFO - Running eval_interaction_utilities on 12540 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.eatout.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.eatout.trip_destination_simulate : 0.266 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.escort with 117 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.escort.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.escort.sample.interaction_sample Running adaptive_chunked_choosers with 117 choosers\r\n", - "INFO - Running chunk 1 of 1 with 117 of 117 choosers\r\n", - "INFO - Running eval_interaction_utilities on 22230 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.escort.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.escort.trip_destination_sample : 0.119 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.escort.compute_logsums with 22230 samples\r\n", - "INFO - trip_destination.trip_num_1.escort.compute_logsums.od Running adaptive_chunked_choosers with 22230 choosers\r\n", - "INFO - Running chunk 1 of 1 with 22230 of 22230 choosers\r\n", - "INFO - trip_destination.trip_num_1.escort.compute_logsums.dp Running adaptive_chunked_choosers with 22230 choosers\r\n", - "INFO - Running chunk 1 of 1 with 22230 of 22230 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.escort.compute_logsums : 4.465 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 117 trips\r\n", - "INFO - trip_destination.trip_num_1.escort.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 117 choosers and 22230 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 117 of 117 choosers\r\n", - "INFO - Running eval_interaction_utilities on 22230 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.escort.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.escort.trip_destination_simulate : 0.431 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.othdiscr with 207 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 207 choosers\r\n", - "INFO - Running chunk 1 of 1 with 207 of 207 choosers\r\n", - "INFO - Running eval_interaction_utilities on 39330 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.othdiscr.trip_destination_sample : 0.117 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.othdiscr.compute_logsums with 39330 samples\r\n", - "INFO - trip_destination.trip_num_1.othdiscr.compute_logsums.od Running adaptive_chunked_choosers with 39330 choosers\r\n", - "INFO - Running chunk 1 of 1 with 39330 of 39330 choosers\r\n", - "INFO - trip_destination.trip_num_1.othdiscr.compute_logsums.dp Running adaptive_chunked_choosers with 39330 choosers\r\n", - "INFO - Running chunk 1 of 1 with 39330 of 39330 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.othdiscr.compute_logsums : 6.014 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 207 trips\r\n", - "INFO - trip_destination.trip_num_1.othdiscr.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 207 choosers and 39330 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 207 of 207 choosers\r\n", - "INFO - Running eval_interaction_utilities on 39330 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.othdiscr.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.othdiscr.trip_destination_simulate : 0.567 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.othmaint with 122 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 122 choosers\r\n", - "INFO - Running chunk 1 of 1 with 122 of 122 choosers\r\n", - "INFO - Running eval_interaction_utilities on 23180 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.othmaint.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.othmaint.trip_destination_sample : 0.095 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.othmaint.compute_logsums with 23180 samples\r\n", - "INFO - trip_destination.trip_num_1.othmaint.compute_logsums.od Running adaptive_chunked_choosers with 23180 choosers\r\n", - "INFO - Running chunk 1 of 1 with 23180 of 23180 choosers\r\n", - "INFO - trip_destination.trip_num_1.othmaint.compute_logsums.dp Running adaptive_chunked_choosers with 23180 choosers\r\n", - "INFO - Running chunk 1 of 1 with 23180 of 23180 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.othmaint.compute_logsums : 4.333 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 122 trips\r\n", - "INFO - trip_destination.trip_num_1.othmaint.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 122 choosers and 23180 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 122 of 122 choosers\r\n", - "INFO - Running eval_interaction_utilities on 23180 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.othmaint.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.othmaint.trip_destination_simulate : 0.417 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.school with 119 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.school.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.school.sample.interaction_sample Running adaptive_chunked_choosers with 119 choosers\r\n", - "INFO - Running chunk 1 of 1 with 119 of 119 choosers\r\n", - "INFO - Running eval_interaction_utilities on 22610 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.school.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.school.trip_destination_sample : 0.091 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.school.compute_logsums with 22610 samples\r\n", - "INFO - trip_destination.trip_num_1.school.compute_logsums.od Running adaptive_chunked_choosers with 22610 choosers\r\n", - "INFO - Running chunk 1 of 1 with 22610 of 22610 choosers\r\n", - "INFO - trip_destination.trip_num_1.school.compute_logsums.dp Running adaptive_chunked_choosers with 22610 choosers\r\n", - "INFO - Running chunk 1 of 1 with 22610 of 22610 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.school.compute_logsums : 4.473 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 119 trips\r\n", - "INFO - trip_destination.trip_num_1.school.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 119 choosers and 22610 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 119 of 119 choosers\r\n", - "INFO - Running eval_interaction_utilities on 22610 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.school.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.school.trip_destination_simulate : 0.423 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.shopping with 322 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.shopping.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 322 choosers\r\n", - "INFO - Running chunk 1 of 1 with 322 of 322 choosers\r\n", - "INFO - Running eval_interaction_utilities on 61180 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.shopping.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.shopping.trip_destination_sample : 0.16 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.shopping.compute_logsums with 61180 samples\r\n", - "INFO - trip_destination.trip_num_1.shopping.compute_logsums.od Running adaptive_chunked_choosers with 61180 choosers\r\n", - "INFO - Running chunk 1 of 1 with 61180 of 61180 choosers\r\n", - "INFO - trip_destination.trip_num_1.shopping.compute_logsums.dp Running adaptive_chunked_choosers with 61180 choosers\r\n", - "INFO - Running chunk 1 of 1 with 61180 of 61180 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.shopping.compute_logsums : 8.168 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 322 trips\r\n", - "INFO - trip_destination.trip_num_1.shopping.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 322 choosers and 61180 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 322 of 322 choosers\r\n", - "INFO - Running eval_interaction_utilities on 61180 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.shopping.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.shopping.trip_destination_simulate : 0.892 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.social with 50 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.social.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.social.sample.interaction_sample Running adaptive_chunked_choosers with 50 choosers\r\n", - "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", - "INFO - Running eval_interaction_utilities on 9500 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.social.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.social.trip_destination_sample : 0.074 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.social.compute_logsums with 9500 samples\r\n", - "INFO - trip_destination.trip_num_1.social.compute_logsums.od Running adaptive_chunked_choosers with 9500 choosers\r\n", - "INFO - Running chunk 1 of 1 with 9500 of 9500 choosers\r\n", - "INFO - trip_destination.trip_num_1.social.compute_logsums.dp Running adaptive_chunked_choosers with 9500 choosers\r\n", - "INFO - Running chunk 1 of 1 with 9500 of 9500 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.social.compute_logsums : 2.802 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 50 trips\r\n", - "INFO - trip_destination.trip_num_1.social.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 50 choosers and 9500 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 50 of 50 choosers\r\n", - "INFO - Running eval_interaction_utilities on 9500 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.social.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.social.trip_destination_simulate : 0.285 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.univ with 75 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.univ.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.univ.sample.interaction_sample Running adaptive_chunked_choosers with 75 choosers\r\n", - "INFO - Running chunk 1 of 1 with 75 of 75 choosers\r\n", - "INFO - Running eval_interaction_utilities on 14250 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.univ.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.univ.trip_destination_sample : 0.084 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.univ.compute_logsums with 14250 samples\r\n", - "INFO - trip_destination.trip_num_1.univ.compute_logsums.od Running adaptive_chunked_choosers with 14250 choosers\r\n", - "INFO - Running chunk 1 of 1 with 14250 of 14250 choosers\r\n", - "INFO - trip_destination.trip_num_1.univ.compute_logsums.dp Running adaptive_chunked_choosers with 14250 choosers\r\n", - "INFO - Running chunk 1 of 1 with 14250 of 14250 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.univ.compute_logsums : 3.17 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 75 trips\r\n", - "INFO - trip_destination.trip_num_1.univ.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 75 choosers and 14250 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 75 of 75 choosers\r\n", - "INFO - Running eval_interaction_utilities on 14250 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.univ.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.univ.trip_destination_simulate : 0.31 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_1.work with 859 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.work.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_1.work.sample.interaction_sample Running adaptive_chunked_choosers with 859 choosers\r\n", - "INFO - Running chunk 1 of 1 with 859 of 859 choosers\r\n", - "INFO - Running eval_interaction_utilities on 163210 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_1.work.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_1.work.trip_destination_sample : 0.278 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_1.work.compute_logsums with 163210 samples\r\n", - "INFO - trip_destination.trip_num_1.work.compute_logsums.od Running adaptive_chunked_choosers with 163210 choosers\r\n", - "INFO - Running chunk 1 of 1 with 163210 of 163210 choosers\r\n", - "INFO - trip_destination.trip_num_1.work.compute_logsums.dp Running adaptive_chunked_choosers with 163210 choosers\r\n", - "INFO - Running chunk 1 of 1 with 163210 of 163210 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_1.work.compute_logsums : 17.277 seconds (0.3 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 859 trips\r\n", - "INFO - trip_destination.trip_num_1.work.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 859 choosers and 163210 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 859 of 859 choosers\r\n", - "INFO - Running eval_interaction_utilities on 163210 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_1.work.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_1.work.trip_destination_simulate : 3.053 seconds (0.1 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2 with 542 trips\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.atwork with 18 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.atwork.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 18 choosers\r\n", - "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3420 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.atwork.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.atwork.trip_destination_sample : 0.07 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.atwork.compute_logsums with 3420 samples\r\n", - "INFO - trip_destination.trip_num_2.atwork.compute_logsums.od Running adaptive_chunked_choosers with 3420 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3420 of 3420 choosers\r\n", - "INFO - trip_destination.trip_num_2.atwork.compute_logsums.dp Running adaptive_chunked_choosers with 3420 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3420 of 3420 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.atwork.compute_logsums : 2.279 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 18 trips\r\n", - "INFO - trip_destination.trip_num_2.atwork.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 18 choosers and 3420 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 18 of 18 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3420 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.atwork.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.atwork.trip_destination_simulate : 0.383 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.eatout with 11 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.eatout.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 11 choosers\r\n", - "INFO - Running chunk 1 of 1 with 11 of 11 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2090 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.eatout.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.eatout.trip_destination_sample : 0.084 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.eatout.compute_logsums with 2090 samples\r\n", - "INFO - trip_destination.trip_num_2.eatout.compute_logsums.od Running adaptive_chunked_choosers with 2090 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2090 of 2090 choosers\r\n", - "INFO - trip_destination.trip_num_2.eatout.compute_logsums.dp Running adaptive_chunked_choosers with 2090 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2090 of 2090 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.eatout.compute_logsums : 2.19 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 11 trips\r\n", - "INFO - trip_destination.trip_num_2.eatout.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 11 choosers and 2090 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 11 of 11 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2090 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.eatout.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.eatout.trip_destination_simulate : 0.486 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.escort with 32 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.escort.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.escort.sample.interaction_sample Running adaptive_chunked_choosers with 32 choosers\r\n", - "INFO - Running chunk 1 of 1 with 32 of 32 choosers\r\n", - "INFO - Running eval_interaction_utilities on 6080 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.escort.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.escort.trip_destination_sample : 0.092 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.escort.compute_logsums with 6080 samples\r\n", - "INFO - trip_destination.trip_num_2.escort.compute_logsums.od Running adaptive_chunked_choosers with 6080 choosers\r\n", - "INFO - Running chunk 1 of 1 with 6080 of 6080 choosers\r\n", - "INFO - trip_destination.trip_num_2.escort.compute_logsums.dp Running adaptive_chunked_choosers with 6080 choosers\r\n", - "INFO - Running chunk 1 of 1 with 6080 of 6080 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.escort.compute_logsums : 2.883 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 32 trips\r\n", - "INFO - trip_destination.trip_num_2.escort.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 32 choosers and 6080 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 32 of 32 choosers\r\n", - "INFO - Running eval_interaction_utilities on 6080 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.escort.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.escort.trip_destination_simulate : 0.844 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.othdiscr with 56 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 56 choosers\r\n", - "INFO - Running chunk 1 of 1 with 56 of 56 choosers\r\n", - "INFO - Running eval_interaction_utilities on 10640 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.othdiscr.trip_destination_sample : 0.15 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.othdiscr.compute_logsums with 10640 samples\r\n", - "INFO - trip_destination.trip_num_2.othdiscr.compute_logsums.od Running adaptive_chunked_choosers with 10640 choosers\r\n", - "INFO - Running chunk 1 of 1 with 10640 of 10640 choosers\r\n", - "INFO - trip_destination.trip_num_2.othdiscr.compute_logsums.dp Running adaptive_chunked_choosers with 10640 choosers\r\n", - "INFO - Running chunk 1 of 1 with 10640 of 10640 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.othdiscr.compute_logsums : 4.791 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 56 trips\r\n", - "INFO - trip_destination.trip_num_2.othdiscr.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 56 choosers and 10640 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 56 of 56 choosers\r\n", - "INFO - Running eval_interaction_utilities on 10640 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.othdiscr.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.othdiscr.trip_destination_simulate : 0.442 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.othmaint with 53 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 53 choosers\r\n", - "INFO - Running chunk 1 of 1 with 53 of 53 choosers\r\n", - "INFO - Running eval_interaction_utilities on 10070 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.othmaint.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.othmaint.trip_destination_sample : 0.07 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.othmaint.compute_logsums with 10070 samples\r\n", - "INFO - trip_destination.trip_num_2.othmaint.compute_logsums.od Running adaptive_chunked_choosers with 10070 choosers\r\n", - "INFO - Running chunk 1 of 1 with 10070 of 10070 choosers\r\n", - "INFO - trip_destination.trip_num_2.othmaint.compute_logsums.dp Running adaptive_chunked_choosers with 10070 choosers\r\n", - "INFO - Running chunk 1 of 1 with 10070 of 10070 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.othmaint.compute_logsums : 2.728 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 53 trips\r\n", - "INFO - trip_destination.trip_num_2.othmaint.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 53 choosers and 10070 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 53 of 53 choosers\r\n", - "INFO - Running eval_interaction_utilities on 10070 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.othmaint.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.othmaint.trip_destination_simulate : 0.486 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.school with 29 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.school.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.school.sample.interaction_sample Running adaptive_chunked_choosers with 29 choosers\r\n", - "INFO - Running chunk 1 of 1 with 29 of 29 choosers\r\n", - "INFO - Running eval_interaction_utilities on 5510 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.school.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.school.trip_destination_sample : 0.055 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.school.compute_logsums with 5510 samples\r\n", - "INFO - trip_destination.trip_num_2.school.compute_logsums.od Running adaptive_chunked_choosers with 5510 choosers\r\n", - "INFO - Running chunk 1 of 1 with 5510 of 5510 choosers\r\n", - "INFO - trip_destination.trip_num_2.school.compute_logsums.dp Running adaptive_chunked_choosers with 5510 choosers\r\n", - "INFO - Running chunk 1 of 1 with 5510 of 5510 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.school.compute_logsums : 2.617 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 29 trips\r\n", - "INFO - trip_destination.trip_num_2.school.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 29 choosers and 5510 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 29 of 29 choosers\r\n", - "INFO - Running eval_interaction_utilities on 5510 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.school.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.school.trip_destination_simulate : 0.609 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.shopping with 80 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.shopping.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 80 choosers\r\n", - "INFO - Running chunk 1 of 1 with 80 of 80 choosers\r\n", - "INFO - Running eval_interaction_utilities on 15200 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.shopping.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.shopping.trip_destination_sample : 0.073 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.shopping.compute_logsums with 15200 samples\r\n", - "INFO - trip_destination.trip_num_2.shopping.compute_logsums.od Running adaptive_chunked_choosers with 15200 choosers\r\n", - "INFO - Running chunk 1 of 1 with 15200 of 15200 choosers\r\n", - "INFO - trip_destination.trip_num_2.shopping.compute_logsums.dp Running adaptive_chunked_choosers with 15200 choosers\r\n", - "INFO - Running chunk 1 of 1 with 15200 of 15200 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.shopping.compute_logsums : 3.393 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 80 trips\r\n", - "INFO - trip_destination.trip_num_2.shopping.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 80 choosers and 15200 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 80 of 80 choosers\r\n", - "INFO - Running eval_interaction_utilities on 15200 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.shopping.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.shopping.trip_destination_simulate : 0.622 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.social with 7 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.social.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.social.sample.interaction_sample Running adaptive_chunked_choosers with 7 choosers\r\n", - "INFO - Running chunk 1 of 1 with 7 of 7 choosers\r\n", - "INFO - Running eval_interaction_utilities on 1330 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.social.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.social.trip_destination_sample : 0.076 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.social.compute_logsums with 1330 samples\r\n", - "INFO - trip_destination.trip_num_2.social.compute_logsums.od Running adaptive_chunked_choosers with 1330 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1330 of 1330 choosers\r\n", - "INFO - trip_destination.trip_num_2.social.compute_logsums.dp Running adaptive_chunked_choosers with 1330 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1330 of 1330 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.social.compute_logsums : 2.247 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 7 trips\r\n", - "INFO - trip_destination.trip_num_2.social.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 7 choosers and 1330 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 7 of 7 choosers\r\n", - "INFO - Running eval_interaction_utilities on 1330 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.social.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.social.trip_destination_simulate : 0.414 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.univ with 20 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.univ.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.univ.sample.interaction_sample Running adaptive_chunked_choosers with 20 choosers\r\n", - "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3800 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.univ.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.univ.trip_destination_sample : 0.054 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.univ.compute_logsums with 3800 samples\r\n", - "INFO - trip_destination.trip_num_2.univ.compute_logsums.od Running adaptive_chunked_choosers with 3800 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3800 of 3800 choosers\r\n", - "INFO - trip_destination.trip_num_2.univ.compute_logsums.dp Running adaptive_chunked_choosers with 3800 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3800 of 3800 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.univ.compute_logsums : 2.624 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 20 trips\r\n", - "INFO - trip_destination.trip_num_2.univ.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 20 choosers and 3800 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 20 of 20 choosers\r\n", - "INFO - Running eval_interaction_utilities on 3800 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.univ.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.univ.trip_destination_simulate : 0.485 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_2.work with 236 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.work.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_2.work.sample.interaction_sample Running adaptive_chunked_choosers with 236 choosers\r\n", - "INFO - Running chunk 1 of 1 with 236 of 236 choosers\r\n", - "INFO - Running eval_interaction_utilities on 44840 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_2.work.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_2.work.trip_destination_sample : 0.121 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_2.work.compute_logsums with 44840 samples\r\n", - "INFO - trip_destination.trip_num_2.work.compute_logsums.od Running adaptive_chunked_choosers with 44840 choosers\r\n", - "INFO - Running chunk 1 of 1 with 44840 of 44840 choosers\r\n", - "INFO - trip_destination.trip_num_2.work.compute_logsums.dp Running adaptive_chunked_choosers with 44840 choosers\r\n", - "INFO - Running chunk 1 of 1 with 44840 of 44840 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_2.work.compute_logsums : 6.123 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 236 trips\r\n", - "INFO - trip_destination.trip_num_2.work.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 236 choosers and 44840 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 236 of 236 choosers\r\n", - "INFO - Running eval_interaction_utilities on 44840 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_2.work.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_2.work.trip_destination_simulate : 0.961 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3 with 193 trips\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.atwork with 3 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.atwork.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.atwork.sample.interaction_sample Running adaptive_chunked_choosers with 3 choosers\r\n", - "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", - "INFO - Running eval_interaction_utilities on 570 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.atwork.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.atwork.trip_destination_sample : 0.062 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.atwork.compute_logsums with 570 samples\r\n", - "INFO - trip_destination.trip_num_3.atwork.compute_logsums.od Running adaptive_chunked_choosers with 570 choosers\r\n", - "INFO - Running chunk 1 of 1 with 570 of 570 choosers\r\n", - "INFO - trip_destination.trip_num_3.atwork.compute_logsums.dp Running adaptive_chunked_choosers with 570 choosers\r\n", - "INFO - Running chunk 1 of 1 with 570 of 570 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.atwork.compute_logsums : 1.823 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 3 trips\r\n", - "INFO - trip_destination.trip_num_3.atwork.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 3 choosers and 570 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 3 of 3 choosers\r\n", - "INFO - Running eval_interaction_utilities on 570 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.atwork.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.atwork.trip_destination_simulate : 0.433 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.eatout with 4 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.eatout.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.eatout.sample.interaction_sample Running adaptive_chunked_choosers with 4 choosers\r\n", - "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", - "INFO - Running eval_interaction_utilities on 760 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.eatout.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.eatout.trip_destination_sample : 0.069 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.eatout.compute_logsums with 760 samples\r\n", - "INFO - trip_destination.trip_num_3.eatout.compute_logsums.od Running adaptive_chunked_choosers with 760 choosers\r\n", - "INFO - Running chunk 1 of 1 with 760 of 760 choosers\r\n", - "INFO - trip_destination.trip_num_3.eatout.compute_logsums.dp Running adaptive_chunked_choosers with 760 choosers\r\n", - "INFO - Running chunk 1 of 1 with 760 of 760 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.eatout.compute_logsums : 1.92 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 4 trips\r\n", - "INFO - trip_destination.trip_num_3.eatout.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 4 choosers and 760 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 4 of 4 choosers\r\n", - "INFO - Running eval_interaction_utilities on 760 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.eatout.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.eatout.trip_destination_simulate : 0.447 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.escort with 12 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.escort.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.escort.sample.interaction_sample Running adaptive_chunked_choosers with 12 choosers\r\n", - "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2280 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.escort.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.escort.trip_destination_sample : 0.048 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.escort.compute_logsums with 2280 samples\r\n", - "INFO - trip_destination.trip_num_3.escort.compute_logsums.od Running adaptive_chunked_choosers with 2280 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2280 of 2280 choosers\r\n", - "INFO - trip_destination.trip_num_3.escort.compute_logsums.dp Running adaptive_chunked_choosers with 2280 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2280 of 2280 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.escort.compute_logsums : 2.102 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 12 trips\r\n", - "INFO - trip_destination.trip_num_3.escort.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 12 choosers and 2280 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 12 of 12 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2280 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.escort.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.escort.trip_destination_simulate : 0.462 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.othdiscr with 22 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.othdiscr.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.othdiscr.sample.interaction_sample Running adaptive_chunked_choosers with 22 choosers\r\n", - "INFO - Running chunk 1 of 1 with 22 of 22 choosers\r\n", - "INFO - Running eval_interaction_utilities on 4180 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.othdiscr.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.othdiscr.trip_destination_sample : 0.071 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.othdiscr.compute_logsums with 4180 samples\r\n", - "INFO - trip_destination.trip_num_3.othdiscr.compute_logsums.od Running adaptive_chunked_choosers with 4180 choosers\r\n", - "INFO - Running chunk 1 of 1 with 4180 of 4180 choosers\r\n", - "INFO - trip_destination.trip_num_3.othdiscr.compute_logsums.dp Running adaptive_chunked_choosers with 4180 choosers\r\n", - "INFO - Running chunk 1 of 1 with 4180 of 4180 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.othdiscr.compute_logsums : 2.22 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 22 trips\r\n", - "INFO - trip_destination.trip_num_3.othdiscr.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 22 choosers and 4180 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 22 of 22 choosers\r\n", - "INFO - Running eval_interaction_utilities on 4180 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.othdiscr.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.othdiscr.trip_destination_simulate : 0.506 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.othmaint with 14 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.othmaint.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.othmaint.sample.interaction_sample Running adaptive_chunked_choosers with 14 choosers\r\n", - "INFO - Running chunk 1 of 1 with 14 of 14 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2660 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.othmaint.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.othmaint.trip_destination_sample : 0.066 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.othmaint.compute_logsums with 2660 samples\r\n", - "INFO - trip_destination.trip_num_3.othmaint.compute_logsums.od Running adaptive_chunked_choosers with 2660 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2660 of 2660 choosers\r\n", - "INFO - trip_destination.trip_num_3.othmaint.compute_logsums.dp Running adaptive_chunked_choosers with 2660 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2660 of 2660 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.othmaint.compute_logsums : 2.088 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 14 trips\r\n", - "INFO - trip_destination.trip_num_3.othmaint.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 14 choosers and 2660 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 14 of 14 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2660 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.othmaint.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.othmaint.trip_destination_simulate : 0.483 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.school with 13 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.school.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.school.sample.interaction_sample Running adaptive_chunked_choosers with 13 choosers\r\n", - "INFO - Running chunk 1 of 1 with 13 of 13 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2470 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.school.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.school.trip_destination_sample : 0.052 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.school.compute_logsums with 2470 samples\r\n", - "INFO - trip_destination.trip_num_3.school.compute_logsums.od Running adaptive_chunked_choosers with 2470 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2470 of 2470 choosers\r\n", - "INFO - trip_destination.trip_num_3.school.compute_logsums.dp Running adaptive_chunked_choosers with 2470 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2470 of 2470 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.school.compute_logsums : 2.017 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 13 trips\r\n", - "INFO - trip_destination.trip_num_3.school.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 13 choosers and 2470 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 13 of 13 choosers\r\n", - "INFO - Running eval_interaction_utilities on 2470 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.school.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.school.trip_destination_simulate : 0.458 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.shopping with 28 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.shopping.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 28 choosers\r\n", - "INFO - Running chunk 1 of 1 with 28 of 28 choosers\r\n", - "INFO - Running eval_interaction_utilities on 5320 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.shopping.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.shopping.trip_destination_sample : 0.078 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.shopping.compute_logsums with 5320 samples\r\n", - "INFO - trip_destination.trip_num_3.shopping.compute_logsums.od Running adaptive_chunked_choosers with 5320 choosers\r\n", - "INFO - Running chunk 1 of 1 with 5320 of 5320 choosers\r\n", - "INFO - trip_destination.trip_num_3.shopping.compute_logsums.dp Running adaptive_chunked_choosers with 5320 choosers\r\n", - "INFO - Running chunk 1 of 1 with 5320 of 5320 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.shopping.compute_logsums : 2.804 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 28 trips\r\n", - "INFO - trip_destination.trip_num_3.shopping.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 28 choosers and 5320 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 28 of 28 choosers\r\n", - "INFO - Running eval_interaction_utilities on 5320 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.shopping.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.shopping.trip_destination_simulate : 0.513 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.social with 2 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.social.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.social.sample.interaction_sample Running adaptive_chunked_choosers with 2 choosers\r\n", - "INFO - Running chunk 1 of 1 with 2 of 2 choosers\r\n", - "INFO - Running eval_interaction_utilities on 380 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.social.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.social.trip_destination_sample : 0.07 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.social.compute_logsums with 380 samples\r\n", - "INFO - trip_destination.trip_num_3.social.compute_logsums.od Running adaptive_chunked_choosers with 380 choosers\r\n", - "INFO - Running chunk 1 of 1 with 380 of 380 choosers\r\n", - "INFO - trip_destination.trip_num_3.social.compute_logsums.dp Running adaptive_chunked_choosers with 380 choosers\r\n", - "INFO - Running chunk 1 of 1 with 380 of 380 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.social.compute_logsums : 1.945 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 2 trips\r\n", - "INFO - trip_destination.trip_num_3.social.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 2 choosers and 380 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 2 of 2 choosers\r\n", - "INFO - Running eval_interaction_utilities on 380 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.social.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.social.trip_destination_simulate : 0.472 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.univ with 10 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.univ.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.univ.sample.interaction_sample Running adaptive_chunked_choosers with 10 choosers\r\n", - "INFO - Running chunk 1 of 1 with 10 of 10 choosers\r\n", - "INFO - Running eval_interaction_utilities on 1900 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.univ.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.univ.trip_destination_sample : 0.075 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.univ.compute_logsums with 1900 samples\r\n", - "INFO - trip_destination.trip_num_3.univ.compute_logsums.od Running adaptive_chunked_choosers with 1900 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1900 of 1900 choosers\r\n", - "INFO - trip_destination.trip_num_3.univ.compute_logsums.dp Running adaptive_chunked_choosers with 1900 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1900 of 1900 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.univ.compute_logsums : 1.925 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 10 trips\r\n", - "INFO - trip_destination.trip_num_3.univ.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 10 choosers and 1900 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 10 of 10 choosers\r\n", - "INFO - Running eval_interaction_utilities on 1900 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.univ.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.univ.trip_destination_simulate : 0.53 seconds (0.0 minutes)\r\n", - "INFO - choose_trip_destination trip_destination.trip_num_3.work with 85 trips\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.work.sample using unsampled alternatives short_circuit_choices\r\n", - "INFO - trip_destination.trip_num_3.work.sample.interaction_sample Running adaptive_chunked_choosers with 85 choosers\r\n", - "INFO - Running chunk 1 of 1 with 85 of 85 choosers\r\n", - "INFO - Running eval_interaction_utilities on 16150 rows\r\n", - "INFO - Estimation mode for trip_destination.trip_num_3.work.sample.interaction_sample using unsampled alternatives\r\n", - "INFO - Time to execute trip_destination.trip_num_3.work.trip_destination_sample : 0.083 seconds (0.0 minutes)\r\n", - "INFO - Running trip_destination.trip_num_3.work.compute_logsums with 16150 samples\r\n", - "INFO - trip_destination.trip_num_3.work.compute_logsums.od Running adaptive_chunked_choosers with 16150 choosers\r\n", - "INFO - Running chunk 1 of 1 with 16150 of 16150 choosers\r\n", - "INFO - trip_destination.trip_num_3.work.compute_logsums.dp Running adaptive_chunked_choosers with 16150 choosers\r\n", - "INFO - Running chunk 1 of 1 with 16150 of 16150 choosers\r\n", - "INFO - Time to execute trip_destination.trip_num_3.work.compute_logsums : 3.316 seconds (0.1 minutes)\r\n", - "DEBUG - trip_destination: write_table cache: choosers\r\n", - "INFO - Running trip_destination_simulate with 85 trips\r\n", - "INFO - trip_destination.trip_num_3.work.trip_dest_simulate.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 85 choosers and 16150 alternatives\r\n", - "INFO - Running chunk 1 of 1 with 85 of 85 choosers\r\n", - "INFO - Running eval_interaction_utilities on 16150 rows\r\n", - "INFO - trip_destination: eval_interaction_utilities write_interaction_expression_values trip_destination.trip_num_3.work.trip_dest_simulate.interaction_sample_simulate.eval_interaction_utils\r\n", - "DEBUG - trip_destination: write_table cache: interaction_expression_values\r\n", - "DEBUG - trip_destination: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_destination: write_table cache: override_choices\r\n", - "INFO - Time to execute trip_destination.trip_num_3.work.trip_destination_simulate : 0.63 seconds (0.0 minutes)\r\n", - "DEBUG - trip_destination: write_omnibus_table: choosers_combined table_names: ['choices', 'override_choices', 'choosers']\r\n", - "DEBUG - trip_destination: write_omnibus_choosers: output/estimation_data_bundle/trip_destination/trip_destination_choosers_combined.csv\r\n", - "DEBUG - trip_destination: write_omnibus_table: alternatives_combined table_names: ['interaction_sample_alternatives', 'interaction_expression_values']\r\n", - "WARNING - trip_destination: write_omnibus_table: alternatives_combined table 'interaction_sample_alternatives' not found\r\n", - "DEBUG - trip_destination: write_omnibus_choosers: output/estimation_data_bundle/trip_destination/trip_destination_alternatives_combined.csv\r\n", - "INFO - trip_destination: end estimation\r\n", - "Time to execute step 'trip_destination': 141.72 s\r\n", - "Total time to execute iteration 1 with iteration value None: 141.72 s\r\n", - "INFO - #run_model running step trip_scheduling\r\n", - "Running step 'trip_scheduling'\r\n", - "INFO - Initialize Estimator for'trip_scheduling'\r\n", - "DEBUG - trip_scheduling: estimate.write_spec: output/estimation_data_bundle/trip_scheduling/trip_scheduling_PROBS_SPEC.csv\r\n", - "DEBUG - trip_scheduling: write_table cache: choosers\r\n", - "INFO - trip_scheduling Running chunk 1 of 1 with 5778 of 5778 choosers\r\n", - "INFO - trip_scheduling.i1 scheduling 14352 trips within chunk 1\r\n", - "INFO - trip_scheduling.i1 236 failed\r\n", - "INFO - trip_scheduling.i2 scheduling 686 trips within chunk 1\r\n", - "INFO - trip_scheduling.i2 388 failed\r\n", - "INFO - trip_scheduling.i3 scheduling 622 trips within chunk 1\r\n", - "INFO - trip_scheduling.i3 376 failed\r\n", - "INFO - trip_scheduling.i4 scheduling 585 trips within chunk 1\r\n", - "INFO - trip_scheduling.i4 373 failed\r\n", - "INFO - trip_scheduling.i5 scheduling 563 trips within chunk 1\r\n", - "INFO - trip_scheduling.i5 370 failed\r\n", - "INFO - trip_scheduling.i6 scheduling 553 trips within chunk 1\r\n", - "INFO - trip_scheduling.i6 367 failed\r\n", - "INFO - trip_scheduling.i7 scheduling 546 trips within chunk 1\r\n", - "INFO - trip_scheduling.i7 367 failed\r\n", - "INFO - trip_scheduling.i8 scheduling 546 trips within chunk 1\r\n", - "INFO - trip_scheduling.i8 366 failed\r\n", - "INFO - trip_scheduling.i9 scheduling 542 trips within chunk 1\r\n", - "INFO - trip_scheduling.i9 365 failed\r\n", - "INFO - trip_scheduling.i10 scheduling 542 trips within chunk 1\r\n", - "INFO - trip_scheduling.i10 366 failed\r\n", - "INFO - trip_scheduling.i11 scheduling 542 trips within chunk 1\r\n", - "INFO - trip_scheduling.i11 363 failed\r\n", - "INFO - trip_scheduling.i12 scheduling 539 trips within chunk 1\r\n", - "INFO - trip_scheduling.i12 366 failed\r\n", - "INFO - trip_scheduling.i13 scheduling 539 trips within chunk 1\r\n", - "INFO - trip_scheduling.i13 364 failed\r\n", - "INFO - trip_scheduling.i14 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i14 361 failed\r\n", - "INFO - trip_scheduling.i15 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i15 363 failed\r\n", - "INFO - trip_scheduling.i16 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i16 364 failed\r\n", - "INFO - trip_scheduling.i17 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i17 365 failed\r\n", - "INFO - trip_scheduling.i18 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i18 363 failed\r\n", - "INFO - trip_scheduling.i19 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i19 363 failed\r\n", - "INFO - trip_scheduling.i20 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i20 362 failed\r\n", - "INFO - trip_scheduling.i21 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i21 365 failed\r\n", - "INFO - trip_scheduling.i22 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i22 363 failed\r\n", - "INFO - trip_scheduling.i23 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i23 365 failed\r\n", - "INFO - trip_scheduling.i24 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i24 365 failed\r\n", - "INFO - trip_scheduling.i25 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i25 363 failed\r\n", - "INFO - trip_scheduling.i26 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i26 364 failed\r\n", - "INFO - trip_scheduling.i27 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i27 365 failed\r\n", - "INFO - trip_scheduling.i28 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i28 365 failed\r\n", - "INFO - trip_scheduling.i29 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i29 363 failed\r\n", - "INFO - trip_scheduling.i30 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i30 363 failed\r\n", - "INFO - trip_scheduling.i31 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i31 365 failed\r\n", - "INFO - trip_scheduling.i32 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i32 364 failed\r\n", - "INFO - trip_scheduling.i33 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i33 364 failed\r\n", - "INFO - trip_scheduling.i34 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i34 364 failed\r\n", - "INFO - trip_scheduling.i35 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i35 363 failed\r\n", - "INFO - trip_scheduling.i36 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i36 363 failed\r\n", - "INFO - trip_scheduling.i37 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i37 364 failed\r\n", - "INFO - trip_scheduling.i38 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i38 364 failed\r\n", - "INFO - trip_scheduling.i39 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i39 364 failed\r\n", - "INFO - trip_scheduling.i40 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i40 365 failed\r\n", - "INFO - trip_scheduling.i41 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i41 365 failed\r\n", - "INFO - trip_scheduling.i42 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i42 365 failed\r\n", - "INFO - trip_scheduling.i43 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i43 366 failed\r\n", - "INFO - trip_scheduling.i44 scheduling 536 trips within chunk 1\r\n", - "INFO - trip_scheduling.i44 363 failed\r\n", - "INFO - trip_scheduling.i45 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i45 363 failed\r\n", - "INFO - trip_scheduling.i46 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i46 362 failed\r\n", - "INFO - trip_scheduling.i47 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i47 363 failed\r\n", - "INFO - trip_scheduling.i48 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i48 364 failed\r\n", - "INFO - trip_scheduling.i49 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i49 362 failed\r\n", - "INFO - trip_scheduling.i50 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i50 363 failed\r\n", - "INFO - trip_scheduling.i51 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i51 363 failed\r\n", - "INFO - trip_scheduling.i52 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i52 363 failed\r\n", - "INFO - trip_scheduling.i53 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i53 363 failed\r\n", - "INFO - trip_scheduling.i54 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i54 362 failed\r\n", - "INFO - trip_scheduling.i55 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i55 363 failed\r\n", - "INFO - trip_scheduling.i56 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i56 364 failed\r\n", - "INFO - trip_scheduling.i57 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i57 364 failed\r\n", - "INFO - trip_scheduling.i58 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i58 363 failed\r\n", - "INFO - trip_scheduling.i59 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i59 364 failed\r\n", - "INFO - trip_scheduling.i60 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i60 362 failed\r\n", - "INFO - trip_scheduling.i61 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i61 365 failed\r\n", - "INFO - trip_scheduling.i62 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i62 361 failed\r\n", - "INFO - trip_scheduling.i63 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i63 362 failed\r\n", - "INFO - trip_scheduling.i64 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i64 364 failed\r\n", - "INFO - trip_scheduling.i65 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i65 363 failed\r\n", - "INFO - trip_scheduling.i66 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i66 364 failed\r\n", - "INFO - trip_scheduling.i67 scheduling 533 trips within chunk 1\r\n", - "INFO - trip_scheduling.i67 363 failed\r\n", - "INFO - trip_scheduling.i68 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i68 364 failed\r\n", - "INFO - trip_scheduling.i69 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i69 362 failed\r\n", - "INFO - trip_scheduling.i70 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i70 360 failed\r\n", - "INFO - trip_scheduling.i71 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i71 361 failed\r\n", - "INFO - trip_scheduling.i72 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i72 363 failed\r\n", - "INFO - trip_scheduling.i73 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i73 363 failed\r\n", - "INFO - trip_scheduling.i74 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i74 363 failed\r\n", - "INFO - trip_scheduling.i75 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i75 362 failed\r\n", - "INFO - trip_scheduling.i76 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i76 362 failed\r\n", - "INFO - trip_scheduling.i77 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i77 363 failed\r\n", - "INFO - trip_scheduling.i78 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i78 361 failed\r\n", - "INFO - trip_scheduling.i79 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i79 363 failed\r\n", - "INFO - trip_scheduling.i80 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i80 361 failed\r\n", - "INFO - trip_scheduling.i81 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i81 363 failed\r\n", - "INFO - trip_scheduling.i82 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i82 362 failed\r\n", - "INFO - trip_scheduling.i83 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i83 363 failed\r\n", - "INFO - trip_scheduling.i84 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i84 363 failed\r\n", - "INFO - trip_scheduling.i85 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i85 362 failed\r\n", - "INFO - trip_scheduling.i86 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i86 358 failed\r\n", - "INFO - trip_scheduling.i87 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i87 361 failed\r\n", - "INFO - trip_scheduling.i88 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i88 361 failed\r\n", - "INFO - trip_scheduling.i89 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i89 362 failed\r\n", - "INFO - trip_scheduling.i90 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i90 362 failed\r\n", - "INFO - trip_scheduling.i91 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i91 362 failed\r\n", - "INFO - trip_scheduling.i92 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i92 362 failed\r\n", - "INFO - trip_scheduling.i93 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i93 362 failed\r\n", - "INFO - trip_scheduling.i94 scheduling 530 trips within chunk 1\r\n", - "INFO - trip_scheduling.i94 361 failed\r\n", - "INFO - trip_scheduling.i95 scheduling 526 trips within chunk 1\r\n", - "INFO - trip_scheduling.i95 360 failed\r\n", - "INFO - trip_scheduling.i96 scheduling 526 trips within chunk 1\r\n", - "INFO - trip_scheduling.i96 359 failed\r\n", - "INFO - trip_scheduling.i97 scheduling 526 trips within chunk 1\r\n", - "INFO - trip_scheduling.i97 359 failed\r\n", - "INFO - trip_scheduling.i98 scheduling 526 trips within chunk 1\r\n", - "INFO - trip_scheduling.i98 359 failed\r\n", - "INFO - trip_scheduling.i99 scheduling 526 trips within chunk 1\r\n", - "INFO - trip_scheduling.i99 359 failed\r\n", - "INFO - trip_scheduling.i100 scheduling 526 trips within chunk 1\r\n", - "INFO - dumping trip_scheduling.i100.outbound.num_2.failed_choosers\r\n", - "WARNING - trip_scheduling.i100.outbound.num_2 coercing 2 depart choices to most initial\r\n", - "INFO - dumping trip_scheduling.i100.outbound.num_3.failed_choosers\r\n", - "WARNING - trip_scheduling.i100.outbound.num_3 coercing 11 depart choices to most initial\r\n", - "INFO - dumping trip_scheduling.i100.outbound.num_4.failed_choosers\r\n", - "WARNING - trip_scheduling.i100.outbound.num_4 coercing 9 depart choices to most initial\r\n", - "INFO - dumping trip_scheduling.i100.inbound.num_1.failed_choosers\r\n", - "WARNING - trip_scheduling.i100.inbound.num_1 coercing 133 depart choices to most initial\r\n", - "INFO - dumping trip_scheduling.i100.inbound.num_2.failed_choosers\r\n", - "WARNING - trip_scheduling.i100.inbound.num_2 coercing 133 depart choices to most initial\r\n", - "INFO - dumping trip_scheduling.i100.inbound.num_3.failed_choosers\r\n", - "WARNING - trip_scheduling.i100.inbound.num_3 coercing 73 depart choices to most initial\r\n", - "INFO - trip_scheduling.i100 0 failed\r\n", - "DEBUG - trip_scheduling: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_scheduling: write_table cache: override_choices\r\n", - "DEBUG - trip_scheduling: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'choosers', 'probs']\r\n", - "WARNING - trip_scheduling: write_omnibus_table: values_combined table 'probs' not found\r\n", - "DEBUG - trip_scheduling: write_omnibus_choosers: output/estimation_data_bundle/trip_scheduling/trip_scheduling_values_combined.csv\r\n", - "INFO - trip_scheduling: end estimation\r\n", - "Time to execute step 'trip_scheduling': 19.54 s\r\n", - "Total time to execute iteration 1 with iteration value None: 19.54 s\r\n", - "INFO - #run_model running step trip_mode_choice\r\n", - "Running step 'trip_mode_choice'\r\n", - "INFO - Running trip_mode_choice with 14352 trips\r\n", - "INFO - primary_purpose top 10 value counts:\r\n", - "work 5428\r\n", - "shopping 1876\r\n", - "othdiscr 1413\r\n", - "school 1081\r\n", - "atwork 1073\r\n", - "othmaint 975\r\n", - "escort 967\r\n", - "eatout 713\r\n", - "social 423\r\n", - "univ 403\r\n", - "Name: primary_purpose, dtype: int64\r\n", - "INFO - Initialize Estimator for'trip_mode_choice'\r\n", - "DEBUG - trip_mode_choice: write_table write: trip_mode_choice_coefficients.csv\r\n", - "DEBUG - trip_mode_choice: write_table write: coefficients_template\r\n", - "DEBUG - trip_mode_choice: estimate.write_spec: output/estimation_data_bundle/trip_mode_choice/trip_mode_choice_SPEC.csv\r\n", - "INFO - trip_mode_choice tour_type 'atwork' (1073 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.atwork.simple_simulate Running adaptive_chunked_choosers with 1073 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1073 of 1073 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'eatout' (713 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.eatout.simple_simulate Running adaptive_chunked_choosers with 713 choosers\r\n", - "INFO - Running chunk 1 of 1 with 713 of 713 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'escort' (967 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.escort.simple_simulate Running adaptive_chunked_choosers with 967 choosers\r\n", - "INFO - Running chunk 1 of 1 with 967 of 967 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'othdiscr' (1413 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.othdiscr.simple_simulate Running adaptive_chunked_choosers with 1413 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1413 of 1413 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'othmaint' (975 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.othmaint.simple_simulate Running adaptive_chunked_choosers with 975 choosers\r\n", - "INFO - Running chunk 1 of 1 with 975 of 975 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'school' (1081 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.school.simple_simulate Running adaptive_chunked_choosers with 1081 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1081 of 1081 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'shopping' (1876 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.shopping.simple_simulate Running adaptive_chunked_choosers with 1876 choosers\r\n", - "INFO - Running chunk 1 of 1 with 1876 of 1876 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'social' (423 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.social.simple_simulate Running adaptive_chunked_choosers with 423 choosers\r\n", - "INFO - Running chunk 1 of 1 with 423 of 423 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'univ' (403 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.univ.simple_simulate Running adaptive_chunked_choosers with 403 choosers\r\n", - "INFO - Running chunk 1 of 1 with 403 of 403 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "INFO - trip_mode_choice tour_type 'work' (5428 trips)\r\n", - "DEBUG - trip_mode_choice: write_table cache: choosers\r\n", - "INFO - trip_mode_choice.work.simple_simulate Running adaptive_chunked_choosers with 5428 choosers\r\n", - "INFO - Running chunk 1 of 1 with 5428 of 5428 choosers\r\n", - "DEBUG - trip_mode_choice: write_table cache: expression_values\r\n", - "DEBUG - trip_mode_choice: write_table cache: choices\r\n", - "DEBUG - get_survey_values: reindexing using trips.index\r\n", - "DEBUG - trip_mode_choice: write_table cache: override_choices\r\n", - "DEBUG - trip_mode_choice: write_omnibus_table: values_combined table_names: ['choices', 'override_choices', 'expression_values', 'choosers']\r\n", - "DEBUG - trip_mode_choice: write_omnibus_choosers: output/estimation_data_bundle/trip_mode_choice/trip_mode_choice_values_combined.csv\r\n", - "INFO - trip_mode_choice: end estimation\r\n", - "INFO - trip_modes top 10 value counts:\r\n", - "DRIVEALONEFREE 3864\r\n", - "SHARED2FREE 2283\r\n", - "SHARED3FREE 1921\r\n", - "WALK_LOC 1524\r\n", - "WALK_LRF 1427\r\n", - "WALK 1259\r\n", - "TNC_SINGLE 871\r\n", - "WALK_HVY 473\r\n", - "BIKE 469\r\n", - "TNC_SHARED 149\r\n", - "Name: tour_mode, dtype: int64\r\n", - "INFO - trip_mode_choice choices top 10 value counts:\r\n", - "DRIVEALONEFREE 4317\r\n", - "SHARED2FREE 1872\r\n", - "WALK_LOC 1828\r\n", - "WALK 1621\r\n", - "SHARED3FREE 1345\r\n", - "WALK_LRF 1189\r\n", - "TNC_SINGLE 1038\r\n", - "BIKE 434\r\n", - "TNC_SHARED 361\r\n", - "WALK_HVY 242\r\n", - "Name: trip_mode, dtype: int64\r\n", - "Time to execute step 'trip_mode_choice': 18.31 s\r\n", - "Total time to execute iteration 1 with iteration value None: 18.31 s\r\n", - "INFO - #run_model running step write_tables\r\n", - "Running step 'write_tables'\r\n", - "Time to execute step 'write_tables': 0.41 s\r\n", - "Total time to execute iteration 1 with iteration value None: 0.41 s\r\n", - "INFO - Time to execute run_model (29 models) : 713.015 seconds (11.9 minutes)\r\n", - "INFO - MainProcess high water mark rss: 1_529_487_360 (1.5 GB) timestamp: 09/05/2022 14:57:08 label:pipeline.run_model trip_destination finished\r\n", - "INFO - MainProcess high water mark uss: 0 (0 B) timestamp: 09/05/2022 14:45:55 label:pipeline.run before preload_injectables\r\n", - "INFO - Time to execute all models : 713.145 seconds (11.9 minutes)\r\n" - ] - } - ], + "execution_count": 4, + "outputs": [], "source": [ - "# run estimation mode TODO: smaller data set?\n", - "!activitysim run -c configs_estimation/configs -c configs -o output -d data_sf" + "# run estimation mode\n", + "#!activitysim run -c configs_estimation/configs -c configs -o output_sf -d data_sf" ], "metadata": { "collapsed": false, @@ -2827,12 +93,14 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 5, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ + "/mnt/c/Users/jan.zill/code/activitysim/activitysim/estimation/larch/simple_simulate.py:92: DtypeWarning: Columns (411) have mixed types.Specify dtype option on import or set low_memory=False.\n", + " chooser_data = _read_csv(chooser_data_file, index_col=values_index_col,)\n", "req_data does not request avail_ca or avail_co but it is set and being provided\n", "req_data does not request avail_ca or avail_co but it is set and being provided\n", "req_data does not request avail_ca or avail_co but it is set and being provided\n", @@ -2859,14 +127,6 @@ "problem: chosen-but-not-available (2 issues)\n", "problem: low-variance-data-co (1 issues)\n" ] - }, - { - "data": { - "text/plain": "-10094.898223413013" - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -2893,13 +153,15 @@ }, { "cell_type": "code", - "execution_count": 124, + "execution_count": 16, "outputs": [], "source": [ "probs_0 = pd.DataFrame(probs[0])\n", "m_0 = model._k_models[0]\n", "\n", - "probs_0.index = m_0.dataframes.data_ch.index" + "probs_0.index = m_0.dataframes.data_ch.index\n", + "\n", + "probs_0 = probs_0.rename(columns={v-1: k for k,v in data.settings[\"CONSTANTS\"][\"I_MODE_MAP\"].items()})" ], "metadata": { "collapsed": false, @@ -2910,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 17, "outputs": [ { "data": { @@ -2940,13 +202,13 @@ }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 18, "outputs": [], "source": [ "#data.chooser_data[['override_choice', 'override_choice_code']].drop_duplicates().reset_index(drop=True).set_index\\\n", "# ('override_choice_code').to_dict()['override_choice']\n", - "mode_map = pd.DataFrame.from_dict(data.settings[\"CONSTANTS\"][\"I_MODE_MAP\"], orient='index', columns=[\"mode_code\"])\\\n", - " .reset_index().rename(columns={\"index\": \"mode_name\"})" + "#mode_map = pd.DataFrame.from_dict(data.settings[\"CONSTANTS\"][\"I_MODE_MAP\"], orient='index', columns=[\"mode_code\"])\\\n", + "# .reset_index().rename(columns={\"index\": \"mode_name\"})" ], "metadata": { "collapsed": false, @@ -2957,39 +219,83 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 19, "outputs": [ { "data": { - "text/plain": " trip_id model_choice override_choice \\\ntour_id \n3021985 24175881 DRIVEALONEFREE DRIVEALONEFREE \n3021985 24175885 DRIVEALONEFREE DRIVEALONEFREE \n\n util_DRIVEALONEFREE_Unavailable \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_zero_auto_households \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_persons_less_than_16 \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_joint_tours \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_work \\\ntour_id \n3021985 0.0 \n3021985 0.0 \n\n util_DRIVEALONEFREE_In_vehicle_time \\\ntour_id \n3021985 11.50 \n3021985 11.81 \n\n util_DRIVEALONEFREE_Terminal_time ... \\\ntour_id ... \n3021985 4.14944 ... \n3021985 4.14944 ... \n\n drive_heavyrail_available_outbound \\\ntour_id \n3021985 False \n3021985 False \n\n drive_heavyrail_available_inbound drive_commuter_available_outbound \\\ntour_id \n3021985 False False \n3021985 False False \n\n drive_commuter_available_inbound walk_ferry_available \\\ntour_id \n3021985 False False \n3021985 False False \n\n drive_ferry_available distance distance_walk_od distance_bike_od \\\ntour_id \n3021985 False 5.96 5.99 5.99 \n3021985 False 5.99 5.99 5.99 \n\n override_choice_code \ntour_id \n3021985 1 \n3021985 1 \n\n[2 rows x 478 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_idmodel_choiceoverride_choiceutil_DRIVEALONEFREE_Unavailableutil_DRIVEALONEFREE_Unavailable_for_zero_auto_householdsutil_DRIVEALONEFREE_Unavailable_for_persons_less_than_16util_DRIVEALONEFREE_Unavailable_for_joint_toursutil_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_workutil_DRIVEALONEFREE_In_vehicle_timeutil_DRIVEALONEFREE_Terminal_time...drive_heavyrail_available_outbounddrive_heavyrail_available_inbounddrive_commuter_available_outbounddrive_commuter_available_inboundwalk_ferry_availabledrive_ferry_availabledistancedistance_walk_oddistance_bike_odoverride_choice_code
tour_id
302198524175881DRIVEALONEFREEDRIVEALONEFREE0.00.00.00.00.011.504.14944...FalseFalseFalseFalseFalseFalse5.965.995.991
302198524175885DRIVEALONEFREEDRIVEALONEFREE0.00.00.00.00.011.814.14944...FalseFalseFalseFalseFalseFalse5.995.995.991
\n

2 rows × 478 columns

\n
" + "text/plain": "Int64Index([ 2961920, 2970120, 2998943, 3013252, 3015794, 3021985,\n 3022354, 3025019, 3055523, 3060361,\n ...\n 308028857, 308037836, 308057680, 308057681, 308070308, 308070309,\n 308073875, 308090603, 308105896, 308122624],\n dtype='int64', name='_caseid_', length=2124)" + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m_0.dataframes.data_ch.index.unique()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 34, + "outputs": [ + { + "data": { + "text/plain": " trip_id model_choice override_choice \\\ntour_id \n2961920 23695361 WALK_LRF WALK_LRF \n2961920 23695365 WALK WALK_LRF \n\n util_DRIVEALONEFREE_Unavailable \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_zero_auto_households \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_persons_less_than_16 \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_joint_tours \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_work \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_In_vehicle_time \\\ntour_id \n2961920 5.36 \n2961920 4.91 \n\n util_DRIVEALONEFREE_Terminal_time ... \\\ntour_id ... \n2961920 10.45084 ... \n2961920 10.45084 ... \n\n drive_heavyrail_available_outbound \\\ntour_id \n2961920 False \n2961920 False \n\n drive_heavyrail_available_inbound drive_commuter_available_outbound \\\ntour_id \n2961920 False False \n2961920 False False \n\n drive_commuter_available_inbound walk_ferry_available \\\ntour_id \n2961920 False False \n2961920 False False \n\n drive_ferry_available distance distance_walk_od distance_bike_od \\\ntour_id \n2961920 False 1.98 1.98 1.98 \n2961920 False 1.96 1.96 1.96 \n\n override_choice_code \ntour_id \n2961920 10 \n2961920 10 \n\n[2 rows x 478 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_idmodel_choiceoverride_choiceutil_DRIVEALONEFREE_Unavailableutil_DRIVEALONEFREE_Unavailable_for_zero_auto_householdsutil_DRIVEALONEFREE_Unavailable_for_persons_less_than_16util_DRIVEALONEFREE_Unavailable_for_joint_toursutil_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_workutil_DRIVEALONEFREE_In_vehicle_timeutil_DRIVEALONEFREE_Terminal_time...drive_heavyrail_available_outbounddrive_heavyrail_available_inbounddrive_commuter_available_outbounddrive_commuter_available_inboundwalk_ferry_availabledrive_ferry_availabledistancedistance_walk_oddistance_bike_odoverride_choice_code
tour_id
296192023695361WALK_LRFWALK_LRF0.00.00.00.00.05.3610.45084...FalseFalseFalseFalseFalseFalse1.981.981.9810
296192023695365WALKWALK_LRF0.00.00.00.00.04.9110.45084...FalseFalseFalseFalseFalseFalse1.961.961.9610
\n

2 rows × 478 columns

\n
" }, "metadata": {}, "output_type": "display_data" }, { "data": { - "text/plain": " 1 2 3 4 5 6 7 8 9 10 ... 12 13 \\\n_caseid_ ... \n3021985 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 \n3021985 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 \n\n 14 15 16 17 18 19 20 21 \n_caseid_ \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n\n[2 rows x 21 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
12345678910...12131415161718192021
_caseid_
30219851.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
30219851.00.00.00.00.00.00.00.00.00.0...0.00.00.00.00.00.00.00.00.00.0
\n

2 rows × 21 columns

\n
" + "text/plain": " 1 2 3 4 5 6 7 8 9 10 ... 12 13 \\\n_caseid_ ... \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 \n\n 14 15 16 17 18 19 20 21 \n_caseid_ \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n\n[2 rows x 21 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
12345678910...12131415161718192021
_caseid_
29619200.00.00.00.00.00.00.00.00.01.0...0.00.00.00.00.00.00.00.00.00.0
29619200.00.00.00.00.00.00.00.00.01.0...0.00.00.00.00.00.00.00.00.00.0
\n

2 rows × 21 columns

\n
" }, "metadata": {}, "output_type": "display_data" }, { "data": { - "text/plain": " 0 1 2 3 4 5 6 7 8 9 ... \\\n_caseid_ ... \n3021985 0.999871 0.0 0.0 0.0 0.0 0.0 0.000006 0.0 0.0 0.0 ... \n3021985 0.999886 0.0 0.0 0.0 0.0 0.0 0.000006 0.0 0.0 0.0 ... \n\n 11 12 13 14 15 16 17 18 19 20 \n_caseid_ \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.368530e-06 0.000021 0.000100 \n3021985 0.0 0.0 0.0 0.0 0.0 0.0 0.0 3.692194e-07 0.000011 0.000096 \n\n[2 rows x 21 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
0123456789...11121314151617181920
_caseid_
30219850.9998710.00.00.00.00.00.0000060.00.00.0...0.00.00.00.00.00.00.01.368530e-060.0000210.000100
30219850.9998860.00.00.00.00.00.0000060.00.00.0...0.00.00.00.00.00.00.03.692194e-070.0000110.000096
\n

2 rows × 21 columns

\n
" + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n_caseid_ \n2961920 0.0 0.0 2.027363e-06 0.0 \n2961920 0.0 0.0 2.943297e-09 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF ... \\\n_caseid_ ... \n2961920 1.061530e-06 0.0 0.111823 0.0 0.263130 0.625043 ... \n2961920 1.967559e-08 0.0 0.119603 0.0 0.272258 0.608139 ... \n\n WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY \\\n_caseid_ \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n_caseid_ \n2961920 0.0 2.489336e-08 4.965881e-08 4.072015e-07 \n2961920 0.0 2.435815e-08 4.996681e-08 4.311721e-07 \n\n[2 rows x 21 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRF...WALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
_caseid_
29619200.00.02.027363e-060.01.061530e-060.00.1118230.00.2631300.625043...0.00.00.00.00.00.00.02.489336e-084.965881e-084.072015e-07
29619200.00.02.943297e-090.01.967559e-080.00.1196030.00.2722580.608139...0.00.00.00.00.00.00.02.435815e-084.996681e-084.311721e-07
\n

2 rows × 21 columns

\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "23695361\n" + ] + }, + { + "data": { + "text/plain": " SHARED2FREE SHARED3FREE WALK WALK_LOC WALK_LRF \\\n_caseid_ \n2961920 2.027363e-06 1.061530e-06 0.111823 0.263130 0.625043 \n2961920 2.943297e-09 1.967559e-08 0.119603 0.272258 0.608139 \n\n TAXI TNC_SINGLE TNC_SHARED \n_caseid_ \n2961920 2.489336e-08 4.965881e-08 4.072015e-07 \n2961920 2.435815e-08 4.996681e-08 4.311721e-07 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREEWALKWALK_LOCWALK_LRFTAXITNC_SINGLETNC_SHARED
_caseid_
29619202.027363e-061.061530e-060.1118230.2631300.6250432.489336e-084.965881e-084.072015e-07
29619202.943297e-091.967559e-080.1196030.2722580.6081392.435815e-084.996681e-084.311721e-07
\n
" }, "metadata": {}, "output_type": "display_data" } ], "source": [ - "tour_id_to_check = 3021985\n", + "tour_id_to_check = 2961920\n", "\n", "display(data.chooser_data.loc[data.chooser_data.index == tour_id_to_check])\n", "display(m_0.dataframes.data_ch.loc[m_0.dataframes.data_ch.index == tour_id_to_check])\n", - "display(probs_0.loc[probs_0.index == tour_id_to_check])" + "display(probs_0.loc[probs_0.index == tour_id_to_check])\n", + "\n", + "# choose the first trip to look into probabilities below\n", + "trip_id_to_check = data.chooser_data.loc[data.chooser_data.index == tour_id_to_check].trip_id.values[0]\n", + "print(trip_id_to_check)\n", + "res_ = probs_0.loc[(probs_0.index == tour_id_to_check)]\n", + "display(res_.loc[:, (res_ != 0).any(axis=0)])" ], "metadata": { "collapsed": false, @@ -2999,12 +305,18 @@ } }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 46, + "outputs": [], "source": [ - "## now use my code to calculate probabilities" + "data.chooser_data.loc[data.chooser_data.index.isin(m_0.dataframes.data_ch.index)].to_csv(os.path.join(example_dir, \"choosers_larch.csv\"))\n", + "probs_0.to_csv(os.path.join(example_dir, \"probabilities_larch.csv\"))" ], "metadata": { - "collapsed": false + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } } }, { diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 84605dcd55..0843c4a2d6 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 24, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { "execution": { @@ -26,14 +26,23 @@ }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 25, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { "execution": { @@ -52,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 43, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -82,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 27, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -101,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 28, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -121,7 +130,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 29, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -140,7 +149,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 30, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -167,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 31, "outputs": [], "source": [ "## need to do this before loading checkpoint tables\n", @@ -185,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 45, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -202,7 +211,6 @@ "name": "stderr", "output_type": "stream", "text": [ - "setting trace_hh_id is wrong type, should be an int, but was \n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -247,8 +255,8 @@ "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n", - "CPU times: user 16.9 s, sys: 2.78 s, total: 19.7 s\n", - "Wall time: 14.7 s\n" + "CPU times: user 16.3 s, sys: 2.48 s, total: 18.8 s\n", + "Wall time: 14.1 s\n" ] } ], @@ -260,14 +268,14 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 46, "outputs": [ { "data": { "text/plain": " trip_mode_fru trip_mode_asim\nWALK 308 304\nWALK_LOC 87 85\nWALK_LRF 54 53\nBIKE 15 17\nTNC_SINGLE 7 4\nDRIVEALONEFREE 5 4\nSHARED2FREE 3 4\nSHARED3FREE 2 1\nWALK_HVY 1 4", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK308304
WALK_LOC8785
WALK_LRF5453
BIKE1517
TNC_SINGLE74
DRIVEALONEFREE54
SHARED2FREE34
SHARED3FREE21
WALK_HVY14
\n
" }, - "execution_count": 10, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -287,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 47, "outputs": [ { "data": { @@ -315,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 48, "outputs": [], "source": [ "#ls_ = np.abs(((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru))\n", @@ -349,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 49, "outputs": [ { "name": "stderr", @@ -390,7 +398,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 51, "outputs": [ { "name": "stderr", @@ -403,11 +411,24 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'social' (100000 trips)\n", - "Done\n", - "CPU times: user 1min 1s, sys: 3.78 s, total: 1min 5s\n", - "Wall time: 1min 2s\n" + "trip_mode_choice tour_type 'social' (10000 trips)\n", + "Done\n" + ] + }, + { + "data": { + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.004600 0.000000e+00 0.000000 0.000300 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.042100 0.120600 0.832400 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0046000.000000e+000.0000000.0003000.0000000.0421000.1206000.832400
\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 7.16 s, sys: 594 ms, total: 7.75 s\n", + "Wall time: 6.98 s\n" ] } ], @@ -415,7 +436,7 @@ "%%time\n", "#prun\n", "\n", - "num_samples = 100000\n", + "num_samples = 10000\n", "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like 65s and 1e4 takes 8s\n", "# work 615236801 also in trace log\n", "# social 2464104885 642446345 1767182945\n", @@ -743,6 +764,108 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 52, + "outputs": [], + "source": [ + "from activitysim.core import config\n", + "resume_after = \"trip_scheduling\"\n", + "model_name = \"trip_mode_choice\"\n", + "\n", + "pipeline.open_pipeline(resume_after)\n", + "# preload any bulky injectables (e.g. skims) not in pipeline\n", + "inject.get_injectable('preload_injectables', None)\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "#step_name = model_name\n", + "args = {}\n", + "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "trips = inject.get_table('trips')\n", + "tours_merged = inject.get_table('tours_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "\n", + "trace_label = 'trip_mode_choice'\n", + "model_settings_file_name = 'trip_mode_choice.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", + "mode_column_name = 'trip_mode'\n", + "\n", + "trips_df = trips.to_frame()\n", + "\n", + "tours_merged = tours_merged.to_frame()\n", + "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", + "\n", + "# - trips_merged - merge trips and tours_merged\n", + "trips_merged = pd.merge(\n", + " trips_df,\n", + " tours_merged,\n", + " left_on='tour_id',\n", + " right_index=True,\n", + " how=\"left\")\n", + "assert trips_merged.index.equals(trips.index)\n", + "\n", + "# setup skim keys\n", + "assert ('trip_period' not in trips_merged)\n", + "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", + "pipeline.close_pipeline()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 56, + "outputs": [ + { + "data": { + "text/plain": "Index(['person_id', 'household_id', 'primary_purpose', 'trip_num', 'outbound',\n 'trip_count', 'destination', 'origin', 'tour_id', 'purpose',\n 'destination_logsum', 'depart', 'hhsize', 'age', 'auto_ownership',\n 'number_of_participants', 'tour_category', 'parent_tour_id',\n 'tour_mode', 'duration', 'value_of_time', 'tour_type',\n 'free_parking_at_work', 'trip_period'],\n dtype='object')" + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trips_merged.columns" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, From 4ee74ea7b9a291ca3ab413f4644698d423f99d2c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 10 May 2022 00:40:11 +1000 Subject: [PATCH 024/298] clean up --- notebooks/check_sf_probs.ipynb | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/notebooks/check_sf_probs.ipynb b/notebooks/check_sf_probs.ipynb index de5d82d521..c14f1578b6 100644 --- a/notebooks/check_sf_probs.ipynb +++ b/notebooks/check_sf_probs.ipynb @@ -234,7 +234,7 @@ "num_samples = 10000\n", "\n", "comp = compare_mode_share_simulation(trip_id_to_check, num_samples, larch_trips_merged, larch_probs)\n", - "comp.loc[(comp != 0).any(axis=1), :].applymap(lambda x: f\"{x:.3%}\")" + "comp.loc[(comp > 1e-3).any(axis=1), :].applymap(lambda x: f\"{x:.3%}\")" ], "metadata": { "collapsed": false, @@ -244,6 +244,30 @@ } } }, + { + "cell_type": "code", + "execution_count": 95, + "outputs": [ + { + "data": { + "text/plain": " me asim larch\nSHARED2FREE 0.0000 0.0000 2.027363e-06\nSHARED3FREE 0.0000 0.0000 1.061530e-06\nTAXI 0.0000 0.0000 2.489336e-08\nTNC_SHARED 0.0000 0.0000 4.072015e-07\nTNC_SINGLE 0.0000 0.0000 4.965881e-08\nWALK 0.1085 0.1166 1.118235e-01\nWALK_LOC 0.2615 0.2015 2.631296e-01\nWALK_LRF 0.6300 0.6819 6.250434e-01", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
measimlarch
SHARED2FREE0.00000.00002.027363e-06
SHARED3FREE0.00000.00001.061530e-06
TAXI0.00000.00002.489336e-08
TNC_SHARED0.00000.00004.072015e-07
TNC_SINGLE0.00000.00004.965881e-08
WALK0.10850.11661.118235e-01
WALK_LOC0.26150.20152.631296e-01
WALK_LRF0.63000.68196.250434e-01
\n
" + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "comp.loc[(comp > 0).any(axis=1), :]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, From 4d0d8232aa5ed9b97340c6f683163140e4f19272 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 10 May 2022 01:10:24 +1000 Subject: [PATCH 025/298] inspecting a couple of test cases --- notebooks/check_sf_probs.ipynb | 42 ++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/notebooks/check_sf_probs.ipynb b/notebooks/check_sf_probs.ipynb index c14f1578b6..623b946bc5 100644 --- a/notebooks/check_sf_probs.ipynb +++ b/notebooks/check_sf_probs.ipynb @@ -208,6 +208,30 @@ } } }, + { + "cell_type": "code", + "execution_count": 124, + "outputs": [ + { + "data": { + "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n23695361 0.000000 0.0 2.027363e-06 0.0 \n23695365 0.000000 0.0 2.943297e-09 0.0 \n23760961 0.999664 0.0 0.000000e+00 0.0 \n23760962 0.998934 0.0 0.000000e+00 0.0 \n23760963 0.997988 0.0 0.000000e+00 0.0 \n... ... ... ... ... \n2464724829 0.000000 0.0 3.268925e-05 0.0 \n2464847169 0.000000 0.0 2.321209e-03 0.0 \n2464847173 0.000000 0.0 1.269812e-03 0.0 \n2464980993 0.000000 0.0 8.280461e-05 0.0 \n2464980997 0.000000 0.0 6.298188e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n23695361 1.061530e-06 0.0 0.111823 0.0 0.263130 0.625043 \n23695365 1.967559e-08 0.0 0.119603 0.0 0.272258 0.608139 \n23760961 0.000000e+00 0.0 0.000010 0.0 0.000000 0.000000 \n23760962 0.000000e+00 0.0 0.000579 0.0 0.000000 0.000000 \n23760963 0.000000e+00 0.0 0.001509 0.0 0.000000 0.000000 \n... ... ... ... ... ... ... \n2464724829 3.022226e-05 0.0 0.691838 0.0 0.308044 0.000000 \n2464847169 1.213314e-03 0.0 0.249261 0.0 0.745641 0.000000 \n2464847173 7.535248e-04 0.0 0.186394 0.0 0.810411 0.000000 \n2464980993 4.326905e-05 0.0 0.003217 0.0 0.996572 0.000000 \n2464980997 3.791076e-05 0.0 0.003749 0.0 0.996046 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n23695361 0.0 0.0 0.0 0.0 0.0 0.0 \n23695365 0.0 0.0 0.0 0.0 0.0 0.0 \n23760961 0.0 0.0 0.0 0.0 0.0 0.0 \n23760962 0.0 0.0 0.0 0.0 0.0 0.0 \n23760963 0.0 0.0 0.0 0.0 0.0 0.0 \n... ... ... ... ... ... ... \n2464724829 0.0 0.0 0.0 0.0 0.0 0.0 \n2464847169 0.0 0.0 0.0 0.0 0.0 0.0 \n2464847173 0.0 0.0 0.0 0.0 0.0 0.0 \n2464980993 0.0 0.0 0.0 0.0 0.0 0.0 \n2464980997 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n23695361 0.0 0.0 2.489336e-08 4.965881e-08 4.072015e-07 \n23695365 0.0 0.0 2.435815e-08 4.996681e-08 4.311721e-07 \n23760961 0.0 0.0 4.724941e-05 1.754653e-04 1.035425e-04 \n23760962 0.0 0.0 1.066593e-04 2.780937e-04 1.021060e-04 \n23760963 0.0 0.0 1.039326e-04 2.850044e-04 1.143830e-04 \n... ... ... ... ... ... \n2464724829 0.0 0.0 3.606576e-05 4.836729e-07 1.874068e-05 \n2464847169 0.0 0.0 4.274883e-04 5.407950e-04 5.947334e-04 \n2464847173 0.0 0.0 3.063342e-04 4.098183e-04 4.563525e-04 \n2464980993 0.0 0.0 1.593300e-05 4.967854e-05 1.954461e-05 \n2464980997 0.0 0.0 2.239090e-05 6.033135e-05 2.206105e-05 \n\n[5428 rows x 21 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
236953610.0000000.02.027363e-060.01.061530e-060.00.1118230.00.2631300.6250430.00.00.00.00.00.00.00.02.489336e-084.965881e-084.072015e-07
236953650.0000000.02.943297e-090.01.967559e-080.00.1196030.00.2722580.6081390.00.00.00.00.00.00.00.02.435815e-084.996681e-084.311721e-07
237609610.9996640.00.000000e+000.00.000000e+000.00.0000100.00.0000000.0000000.00.00.00.00.00.00.00.04.724941e-051.754653e-041.035425e-04
237609620.9989340.00.000000e+000.00.000000e+000.00.0005790.00.0000000.0000000.00.00.00.00.00.00.00.01.066593e-042.780937e-041.021060e-04
237609630.9979880.00.000000e+000.00.000000e+000.00.0015090.00.0000000.0000000.00.00.00.00.00.00.00.01.039326e-042.850044e-041.143830e-04
..................................................................
24647248290.0000000.03.268925e-050.03.022226e-050.00.6918380.00.3080440.0000000.00.00.00.00.00.00.00.03.606576e-054.836729e-071.874068e-05
24648471690.0000000.02.321209e-030.01.213314e-030.00.2492610.00.7456410.0000000.00.00.00.00.00.00.00.04.274883e-045.407950e-045.947334e-04
24648471730.0000000.01.269812e-030.07.535248e-040.00.1863940.00.8104110.0000000.00.00.00.00.00.00.00.03.063342e-044.098183e-044.563525e-04
24649809930.0000000.08.280461e-050.04.326905e-050.00.0032170.00.9965720.0000000.00.00.00.00.00.00.00.01.593300e-054.967854e-051.954461e-05
24649809970.0000000.06.298188e-050.03.791076e-050.00.0037490.00.9960460.0000000.00.00.00.00.00.00.00.02.239090e-056.033135e-052.206105e-05
\n

5428 rows × 21 columns

\n
" + }, + "execution_count": 124, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "larch_probs" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, @@ -230,8 +254,10 @@ "source": [ "%%time\n", "\n", - "trip_id_to_check = 23695361\n", - "num_samples = 10000\n", + "trip_id_to_check = 23695361 # obvious results with 1e4 samples: 23695361 24106017\n", + "# better with 1e5 samples: 1932588125\n", + "# requires higher precision, check when time: 38280865\n", + "num_samples = 10000 # 1e5 takes about 80s\n", "\n", "comp = compare_mode_share_simulation(trip_id_to_check, num_samples, larch_trips_merged, larch_probs)\n", "comp.loc[(comp > 1e-3).any(axis=1), :].applymap(lambda x: f\"{x:.3%}\")" @@ -244,6 +270,18 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": 95, From fba872882d5221fc21775f8e6a1cc0a90e59c9b3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 10 May 2022 13:10:43 +1000 Subject: [PATCH 026/298] nest scales in asim seem to be all between 0 and 1, so not like in larch where they are non-increasing - re-adjust util calc. should really document this somewhere --- activitysim/core/logit.py | 3 - activitysim/core/simulate.py | 7 +- notebooks/check_sf_probs.ipynb | 75 ++++++++++++++++++---- notebooks/validate_frozen_impl.ipynb | 95 +++++++++++++++++----------- 4 files changed, 125 insertions(+), 55 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 40a3a8671f..adfafcef2e 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -386,7 +386,6 @@ def __init__(self, name=None, level=0): self.ancestors = [] self.alternatives = None self.coefficient = 0 - self.parent_scale = 1 def print(self): print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s " % @@ -461,7 +460,6 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients * coefficient nest.alternatives = alternatives nest.ancestors = parent_nest.ancestors + [name] - nest.parent_scale = parent_nest.coefficient if pre_order: yield spec, nest @@ -482,7 +480,6 @@ def _each_nest(spec, parent_nest, post_order): nest.product_of_coefficients = parent_nest.product_of_coefficients nest.ancestors = parent_nest.ancestors + [name] nest.coefficient = parent_nest.coefficient - nest.parent_scale = parent_nest.coefficient yield spec, nest diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index be03116968..3bfd4f7761 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -737,16 +737,13 @@ def compute_nested_utilities(raw_utilities, nest_spec): name = nest.name if nest.is_leaf: # do not scale here, do afterwards so recursive structure works - nested_utilities[name] = raw_utilities[name].astype(float) + nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients else: # the alternative nested_utilities will already have been computed due to post_order with np.errstate(divide='ignore'): nested_utilities[name] = \ nest.coefficient * np.log( - np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) - - if nest.level > 1: # root does not have parent. also, parent_scale seems to be 0. - nested_utilities[name] = nested_utilities[name] / nest.parent_scale + np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) return nested_utilities diff --git a/notebooks/check_sf_probs.ipynb b/notebooks/check_sf_probs.ipynb index 623b946bc5..dfa75b24a2 100644 --- a/notebooks/check_sf_probs.ipynb +++ b/notebooks/check_sf_probs.ipynb @@ -234,7 +234,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 142, "outputs": [ { "name": "stderr", @@ -247,14 +247,41 @@ "name": "stdout", "output_type": "stream", "text": [ - "trip_mode_choice tour_type 'work' (10000 trips)\n" + "trip_mode_choice tour_type 'work' (10000 trips)\n", + "Done\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'work' (10000 trips)\n", + "Done\n", + "CPU times: user 9.94 s, sys: 844 ms, total: 10.8 s\n", + "Wall time: 9.56 s\n" ] + }, + { + "data": { + "text/plain": " me asim larch\nSHARED2FREE 0.450% 0.400% 0.359%\nSHARED3FREE 0.220% 0.140% 0.188%\nTAXI 0.110% 0.160% 0.080%\nTNC_SINGLE 0.280% 0.270% 0.202%\nWALK 0.520% 0.480% 0.435%\nWALK_HVY 69.070% 70.030% 60.165%\nWALK_LOC 9.540% 9.310% 14.513%\nWALK_LRF 19.710% 19.120% 23.977%", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
measimlarch
SHARED2FREE0.450%0.400%0.359%
SHARED3FREE0.220%0.140%0.188%
TAXI0.110%0.160%0.080%
TNC_SINGLE0.280%0.270%0.202%
WALK0.520%0.480%0.435%
WALK_HVY69.070%70.030%60.165%
WALK_LOC9.540%9.310%14.513%
WALK_LRF19.710%19.120%23.977%
\n
" + }, + "execution_count": 142, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "%%time\n", "\n", - "trip_id_to_check = 23695361 # obvious results with 1e4 samples: 23695361 24106017\n", + "trip_id_to_check = 24106017 # obvious results with 1e4 samples: 23695361 24106017\n", "# better with 1e5 samples: 1932588125\n", "# requires higher precision, check when time: 38280865\n", "num_samples = 10000 # 1e5 takes about 80s\n", @@ -265,8 +292,19 @@ "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n", - "is_executing": true + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" } } }, @@ -284,20 +322,33 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 139, + "outputs": [], + "source": [ + "#comp.loc[(comp > 0).any(axis=1), :]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 140, "outputs": [ { "data": { - "text/plain": " me asim larch\nSHARED2FREE 0.0000 0.0000 2.027363e-06\nSHARED3FREE 0.0000 0.0000 1.061530e-06\nTAXI 0.0000 0.0000 2.489336e-08\nTNC_SHARED 0.0000 0.0000 4.072015e-07\nTNC_SINGLE 0.0000 0.0000 4.965881e-08\nWALK 0.1085 0.1166 1.118235e-01\nWALK_LOC 0.2615 0.2015 2.631296e-01\nWALK_LRF 0.6300 0.6819 6.250434e-01", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
measimlarch
SHARED2FREE0.00000.00002.027363e-06
SHARED3FREE0.00000.00001.061530e-06
TAXI0.00000.00002.489336e-08
TNC_SHARED0.00000.00004.072015e-07
TNC_SINGLE0.00000.00004.965881e-08
WALK0.10850.11661.118235e-01
WALK_LOC0.26150.20152.631296e-01
WALK_LRF0.63000.68196.250434e-01
\n
" + "text/plain": "0.252" }, - "execution_count": 95, + "execution_count": 140, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "comp.loc[(comp > 0).any(axis=1), :]" + "0.72 * 0.35" ], "metadata": { "collapsed": false, @@ -310,7 +361,9 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [], + "source": [ + "# check what happens if you multiply them - like Train at the lower level? but then 0\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK308304
WALK_LOC8785
WALK_LRF5453
BIKE1517
TNC_SINGLE74
DRIVEALONEFREE54
SHARED2FREE34
SHARED3FREE21
WALK_HVY14
\n" + "text/plain": " trip_mode_fru trip_mode_asim\nWALK 309 304\nWALK_LOC 85 85\nWALK_LRF 55 53\nBIKE 15 17\nTNC_SINGLE 7 4\nDRIVEALONEFREE 5 4\nSHARED2FREE 3 4\nSHARED3FREE 2 1\nWALK_HVY 1 4", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK309304
WALK_LOC8585
WALK_LRF5553
BIKE1517
TNC_SINGLE74
DRIVEALONEFREE54
SHARED2FREE34
SHARED3FREE21
WALK_HVY14
\n
" }, - "execution_count": 46, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -295,17 +286,14 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 16, "outputs": [ { - "data": { - "text/plain": "
", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "Logsums agree\n" + ] } ], "source": [ @@ -396,6 +384,41 @@ } } }, + { + "cell_type": "code", + "execution_count": 58, + "outputs": [ + { + "data": { + "text/plain": "{'name': 'root',\n 'coefficient': 1.0,\n 'alternatives': [{'name': 'AUTO',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'DRIVEALONE',\n 'coefficient': 0.35,\n 'alternatives': ['DRIVEALONEFREE', 'DRIVEALONEPAY']},\n {'name': 'SHAREDRIDE2',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED2FREE', 'SHARED2PAY']},\n {'name': 'SHAREDRIDE3',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED3FREE', 'SHARED3PAY']}]},\n {'name': 'NONMOTORIZED',\n 'coefficient': 0.72,\n 'alternatives': ['WALK', 'BIKE']},\n {'name': 'TRANSIT',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'WALKACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['WALK_LOC',\n 'WALK_LRF',\n 'WALK_EXP',\n 'WALK_HVY',\n 'WALK_COM']},\n {'name': 'DRIVEACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['DRIVE_LOC',\n 'DRIVE_LRF',\n 'DRIVE_EXP',\n 'DRIVE_HVY',\n 'DRIVE_COM']}]},\n {'name': 'RIDEHAIL',\n 'coefficient': 0.36,\n 'alternatives': ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']}]}" + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ns[0]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": 51, From e821b57b9dd6e9cd9596a3c3453cb96b5129482b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 10 May 2022 14:12:29 +1000 Subject: [PATCH 027/298] nest scaling comment --- activitysim/core/simulate.py | 15 +++ notebooks/check_sf_probs.ipynb | 18 +-- .../larch_probabilities_via_estimation.ipynb | 72 ++++++++++++ notebooks/validate_frozen_impl.ipynb | 107 +++++++----------- 4 files changed, 138 insertions(+), 74 deletions(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 3bfd4f7761..aba8d2340c 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -746,6 +746,21 @@ def compute_nested_utilities(raw_utilities, nest_spec): np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) return nested_utilities +# So it looks like TM1 was estimated such that the nest coefficient is the ratio of lower level and upper level +# nest in larch. This means the values can all be between 0 and 1, unlike for larch where they need to be decreasing +# going down the tree. In that world, the above would read +# for nest in logit.each_nest(nest_spec, post_order=True): +# name = nest.name +# if nest.is_leaf: +# # do not scale here, do afterwards so recursive structure works +# nested_utilities[name] = raw_utilities[name].astype(float) +# else: +# # the alternative nested_utilities will already have been computed due to post_order +# with np.errstate(divide='ignore'): +# nested_utilities[name] = \ +# nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) +# nested_utilities[name] /= parent_nest_scale # parent_nest_scale would need to be defined as part of nest +# # and would be = coeffiecient for leaves and for nests it would be that of the parent nest def compute_nested_exp_utilities(raw_utilities, nest_spec): diff --git a/notebooks/check_sf_probs.ipynb b/notebooks/check_sf_probs.ipynb index dfa75b24a2..eb5c355cad 100644 --- a/notebooks/check_sf_probs.ipynb +++ b/notebooks/check_sf_probs.ipynb @@ -234,7 +234,7 @@ }, { "cell_type": "code", - "execution_count": 142, + "execution_count": 144, "outputs": [ { "name": "stderr", @@ -247,7 +247,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "trip_mode_choice tour_type 'work' (10000 trips)\n", + "trip_mode_choice tour_type 'work' (100000 trips)\n", "Done\n" ] }, @@ -262,18 +262,18 @@ "name": "stdout", "output_type": "stream", "text": [ - "trip_mode_choice tour_type 'work' (10000 trips)\n", + "trip_mode_choice tour_type 'work' (100000 trips)\n", "Done\n", - "CPU times: user 9.94 s, sys: 844 ms, total: 10.8 s\n", - "Wall time: 9.56 s\n" + "CPU times: user 1min 10s, sys: 3.98 s, total: 1min 14s\n", + "Wall time: 1min 9s\n" ] }, { "data": { - "text/plain": " me asim larch\nSHARED2FREE 0.450% 0.400% 0.359%\nSHARED3FREE 0.220% 0.140% 0.188%\nTAXI 0.110% 0.160% 0.080%\nTNC_SINGLE 0.280% 0.270% 0.202%\nWALK 0.520% 0.480% 0.435%\nWALK_HVY 69.070% 70.030% 60.165%\nWALK_LOC 9.540% 9.310% 14.513%\nWALK_LRF 19.710% 19.120% 23.977%", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
measimlarch
SHARED2FREE0.450%0.400%0.359%
SHARED3FREE0.220%0.140%0.188%
TAXI0.110%0.160%0.080%
TNC_SINGLE0.280%0.270%0.202%
WALK0.520%0.480%0.435%
WALK_HVY69.070%70.030%60.165%
WALK_LOC9.540%9.310%14.513%
WALK_LRF19.710%19.120%23.977%
\n
" + "text/plain": " me asim larch\nSHARED2FREE 0.395% 0.420% 0.359%\nSHARED3FREE 0.214% 0.226% 0.188%\nTNC_SINGLE 0.212% 0.231% 0.202%\nWALK 0.488% 0.466% 0.435%\nWALK_HVY 69.439% 69.422% 60.165%\nWALK_LOC 9.697% 9.596% 14.513%\nWALK_LRF 19.390% 19.469% 23.977%", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
measimlarch
SHARED2FREE0.395%0.420%0.359%
SHARED3FREE0.214%0.226%0.188%
TNC_SINGLE0.212%0.231%0.202%
WALK0.488%0.466%0.435%
WALK_HVY69.439%69.422%60.165%
WALK_LOC9.697%9.596%14.513%
WALK_LRF19.390%19.469%23.977%
\n
" }, - "execution_count": 142, + "execution_count": 144, "metadata": {}, "output_type": "execute_result" } @@ -284,7 +284,7 @@ "trip_id_to_check = 24106017 # obvious results with 1e4 samples: 23695361 24106017\n", "# better with 1e5 samples: 1932588125\n", "# requires higher precision, check when time: 38280865\n", - "num_samples = 10000 # 1e5 takes about 80s\n", + "num_samples = 100000 # 1e5 takes about 80s\n", "\n", "comp = compare_mode_share_simulation(trip_id_to_check, num_samples, larch_trips_merged, larch_probs)\n", "comp.loc[(comp > 1e-3).any(axis=1), :].applymap(lambda x: f\"{x:.3%}\")" diff --git a/notebooks/larch_probabilities_via_estimation.ipynb b/notebooks/larch_probabilities_via_estimation.ipynb index db362e994c..f6105fb11a 100644 --- a/notebooks/larch_probabilities_via_estimation.ipynb +++ b/notebooks/larch_probabilities_via_estimation.ipynb @@ -319,6 +319,78 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# see if larch scales nest scale" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 46, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 46, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 46, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 0e80ea7dc5..cb12d91201 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -345,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 17, "outputs": [ { "name": "stderr", @@ -386,42 +386,7 @@ }, { "cell_type": "code", - "execution_count": 58, - "outputs": [ - { - "data": { - "text/plain": "{'name': 'root',\n 'coefficient': 1.0,\n 'alternatives': [{'name': 'AUTO',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'DRIVEALONE',\n 'coefficient': 0.35,\n 'alternatives': ['DRIVEALONEFREE', 'DRIVEALONEPAY']},\n {'name': 'SHAREDRIDE2',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED2FREE', 'SHARED2PAY']},\n {'name': 'SHAREDRIDE3',\n 'coefficient': 0.35,\n 'alternatives': ['SHARED3FREE', 'SHARED3PAY']}]},\n {'name': 'NONMOTORIZED',\n 'coefficient': 0.72,\n 'alternatives': ['WALK', 'BIKE']},\n {'name': 'TRANSIT',\n 'coefficient': 0.72,\n 'alternatives': [{'name': 'WALKACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['WALK_LOC',\n 'WALK_LRF',\n 'WALK_EXP',\n 'WALK_HVY',\n 'WALK_COM']},\n {'name': 'DRIVEACCESS',\n 'coefficient': 0.5,\n 'alternatives': ['DRIVE_LOC',\n 'DRIVE_LRF',\n 'DRIVE_EXP',\n 'DRIVE_HVY',\n 'DRIVE_COM']}]},\n {'name': 'RIDEHAIL',\n 'coefficient': 0.36,\n 'alternatives': ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']}]}" - }, - "execution_count": 58, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ns[0]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 51, + "execution_count": 18, "outputs": [ { "name": "stderr", @@ -440,8 +405,8 @@ }, { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.004600 0.000000e+00 0.000000 0.000300 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.042100 0.120600 0.832400 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0046000.000000e+000.0000000.0003000.0000000.0421000.1206000.832400
\n
" + "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.005000 0.000000e+00 0.000000 0.000300 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.043900 0.059400 0.891400 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0050000.000000e+000.0000000.0003000.0000000.0439000.0594000.891400
\n
" }, "metadata": {}, "output_type": "display_data" @@ -450,8 +415,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 7.16 s, sys: 594 ms, total: 7.75 s\n", - "Wall time: 6.98 s\n" + "CPU times: user 9.06 s, sys: 562 ms, total: 9.62 s\n", + "Wall time: 9.3 s\n" ] } ], @@ -493,6 +458,42 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# Dev corner" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, { "cell_type": "markdown", "source": [ @@ -663,30 +664,6 @@ } } }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, { "cell_type": "markdown", "source": [ From 7bfb80a80839a6df6326302a0ed09fd25e223713 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 15 May 2022 22:32:41 +1000 Subject: [PATCH 028/298] adds comment regarding potential random choice runtime improvement --- activitysim/core/logit.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index adfafcef2e..de292dc3a1 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -221,7 +221,12 @@ def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alter next_level_alts = nest_alternatives_by_name[choice_this_level] raise ValueError("This should never happen - no alternative found") - +# Note: this is relatively slow due to the apply. +# It could *maybe* be sped up by using the fact that the nesting structure is the same for all rows: Add ev1(0,1) to +# all entries (as is currently being done). Then, at each level, pick the maximum of the available composite +# alternatives and set the corresponding entry to 1 for each row, set all other alternatives at this level to zero. +# Once the tree is walked (all alternatives have been processed), take the product of the alternatives in each +# leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_choosers=None): """ walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') From 42901bf2e7a331529ccad5420e996fbb4c676617 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 16 May 2022 16:15:51 +1000 Subject: [PATCH 029/298] adds goodness of fit test and loop over all trips --- notebooks/fru_utils.py | 11 +- .../larch_probabilities_via_estimation.ipynb | 176 ++++++- notebooks/validate_frozen_impl.ipynb | 491 ++++++++++++++++-- 3 files changed, 632 insertions(+), 46 deletions(-) diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py index d466184af0..645db8f174 100644 --- a/notebooks/fru_utils.py +++ b/notebooks/fru_utils.py @@ -162,12 +162,13 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa return trips_merged, choices, nest_spec -def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check): - t_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, - trip_id_to_check=trip_id_to_check, num_samples=num_samples) +# def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check): +# t_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, +# trip_id_to_check=trip_id_to_check, num_samples=num_samples) +# sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0] +# return sim_mode_shares - sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0] - #sim_mode_shares.columns = ["mode_share_sim"] +def comp_mode_shares(base_probs, sim_mode_shares, trip_id_to_check): obs_probs = base_probs.loc[base_probs.index == trip_id_to_check].T obs_probs.columns = ["mode_share_obs"] ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how="outer").fillna(0) diff --git a/notebooks/larch_probabilities_via_estimation.ipynb b/notebooks/larch_probabilities_via_estimation.ipynb index f6105fb11a..1dc495ed64 100644 --- a/notebooks/larch_probabilities_via_estimation.ipynb +++ b/notebooks/larch_probabilities_via_estimation.ipynb @@ -23,6 +23,34 @@ "cell_type": "code", "execution_count": 1, "outputs": [], + "source": [ + "%load_ext autoreload" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [], + "source": [ + "%autoreload 2" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [], "source": [ "import os\n", "import sys\n", @@ -47,7 +75,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "outputs": [], "source": [ "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", @@ -63,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "outputs": [], "source": [ "# create estimation test example\n", @@ -78,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "outputs": [], "source": [ "# run estimation mode\n", @@ -93,7 +121,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "outputs": [ { "name": "stderr", @@ -153,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 8, "outputs": [], "source": [ "probs_0 = pd.DataFrame(probs[0])\n", @@ -172,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 9, "outputs": [ { "data": { @@ -200,6 +228,142 @@ } } }, + { + "cell_type": "code", + "execution_count": 11, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/mnt/c/Users/jan.zill/code/activitysim/test_estimation_sf\r\n" + ] + } + ], + "source": [ + "!pwd" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 14, + "outputs": [], + "source": [ + "#m_0.graph.to_png(filename=\"/mnt/c/Users/jan.zill/code/activitysim/test_estimation_sf/mtc_test.png\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 15, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'temp' is not defined", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_24538/1635136243.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mdisplay\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtemp\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;31mNameError\u001B[0m: name 'temp' is not defined" + ] + } + ], + "source": [ + "display(temp)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 17, + "outputs": [], + "source": [ + "subg = m_0.graph.subgraph_from(22)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 23, + "outputs": [ + { + "data": { + "text/plain": "", + "text/html": "" + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subg.to_png(filename=\"/mnt/c/Users/jan.zill/code/activitysim/test_estimation_sf/mtc_mode_choice_subgraph.png\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 22, + "outputs": [ + { + "data": { + "text/plain": "", + "text/html": "
\n\nTree\n\n\ncluster_elemental\n\nElemental Alternatives\n\n\n\n1\n\nDRIVEALONEFREE \n(1)\n\n\n\n2\n\nDRIVEALONEPAY \n(2)\n\n\n\n24\n\nSHAREDRIDE2 \n(24)\n\n\n\n25\n\nSHAREDRIDE3 \n(25)\n\n\n\n22\n\nAUTO \n(22)\n\n\n\n22->24\n\n\n\n\n\n22->25\n\n\n\n\n\n23\n\nDRIVEALONE \n(23)\n\n\n\n22->23\n\n\n\n\n\n23->1\n\n\n\n\n\n23->2\n\n\n\n\n\n0\n\nRoot\n\n\n
" + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m_0.graph.partial_figure(source=22)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": 18, diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index cb12d91201..49f3ed786c 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -28,6 +28,7 @@ }, "outputs": [], "source": [ + "import numba\n", "%load_ext autoreload" ] }, @@ -52,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 40, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -73,11 +74,12 @@ "import numpy as np\n", "import pandas as pd\n", "from numpy.random import default_rng\n", + "from statsmodels.stats.gof import powerdiscrepancy\n", "\n", "from activitysim.cli import run\n", "from activitysim.core import inject\n", "\n", - "from fru_utils import run_trip_mode_choice, comp_mode_shares" + "from fru_utils import run_trip_mode_choice, comp_mode_shares, mode_choice_for_trip" ] }, { @@ -140,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 7, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -167,7 +169,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "outputs": [], "source": [ "## need to do this before loading checkpoint tables\n", @@ -185,7 +187,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", "metadata": { "execution": { @@ -202,6 +204,7 @@ "name": "stderr", "output_type": "stream", "text": [ + "setting trace_hh_id is wrong type, should be an int, but was \n", "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" ] }, @@ -220,7 +223,8 @@ "trip_mode_choice tour_type 'social' (19 trips)\n", "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n" + "Done\n", + "Running with %d trips 482\n" ] }, { @@ -234,7 +238,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running with %d trips 482\n", "trip_mode_choice tour_type 'atwork' (27 trips)\n", "trip_mode_choice tour_type 'eatout' (33 trips)\n", "trip_mode_choice tour_type 'escort' (6 trips)\n", @@ -246,8 +249,8 @@ "trip_mode_choice tour_type 'univ' (26 trips)\n", "trip_mode_choice tour_type 'work' (168 trips)\n", "Done\n", - "CPU times: user 17.8 s, sys: 2.34 s, total: 20.2 s\n", - "Wall time: 16.7 s\n" + "CPU times: user 18.3 s, sys: 2.3 s, total: 20.6 s\n", + "Wall time: 17.8 s\n" ] } ], @@ -259,14 +262,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 10, "outputs": [ { "data": { "text/plain": " trip_mode_fru trip_mode_asim\nWALK 309 304\nWALK_LOC 85 85\nWALK_LRF 55 53\nBIKE 15 17\nTNC_SINGLE 7 4\nDRIVEALONEFREE 5 4\nSHARED2FREE 3 4\nSHARED3FREE 2 1\nWALK_HVY 1 4", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK309304
WALK_LOC8585
WALK_LRF5553
BIKE1517
TNC_SINGLE74
DRIVEALONEFREE54
SHARED2FREE34
SHARED3FREE21
WALK_HVY14
\n
" }, - "execution_count": 15, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -286,7 +289,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 11, "outputs": [ { "name": "stdout", @@ -311,7 +314,30 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 12, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.000000e+00\n" + ] + } + ], + "source": [ + "x_ = np.abs((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim)).values\n", + "print(f\"{x_.max():e}\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 13, "outputs": [], "source": [ "#ls_ = np.abs(((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru))\n", @@ -345,7 +371,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "outputs": [ { "name": "stderr", @@ -375,7 +401,8 @@ ], "source": [ "from fru_utils import get_stuff\n", - "t, ru, neu, nu, ns, nested_probs, base_probs = get_stuff() #do_these_purposes=[\"social\"]) #social" + "t, ru, neu, nu, ns, nested_probs, base_probs = get_stuff() #do_these_purposes=[\"social\"]) #social\n", + "bp = pd.concat(base_probs)" ], "metadata": { "collapsed": false, @@ -386,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 107, "outputs": [ { "name": "stderr", @@ -400,45 +427,401 @@ "output_type": "stream", "text": [ "trip_mode_choice tour_type 'social' (10000 trips)\n", - "Done\n" + "Done\n", + "CPU times: user 8.39 s, sys: 453 ms, total: 8.84 s\n", + "Wall time: 9.28 s\n" ] - }, + } + ], + "source": [ + "%%time\n", + "#prun\n", + "\n", + "num_samples = 10000\n", + "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like 65s and 1e4 takes 8s\n", + "# work 615236801 also in trace log\n", + "# social 2464104885 642446345 1767182945\n", + "\n", + "\n", + "#bp = base_probs[7] # 7 for social, 9 for work. Check t[t.index == trip_id_to_check] to see which purpose, and\n", + "# output above to see which index\n", + "# choose_individual_max_utility = False\n", + "#ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", + "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)\n", + "\n", + "choose_individual_max_utility = True\n", + "t_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility,\n", + " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", + "sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", + "ms_comp = comp_mode_shares(bp, sim_mode_shares, trip_id_to_check)\n", + "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "#### generic power discrepancy implementation in statsmodels\n", + "\n", + "lambd{float, str}\n", + "float : exponent a for power discrepancy\n", + "\n", + "‘loglikeratio’: a = 0\n", + "\n", + "‘freeman_tukey’: a = -0.5\n", + "\n", + "‘pearson’: a = 1 (standard chisquare test statistic)\n", + "\n", + "‘modified_loglikeratio’: a = -1\n", + "\n", + "‘cressie_read’: a = 2/3\n", + "\n", + "‘neyman’ : a = -2 (Neyman-modified chisquare, reference from a book?)\n", + "\n", + "note - they are all based on continuous approximations (x^2) so technically they might be inadequate for very small\n", + "occurences of counts" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 64, + "outputs": [ { "data": { - "text/plain": " SHARED2FREE SHARED3FREE TAXI TNC_SHARED TNC_SINGLE \\\nmode_share_obs 0.004883 4.701403e-14 0.000096 0.000287 0.000131 \ntrip_mode 0.005000 0.000000e+00 0.000000 0.000300 0.000000 \n\n WALK WALK_LOC WALK_LRF \nmode_share_obs 0.044905 0.059962 0.889736 \ntrip_mode 0.043900 0.059400 0.891400 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREETAXITNC_SHAREDTNC_SINGLEWALKWALK_LOCWALK_LRF
mode_share_obs0.0048834.701403e-140.0000960.0002870.0001310.0449050.0599620.889736
trip_mode0.0050000.000000e+000.0000000.0003000.0000000.0439000.0594000.891400
\n
" + "text/plain": "(0.3439655414093617, nan)" }, + "execution_count": 64, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" + } + ], + "source": [ + "# Note power tests will be inf when expected count is zero (any non-zero count will be impossible)\n", + "eps = 0 #1e-14\n", + "non_zero_shares = ms_comp.loc[(ms_comp.mode_share_obs > eps) & (ms_comp.trip_mode > eps)][[\"mode_share_obs\", \"trip_mode\"]]\n", + "ddof = non_zero_shares.shape[0] - 1 # what to use here? num alternatives - 1? What about zero expected count?\n", + "\n", + "non_zero_shares.trip_mode *= num_samples\n", + "\n", + "sum_ms = non_zero_shares.mode_share_obs.sum()\n", + "if sum_ms != 1.0:\n", + " if np.abs(1.0 - sum_ms) > 1e-1:\n", + " print(f\"NOT GOOD {sum_ms} should be one\")\n", + " #print(f\"sum ms is not one but {sum_ms}, re-normalising\")\n", + " non_zero_shares.mode_share_obs /= sum_ms\n", + "\n", + "powerdiscrepancy(non_zero_shares.trip_mode.values, non_zero_shares.mode_share_obs.values, lambd=0, ddof=ddof)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 99, + "outputs": [ + { + "data": { + "text/plain": "array([[4.17345080e-03, 5.98926219e-03, 5.00000000e-03],\n [1.45406892e-04, 6.18851051e-04, 3.00000000e-04],\n [4.13473933e-02, 4.66025328e-02, 4.39000000e-02],\n [5.64412252e-02, 6.25036058e-02, 5.94000000e-02],\n [8.87346329e-01, 8.95325012e-01, 8.91400000e-01]])" + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from statsmodels.stats.proportion import multinomial_proportions_confint\n", + "ci = multinomial_proportions_confint(non_zero_shares.trip_mode.values, alpha=0.999, method='goodman')\n", + "vals = non_zero_shares.trip_mode.values / num_samples\n", + "np.hstack((ci, vals.reshape((len(vals),1))))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 106, + "outputs": [ + { + "data": { + "text/plain": "array([ True, True, True, True, True])" + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numba\n", + "\n", + "@numba.jit\n", + "def within(lower, upper, x):\n", + " return (x >= lower) & (x <= upper)\n", + "\n", + "@numba.jit\n", + "def numpy_within(x):\n", + " lower = x[0]\n", + " upper = x[1]\n", + " val = x[2]\n", + " return within(lower, upper, val)\n", + "\n", + "test = np.hstack((ci, vals.reshape((len(vals),1))))\n", + "np.apply_along_axis(numpy_within, 1, test)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# #from scipy.special import rel_entr\n", + "# from scipy.stats import linregress\n", + "# linregress(ms_comp.mode_share_obs.values, ms_comp.trip_mode.values)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "# TODO for paper:\n", + "\n", + "automate such that for a given sample size we calculate all probs for ALL 482 trips. then scatterplot and lin reg\n", + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'atwork' (10000 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n", + "trip_mode_choice tour_type 'atwork' (10000 trips)\n", + "Done\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'atwork' (10000 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n", + "trip_mode_choice tour_type 'atwork' (10000 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n", + "trip_mode_choice tour_type 'atwork' (10000 trips)\n", + "Done\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'atwork' (10000 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n", + "trip_mode_choice tour_type 'atwork' (10000 trips)\n", + "Done\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] }, { "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 9.06 s, sys: 562 ms, total: 9.62 s\n", - "Wall time: 9.3 s\n" + "trip_mode_choice tour_type 'atwork' (10000 trips)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Done\n", + "trip_mode_choice tour_type 'atwork' (10000 trips)\n", + "Done\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'atwork' (10000 trips)\n", + "Done\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'atwork' (10000 trips)\n" ] } ], "source": [ "%%time\n", - "#prun\n", "\n", "num_samples = 10000\n", - "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like 65s and 1e4 takes 8s\n", - "# work 615236801 also in trace log\n", - "# social 2464104885 642446345 1767182945\n", "\n", - "bp = base_probs[7] # 7 for social, 9 for work. Check t[t.index == trip_id_to_check] to see which purpose, and\n", - "# output above to see which index\n", - "# choose_individual_max_utility = False\n", - "#ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", - "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)\n", + "trips_to_check = bp.index.values #[100:103]\n", "\n", - "choose_individual_max_utility = True\n", - "ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", - "display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" + "res = []\n", + "\n", + "for i, trip_id_to_check in enumerate(trips_to_check):\n", + " if i == 50:\n", + " print(f\"Processing {i} out of {trips_to_check.shape[0]}\")\n", + " _, c_, _ = mode_choice_for_trip(choose_individual_max_utility=True,\n", + " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", + " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", + " res.append(sim_mode_shares.rename(trip_id_to_check).to_frame().T)\n", + "\n", + "result_sim = pd.concat(res).fillna(0)" ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n", + "is_executing": true + } + } + }, + { + "cell_type": "code", + "execution_count": 139, + "outputs": [], + "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -482,6 +865,44 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# = bp.merge(result_sim, left_index=True, right_index=True, how=\"outer\").fillna(0)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "markdown", "source": [ From 3700a9e76decb7d4c0582ddf7cba759081a2edda Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 16 May 2022 17:43:32 +1000 Subject: [PATCH 030/298] some validation metrics --- notebooks/validate_frozen_impl.ipynb | 245 +++++++-------------------- 1 file changed, 60 insertions(+), 185 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 49f3ed786c..7444b9df2e 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -594,11 +594,7 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [ - "# #from scipy.special import rel_entr\n", - "# from scipy.stats import linregress\n", - "# linregress(ms_comp.mode_share_obs.values, ms_comp.trip_mode.values)" - ], + "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -624,172 +620,7 @@ { "cell_type": "code", "execution_count": null, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'atwork' (10000 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n", - "trip_mode_choice tour_type 'atwork' (10000 trips)\n", - "Done\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'atwork' (10000 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n", - "trip_mode_choice tour_type 'atwork' (10000 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n", - "trip_mode_choice tour_type 'atwork' (10000 trips)\n", - "Done\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'atwork' (10000 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n", - "trip_mode_choice tour_type 'atwork' (10000 trips)\n", - "Done\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'atwork' (10000 trips)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n", - "trip_mode_choice tour_type 'atwork' (10000 trips)\n", - "Done\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'atwork' (10000 trips)\n", - "Done\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'atwork' (10000 trips)\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "\n", @@ -807,7 +638,8 @@ " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", " res.append(sim_mode_shares.rename(trip_id_to_check).to_frame().T)\n", "\n", - "result_sim = pd.concat(res).fillna(0)" + "result_sim = pd.concat(res).fillna(0)\n", + "result_sim.to_csv(\"temp_res_sim_10ksamples.csv\")" ], "metadata": { "collapsed": false, @@ -819,9 +651,11 @@ }, { "cell_type": "code", - "execution_count": 139, + "execution_count": 158, "outputs": [], - "source": [], + "source": [ + "#result_sim.merge(bp, left_index=True, right_index=True, how=\"outer\", suffixes=[\"_sim\", \"_asim\"]).fillna(0)" + ], "metadata": { "collapsed": false, "pycharm": { @@ -831,9 +665,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 155, "outputs": [], - "source": [], + "source": [ + "merged_results = result_sim.reset_index().rename(columns={\"index\": \"trip_id\"}).melt(id_vars=[\"trip_id\"]).merge(\n", + " bp.reset_index().rename(columns={\"index\": \"trip_id\"}).melt(id_vars=[\"trip_id\"]),\n", + " on=[\"trip_id\", \"variable\"],\n", + " how=\"outer\",\n", + " suffixes=[\"_sim\", \"_obs\"]\n", + ").fillna(0)" + ], "metadata": { "collapsed": false, "pycharm": { @@ -843,9 +684,32 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 157, + "outputs": [ + { + "data": { + "text/plain": "" + }, + "execution_count": 157, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import seaborn as sns\n", + "\n", + "sns.scatterplot(data=merged_results, x=\"value_sim\", y=\"value_obs\")" + ], "metadata": { "collapsed": false, "pycharm": { @@ -855,9 +719,22 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 159, + "outputs": [ + { + "data": { + "text/plain": "LinregressResult(slope=0.999889299809943, intercept=5.271437621759412e-06, rvalue=0.9999938263765986, pvalue=0.0, stderr=3.49259911845371e-05, intercept_stderr=6.896656958760079e-06)" + }, + "execution_count": 159, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#from scipy.special import rel_entr\n", + "from scipy.stats import linregress\n", + "linregress(merged_results.value_sim.values, merged_results.value_obs.values)" + ], "metadata": { "collapsed": false, "pycharm": { @@ -881,9 +758,7 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [ - "# = bp.merge(result_sim, left_index=True, right_index=True, how=\"outer\").fillna(0)" - ], + "source": [], "metadata": { "collapsed": false, "pycharm": { From 75c16a28d4a9088ac810835efae880b3919ee879 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 16 May 2022 21:26:02 +1000 Subject: [PATCH 031/298] validation --- notebooks/validate_frozen_impl.ipynb | 352 +++++++++++++++++++++++---- 1 file changed, 304 insertions(+), 48 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 7444b9df2e..f62f539bcb 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 231, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -75,6 +75,8 @@ "import pandas as pd\n", "from numpy.random import default_rng\n", "from statsmodels.stats.gof import powerdiscrepancy\n", + "from statsmodels.stats.proportion import multinomial_proportions_confint\n", + "import numba\n", "\n", "from activitysim.cli import run\n", "from activitysim.core import inject\n", @@ -463,33 +465,6 @@ } } }, - { - "cell_type": "markdown", - "source": [ - "#### generic power discrepancy implementation in statsmodels\n", - "\n", - "lambd{float, str}\n", - "float : exponent a for power discrepancy\n", - "\n", - "‘loglikeratio’: a = 0\n", - "\n", - "‘freeman_tukey’: a = -0.5\n", - "\n", - "‘pearson’: a = 1 (standard chisquare test statistic)\n", - "\n", - "‘modified_loglikeratio’: a = -1\n", - "\n", - "‘cressie_read’: a = 2/3\n", - "\n", - "‘neyman’ : a = -2 (Neyman-modified chisquare, reference from a book?)\n", - "\n", - "note - they are all based on continuous approximations (x^2) so technically they might be inadequate for very small\n", - "occurences of counts" - ], - "metadata": { - "collapsed": false - } - }, { "cell_type": "code", "execution_count": 64, @@ -590,25 +565,10 @@ } } }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, { "cell_type": "markdown", "source": [ - "# TODO for paper:\n", - "\n", - "automate such that for a given sample size we calculate all probs for ALL 482 trips. then scatterplot and lin reg\n", - "\n" + "# All trips for paper\n" ], "metadata": { "collapsed": false, @@ -624,15 +584,14 @@ "source": [ "%%time\n", "\n", - "num_samples = 10000\n", + "num_samples = 10000 #about 8.5s per 10k per trip, so about 70mins for 10k sample. 1e5 is about 10 times as long, so\n", + "# about 11.5h\n", "\n", "trips_to_check = bp.index.values #[100:103]\n", "\n", "res = []\n", "\n", - "for i, trip_id_to_check in enumerate(trips_to_check):\n", - " if i == 50:\n", - " print(f\"Processing {i} out of {trips_to_check.shape[0]}\")\n", + "for trip_id_to_check in trips_to_check:\n", " _, c_, _ = mode_choice_for_trip(choose_individual_max_utility=True,\n", " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", @@ -742,6 +701,303 @@ } } }, + { + "cell_type": "markdown", + "source": [ + "#### generic power discrepancy implementation in statsmodels\n", + "\n", + "lambd{float, str}\n", + "float : exponent a for power discrepancy\n", + "\n", + "‘loglikeratio’: a = 0\n", + "\n", + "‘freeman_tukey’: a = -0.5\n", + "\n", + "‘pearson’: a = 1 (standard chisquare test statistic)\n", + "\n", + "‘modified_loglikeratio’: a = -1\n", + "\n", + "‘cressie_read’: a = 2/3\n", + "\n", + "‘neyman’ : a = -2 (Neyman-modified chisquare, reference from a book?)\n", + "\n", + "note - they are all based on continuous approximations (x^2) so technically they might be inadequate for very small\n", + "occurences of counts" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 64, + "outputs": [ + { + "data": { + "text/plain": "(0.3439655414093617, nan)" + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Note power tests will be inf when expected count is zero (any non-zero count will be impossible)\n", + "eps = 0 #1e-14\n", + "non_zero_shares = ms_comp.loc[(ms_comp.mode_share_obs > eps) & (ms_comp.trip_mode > eps)][[\"mode_share_obs\", \"trip_mode\"]]\n", + "ddof = non_zero_shares.shape[0] - 1 # what to use here? num alternatives - 1? What about zero expected count?\n", + "\n", + "non_zero_shares.trip_mode *= num_samples\n", + "\n", + "sum_ms = non_zero_shares.mode_share_obs.sum()\n", + "if sum_ms != 1.0:\n", + " if np.abs(1.0 - sum_ms) > 1e-1:\n", + " print(f\"NOT GOOD {sum_ms} should be one\")\n", + " #print(f\"sum ms is not one but {sum_ms}, re-normalising\")\n", + " non_zero_shares.mode_share_obs /= sum_ms\n", + "\n", + "powerdiscrepancy(non_zero_shares.trip_mode.values, non_zero_shares.mode_share_obs.values, lambd=0, ddof=ddof)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 99, + "outputs": [ + { + "data": { + "text/plain": "array([[4.17345080e-03, 5.98926219e-03, 5.00000000e-03],\n [1.45406892e-04, 6.18851051e-04, 3.00000000e-04],\n [4.13473933e-02, 4.66025328e-02, 4.39000000e-02],\n [5.64412252e-02, 6.25036058e-02, 5.94000000e-02],\n [8.87346329e-01, 8.95325012e-01, 8.91400000e-01]])" + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 106, + "outputs": [ + { + "data": { + "text/plain": "array([ True, True, True, True, True])" + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 230, + "outputs": [], + "source": [ + "# test_trip_res = merged_results.loc[merged_results.trip_id == 1045822217]\n", + "# ci = multinomial_proportions_confint(num_samples * test_trip_res.value_sim.values, alpha=0.999, method='goodman')\n", + "# np.hstack((test_trip_res[['value_obs']].to_numpy(), ci))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 275, + "outputs": [], + "source": [ + "@numba.jit\n", + "def within(lower, upper, x):\n", + " return (x >= lower) & (x <= upper)\n", + "\n", + "@numba.jit\n", + "def numpy_within(x):\n", + " lower = x[0]\n", + " upper = x[1]\n", + " val = x[2]\n", + " return within(lower, upper, val)\n", + "\n", + "def all_within_ci(trip_df, num_samples):\n", + " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=0.99999, method='goodman')\n", + " vals_with_ci = np.hstack((ci, trip_df[['value_obs']].to_numpy()))\n", + " return np.apply_along_axis(numpy_within, 1, vals_with_ci).all()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 276, + "outputs": [], + "source": [ + "vals_within_ci = merged_results.groupby(\"trip_id\").apply(lambda group: all_within_ci(group, num_samples))" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 277, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Have 57 with contribution outside 0.99 CI\n" + ] + } + ], + "source": [ + "trips_not_within = vals_within_ci.loc[~vals_within_ci]\n", + "print(f\"Have {trips_not_within.shape[0]} with contribution outside 0.99 CI\")" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 291, + "outputs": [], + "source": [ + "bad_res = []\n", + "\n", + "for bad_trip_id in trips_not_within.index.values:\n", + " trip_df = merged_results.loc[merged_results.trip_id == bad_trip_id]\n", + " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=0.999, method='goodman')\n", + " vals_with_ci = np.hstack((ci, trip_df[['value_obs']].to_numpy()))\n", + " x_ = pd.DataFrame(vals_with_ci)\n", + " x_['in_range'] = np.apply_along_axis(numpy_within, 1, vals_with_ci)[:np.newaxis]\n", + " bad_res.append(x_.loc[~x_.in_range])\n", + "bad_res = pd.concat(bad_res)\n", + "bad_res.columns = [\"lb\", \"ub\", \"value\", \"within\"]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 292, + "outputs": [ + { + "data": { + "text/plain": " lb ub value within\n8 0.001687 0.003703 0.001667 False\n8 0.005178 0.008409 0.004877 False\n8 0.005531 0.008856 0.004911 False\n3 0.000054 0.000738 0.000051 False\n8 0.004739 0.007848 0.004560 False\n3 0.000154 0.001038 0.000112 False\n3 0.000154 0.001038 0.000125 False\n3 0.000403 0.001589 0.000400 False\n3 0.100737 0.112972 0.100614 False\n3 0.000540 0.001852 0.000525 False\n3 0.000470 0.001721 0.000423 False\n7 0.000273 0.001319 0.001341 False\n8 0.000017 0.000575 0.000011 False\n8 0.003184 0.005805 0.002994 False\n8 0.001930 0.004060 0.001838 False\n8 0.003099 0.005690 0.003060 False\n3 0.000154 0.001038 0.000117 False\n8 0.002676 0.005113 0.002415 False\n3 0.001133 0.002858 0.001023 False\n8 0.005266 0.008521 0.004850 False\n8 0.002509 0.004881 0.002161 False\n8 0.005531 0.008856 0.005396 False\n8 0.003269 0.005920 0.003160 False\n3 0.000054 0.000738 0.000030 False\n3 0.000054 0.000738 0.000025 False\n3 0.076921 0.087807 0.076860 False\n8 0.004477 0.007510 0.004245 False\n2 0.001133 0.002858 0.001085 False\n8 0.004477 0.007510 0.004245 False\n8 0.003526 0.006263 0.003404 False\n8 0.003440 0.006149 0.003351 False\n8 0.004739 0.007848 0.004276 False\n8 0.003784 0.006605 0.003404 False\n8 0.004303 0.007285 0.003717 False\n8 0.000017 0.000575 0.000008 False\n1 0.000101 0.000891 0.000084 False\n8 0.001930 0.004060 0.001920 False\n3 0.032773 0.040191 0.032192 False\n3 0.000337 0.001455 0.000256 False\n3 0.000756 0.002236 0.000730 False\n8 0.000017 0.000575 0.000017 False\n1 0.000054 0.000738 0.000033 False\n1 0.000017 0.000575 0.000006 False\n1 0.000017 0.000575 0.000008 False\n8 0.000017 0.000575 0.000014 False\n3 0.000054 0.000738 0.000041 False\n3 0.000101 0.000891 0.000058 False\n8 0.000017 0.000575 0.000016 False\n8 0.018715 0.024460 0.018662 False\n0 0.998545 0.999663 0.999677 False\n1 0.000054 0.000738 0.000053 False\n3 0.103271 0.115636 0.102986 False\n3 0.103271 0.115636 0.102967 False\n8 0.000017 0.000575 0.000012 False\n8 0.000017 0.000575 0.000013 False\n8 0.002676 0.005113 0.002658 False\n3 0.001289 0.003102 0.001196 False\n7 0.009844 0.014139 0.009778 False\n3 0.000470 0.001721 0.000384 False", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
lbubvaluewithin
80.0016870.0037030.001667False
80.0051780.0084090.004877False
80.0055310.0088560.004911False
30.0000540.0007380.000051False
80.0047390.0078480.004560False
30.0001540.0010380.000112False
30.0001540.0010380.000125False
30.0004030.0015890.000400False
30.1007370.1129720.100614False
30.0005400.0018520.000525False
30.0004700.0017210.000423False
70.0002730.0013190.001341False
80.0000170.0005750.000011False
80.0031840.0058050.002994False
80.0019300.0040600.001838False
80.0030990.0056900.003060False
30.0001540.0010380.000117False
80.0026760.0051130.002415False
30.0011330.0028580.001023False
80.0052660.0085210.004850False
80.0025090.0048810.002161False
80.0055310.0088560.005396False
80.0032690.0059200.003160False
30.0000540.0007380.000030False
30.0000540.0007380.000025False
30.0769210.0878070.076860False
80.0044770.0075100.004245False
20.0011330.0028580.001085False
80.0044770.0075100.004245False
80.0035260.0062630.003404False
80.0034400.0061490.003351False
80.0047390.0078480.004276False
80.0037840.0066050.003404False
80.0043030.0072850.003717False
80.0000170.0005750.000008False
10.0001010.0008910.000084False
80.0019300.0040600.001920False
30.0327730.0401910.032192False
30.0003370.0014550.000256False
30.0007560.0022360.000730False
80.0000170.0005750.000017False
10.0000540.0007380.000033False
10.0000170.0005750.000006False
10.0000170.0005750.000008False
80.0000170.0005750.000014False
30.0000540.0007380.000041False
30.0001010.0008910.000058False
80.0000170.0005750.000016False
80.0187150.0244600.018662False
00.9985450.9996630.999677False
10.0000540.0007380.000053False
30.1032710.1156360.102986False
30.1032710.1156360.102967False
80.0000170.0005750.000012False
80.0000170.0005750.000013False
80.0026760.0051130.002658False
30.0012890.0031020.001196False
70.0098440.0141390.009778False
30.0004700.0017210.000384False
\n
" + }, + "execution_count": 292, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bad_res" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, From 8668383a489973a91af2c1c5e5043891f05b1409 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 17 May 2022 00:26:03 +1000 Subject: [PATCH 032/298] nicer comparison --- notebooks/validate_frozen_impl.ipynb | 228 +++++++++------------------ 1 file changed, 72 insertions(+), 156 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index f62f539bcb..6567e6ba41 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -643,20 +643,35 @@ }, { "cell_type": "code", - "execution_count": 157, + "execution_count": 302, + "outputs": [], + "source": [ + "# should use only those without zero probability - they are really not available so not a valid choice\n", + "merged_results_wo_zeros = merged_results.loc[(merged_results.value_sim != 0) & (merged_results.value_obs != 0)]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 303, "outputs": [ { "data": { "text/plain": "" }, - "execution_count": 157, + "execution_count": 303, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": "
", - "image/png": "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\n" + "image/png": "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\n" }, "metadata": { "needs_background": "light" @@ -667,7 +682,8 @@ "source": [ "import seaborn as sns\n", "\n", - "sns.scatterplot(data=merged_results, x=\"value_sim\", y=\"value_obs\")" + "sns.scatterplot(data=merged_results_wo_zeros, x=\"value_sim\", y=\"value_obs\")\n", + "#sns.regplot(data=merged_results, x=\"value_sim\", y=\"value_obs\")" ], "metadata": { "collapsed": false, @@ -678,13 +694,13 @@ }, { "cell_type": "code", - "execution_count": 159, + "execution_count": 304, "outputs": [ { "data": { - "text/plain": "LinregressResult(slope=0.999889299809943, intercept=5.271437621759412e-06, rvalue=0.9999938263765986, pvalue=0.0, stderr=3.49259911845371e-05, intercept_stderr=6.896656958760079e-06)" + "text/plain": "LinregressResult(slope=0.9998410722635966, intercept=4.4762082968718975e-05, rvalue=0.9999904091368037, pvalue=0.0, stderr=0.00011336879523644994, intercept_stderr=5.826949896067866e-05)" }, - "execution_count": 159, + "execution_count": 304, "metadata": {}, "output_type": "execute_result" } @@ -692,7 +708,7 @@ "source": [ "#from scipy.special import rel_entr\n", "from scipy.stats import linregress\n", - "linregress(merged_results.value_sim.values, merged_results.value_obs.values)" + "linregress(merged_results_wo_zeros.value_sim.values, merged_results_wo_zeros.value_obs.values)" ], "metadata": { "collapsed": false, @@ -701,85 +717,31 @@ } } }, - { - "cell_type": "markdown", - "source": [ - "#### generic power discrepancy implementation in statsmodels\n", - "\n", - "lambd{float, str}\n", - "float : exponent a for power discrepancy\n", - "\n", - "‘loglikeratio’: a = 0\n", - "\n", - "‘freeman_tukey’: a = -0.5\n", - "\n", - "‘pearson’: a = 1 (standard chisquare test statistic)\n", - "\n", - "‘modified_loglikeratio’: a = -1\n", - "\n", - "‘cressie_read’: a = 2/3\n", - "\n", - "‘neyman’ : a = -2 (Neyman-modified chisquare, reference from a book?)\n", - "\n", - "note - they are all based on continuous approximations (x^2) so technically they might be inadequate for very small\n", - "occurences of counts" - ], - "metadata": { - "collapsed": false - } - }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 305, "outputs": [ { "data": { - "text/plain": "(0.3439655414093617, nan)" + "text/plain": "" }, - "execution_count": 64, + "execution_count": 305, "metadata": {}, "output_type": "execute_result" - } - ], - "source": [ - "# Note power tests will be inf when expected count is zero (any non-zero count will be impossible)\n", - "eps = 0 #1e-14\n", - "non_zero_shares = ms_comp.loc[(ms_comp.mode_share_obs > eps) & (ms_comp.trip_mode > eps)][[\"mode_share_obs\", \"trip_mode\"]]\n", - "ddof = non_zero_shares.shape[0] - 1 # what to use here? num alternatives - 1? What about zero expected count?\n", - "\n", - "non_zero_shares.trip_mode *= num_samples\n", - "\n", - "sum_ms = non_zero_shares.mode_share_obs.sum()\n", - "if sum_ms != 1.0:\n", - " if np.abs(1.0 - sum_ms) > 1e-1:\n", - " print(f\"NOT GOOD {sum_ms} should be one\")\n", - " #print(f\"sum ms is not one but {sum_ms}, re-normalising\")\n", - " non_zero_shares.mode_share_obs /= sum_ms\n", - "\n", - "powerdiscrepancy(non_zero_shares.trip_mode.values, non_zero_shares.mode_share_obs.values, lambd=0, ddof=ddof)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 99, - "outputs": [ + }, { "data": { - "text/plain": "array([[4.17345080e-03, 5.98926219e-03, 5.00000000e-03],\n [1.45406892e-04, 6.18851051e-04, 3.00000000e-04],\n [4.13473933e-02, 4.66025328e-02, 4.39000000e-02],\n [5.64412252e-02, 6.25036058e-02, 5.94000000e-02],\n [8.87346329e-01, 8.95325012e-01, 8.91400000e-01]])" + "text/plain": "
", + "image/png": "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\n" }, - "execution_count": 99, - "metadata": {}, - "output_type": "execute_result" + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" } ], "source": [ - "\n" + "sns.residplot(data=merged_results_wo_zeros, x=\"value_sim\", y=\"value_obs\")" ], "metadata": { "collapsed": false, @@ -790,33 +752,24 @@ }, { "cell_type": "code", - "execution_count": 106, - "outputs": [ - { - "data": { - "text/plain": "array([ True, True, True, True, True])" - }, - "execution_count": 106, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 230, + "execution_count": 306, "outputs": [], "source": [ - "# test_trip_res = merged_results.loc[merged_results.trip_id == 1045822217]\n", - "# ci = multinomial_proportions_confint(num_samples * test_trip_res.value_sim.values, alpha=0.999, method='goodman')\n", - "# np.hstack((test_trip_res[['value_obs']].to_numpy(), ci))" + "# # Note power tests will be inf when expected count is zero (any non-zero count will be impossible)\n", + "# eps = 0 #1e-14\n", + "# non_zero_shares = ms_comp.loc[(ms_comp.mode_share_obs > eps) & (ms_comp.trip_mode > eps)][[\"mode_share_obs\", \"trip_mode\"]]\n", + "# ddof = non_zero_shares.shape[0] - 1 # what to use here? num alternatives - 1? What about zero expected count?\n", + "#\n", + "# non_zero_shares.trip_mode *= num_samples\n", + "#\n", + "# sum_ms = non_zero_shares.mode_share_obs.sum()\n", + "# if sum_ms != 1.0:\n", + "# if np.abs(1.0 - sum_ms) > 1e-1:\n", + "# print(f\"NOT GOOD {sum_ms} should be one\")\n", + "# #print(f\"sum ms is not one but {sum_ms}, re-normalising\")\n", + "# non_zero_shares.mode_share_obs /= sum_ms\n", + "#\n", + "# powerdiscrepancy(non_zero_shares.trip_mode.values, non_zero_shares.mode_share_obs.values, lambd=0, ddof=ddof)" ], "metadata": { "collapsed": false, @@ -827,7 +780,7 @@ }, { "cell_type": "code", - "execution_count": 275, + "execution_count": 314, "outputs": [], "source": [ "@numba.jit\n", @@ -841,8 +794,8 @@ " val = x[2]\n", " return within(lower, upper, val)\n", "\n", - "def all_within_ci(trip_df, num_samples):\n", - " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=0.99999, method='goodman')\n", + "def all_within_ci(trip_df, num_samples, alpha=0.9):\n", + " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=alpha, method='goodman')\n", " vals_with_ci = np.hstack((ci, trip_df[['value_obs']].to_numpy()))\n", " return np.apply_along_axis(numpy_within, 1, vals_with_ci).all()" ], @@ -855,80 +808,43 @@ }, { "cell_type": "code", - "execution_count": 276, - "outputs": [], - "source": [ - "vals_within_ci = merged_results.groupby(\"trip_id\").apply(lambda group: all_within_ci(group, num_samples))" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 277, + "execution_count": 328, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Have 57 with contribution outside 0.99 CI\n" + "Have 17 with contribution outside 95.00% CI, corresponding to 1.14% of all non-zero observations.\n" ] + }, + { + "data": { + "text/plain": " lb ub value within\n3 0.000031 0.001291 0.000030 False\n3 0.000031 0.001291 0.000025 False\n8 0.003744 0.008367 0.003717 False\n8 0.000009 0.001113 0.000008 False\n1 0.000009 0.001113 0.000006 False\n1 0.000009 0.001113 0.000008 False\n3 0.000062 0.001460 0.000058 False", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
lbubvaluewithin
30.0000310.0012910.000030False
30.0000310.0012910.000025False
80.0037440.0083670.003717False
80.0000090.0011130.000008False
10.0000090.0011130.000006False
10.0000090.0011130.000008False
30.0000620.0014600.000058False
\n
" + }, + "execution_count": 328, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ + "alpha = 0.05 # default. this corresponds to a 95% CI, so 95% of all values should lie within our approximation\n", + "vals_within_ci = merged_results_wo_zeros.groupby(\"trip_id\").apply(lambda group: all_within_ci(group, num_samples, alpha))\n", "trips_not_within = vals_within_ci.loc[~vals_within_ci]\n", - "print(f\"Have {trips_not_within.shape[0]} with contribution outside 0.99 CI\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 291, - "outputs": [], - "source": [ + "print(f\"Have {trips_not_within.shape[0]} with contribution outside {1 - alpha:.2%} CI, corresponding to \"\n", + " f\"{trips_not_within.shape[0] / merged_results_wo_zeros.shape[0]:.2%} of all non-zero observations.\")\n", + "\n", "bad_res = []\n", "\n", "for bad_trip_id in trips_not_within.index.values:\n", " trip_df = merged_results.loc[merged_results.trip_id == bad_trip_id]\n", - " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=0.999, method='goodman')\n", + " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=alpha, method='goodman')\n", " vals_with_ci = np.hstack((ci, trip_df[['value_obs']].to_numpy()))\n", " x_ = pd.DataFrame(vals_with_ci)\n", " x_['in_range'] = np.apply_along_axis(numpy_within, 1, vals_with_ci)[:np.newaxis]\n", " bad_res.append(x_.loc[~x_.in_range])\n", "bad_res = pd.concat(bad_res)\n", - "bad_res.columns = [\"lb\", \"ub\", \"value\", \"within\"]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 292, - "outputs": [ - { - "data": { - "text/plain": " lb ub value within\n8 0.001687 0.003703 0.001667 False\n8 0.005178 0.008409 0.004877 False\n8 0.005531 0.008856 0.004911 False\n3 0.000054 0.000738 0.000051 False\n8 0.004739 0.007848 0.004560 False\n3 0.000154 0.001038 0.000112 False\n3 0.000154 0.001038 0.000125 False\n3 0.000403 0.001589 0.000400 False\n3 0.100737 0.112972 0.100614 False\n3 0.000540 0.001852 0.000525 False\n3 0.000470 0.001721 0.000423 False\n7 0.000273 0.001319 0.001341 False\n8 0.000017 0.000575 0.000011 False\n8 0.003184 0.005805 0.002994 False\n8 0.001930 0.004060 0.001838 False\n8 0.003099 0.005690 0.003060 False\n3 0.000154 0.001038 0.000117 False\n8 0.002676 0.005113 0.002415 False\n3 0.001133 0.002858 0.001023 False\n8 0.005266 0.008521 0.004850 False\n8 0.002509 0.004881 0.002161 False\n8 0.005531 0.008856 0.005396 False\n8 0.003269 0.005920 0.003160 False\n3 0.000054 0.000738 0.000030 False\n3 0.000054 0.000738 0.000025 False\n3 0.076921 0.087807 0.076860 False\n8 0.004477 0.007510 0.004245 False\n2 0.001133 0.002858 0.001085 False\n8 0.004477 0.007510 0.004245 False\n8 0.003526 0.006263 0.003404 False\n8 0.003440 0.006149 0.003351 False\n8 0.004739 0.007848 0.004276 False\n8 0.003784 0.006605 0.003404 False\n8 0.004303 0.007285 0.003717 False\n8 0.000017 0.000575 0.000008 False\n1 0.000101 0.000891 0.000084 False\n8 0.001930 0.004060 0.001920 False\n3 0.032773 0.040191 0.032192 False\n3 0.000337 0.001455 0.000256 False\n3 0.000756 0.002236 0.000730 False\n8 0.000017 0.000575 0.000017 False\n1 0.000054 0.000738 0.000033 False\n1 0.000017 0.000575 0.000006 False\n1 0.000017 0.000575 0.000008 False\n8 0.000017 0.000575 0.000014 False\n3 0.000054 0.000738 0.000041 False\n3 0.000101 0.000891 0.000058 False\n8 0.000017 0.000575 0.000016 False\n8 0.018715 0.024460 0.018662 False\n0 0.998545 0.999663 0.999677 False\n1 0.000054 0.000738 0.000053 False\n3 0.103271 0.115636 0.102986 False\n3 0.103271 0.115636 0.102967 False\n8 0.000017 0.000575 0.000012 False\n8 0.000017 0.000575 0.000013 False\n8 0.002676 0.005113 0.002658 False\n3 0.001289 0.003102 0.001196 False\n7 0.009844 0.014139 0.009778 False\n3 0.000470 0.001721 0.000384 False", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
lbubvaluewithin
80.0016870.0037030.001667False
80.0051780.0084090.004877False
80.0055310.0088560.004911False
30.0000540.0007380.000051False
80.0047390.0078480.004560False
30.0001540.0010380.000112False
30.0001540.0010380.000125False
30.0004030.0015890.000400False
30.1007370.1129720.100614False
30.0005400.0018520.000525False
30.0004700.0017210.000423False
70.0002730.0013190.001341False
80.0000170.0005750.000011False
80.0031840.0058050.002994False
80.0019300.0040600.001838False
80.0030990.0056900.003060False
30.0001540.0010380.000117False
80.0026760.0051130.002415False
30.0011330.0028580.001023False
80.0052660.0085210.004850False
80.0025090.0048810.002161False
80.0055310.0088560.005396False
80.0032690.0059200.003160False
30.0000540.0007380.000030False
30.0000540.0007380.000025False
30.0769210.0878070.076860False
80.0044770.0075100.004245False
20.0011330.0028580.001085False
80.0044770.0075100.004245False
80.0035260.0062630.003404False
80.0034400.0061490.003351False
80.0047390.0078480.004276False
80.0037840.0066050.003404False
80.0043030.0072850.003717False
80.0000170.0005750.000008False
10.0001010.0008910.000084False
80.0019300.0040600.001920False
30.0327730.0401910.032192False
30.0003370.0014550.000256False
30.0007560.0022360.000730False
80.0000170.0005750.000017False
10.0000540.0007380.000033False
10.0000170.0005750.000006False
10.0000170.0005750.000008False
80.0000170.0005750.000014False
30.0000540.0007380.000041False
30.0001010.0008910.000058False
80.0000170.0005750.000016False
80.0187150.0244600.018662False
00.9985450.9996630.999677False
10.0000540.0007380.000053False
30.1032710.1156360.102986False
30.1032710.1156360.102967False
80.0000170.0005750.000012False
80.0000170.0005750.000013False
80.0026760.0051130.002658False
30.0012890.0031020.001196False
70.0098440.0141390.009778False
30.0004700.0017210.000384False
\n
" - }, - "execution_count": 292, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ + "bad_res.columns = [\"lb\", \"ub\", \"value\", \"within\"]\n", "bad_res" ], "metadata": { From 921983fb32d8624bffb08615dcd75f70c0ff33c3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 17 May 2022 10:53:07 +1000 Subject: [PATCH 033/298] adds more convergence analysis --- notebooks/validate_frozen_impl.ipynb | 141 ++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 11 deletions(-) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 6567e6ba41..c267d16757 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -77,6 +77,7 @@ "from statsmodels.stats.gof import powerdiscrepancy\n", "from statsmodels.stats.proportion import multinomial_proportions_confint\n", "import numba\n", + "import matplotlib.pyplot as plt\n", "\n", "from activitysim.cli import run\n", "from activitysim.core import inject\n", @@ -855,22 +856,57 @@ } }, { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "cell_type": "markdown", + "source": [ + "## CI is not great for this\n", + "\n", + "we expect about 5% outliers for 95% CI, and I can see for 1e5 samples the number of outliers actually nearly doubles for the 95CI which makes sense - it is the large sample limit where all this is exact.\n", + "\n", + "Let's plot a histogram of the std deviation, and take a single trip and plot the diff from expected value with number of draws (i.e. prob vs num_draws)" + ], "metadata": { "collapsed": false, "pycharm": { - "name": "#%%\n" + "name": "#%% md\n" } } }, + { + "cell_type": "markdown", + "source": [ + "### histogram of residuals" + ], + "metadata": { + "collapsed": false + } + }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 440, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.4540809129771874e-05\n" + ] + }, + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "merged_results['diff_squared'] = np.square(merged_results.value_sim - merged_results.value_obs)\n", + "print(merged_results['diff_squared'].max())\n", + "plt.hist(merged_results.diff_squared.values, bins=100);" + ], "metadata": { "collapsed": false, "pycharm": { @@ -878,11 +914,94 @@ } } }, + { + "cell_type": "markdown", + "source": [ + "### single trip-mode convergence to expected value" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 431, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trip_mode_choice tour_type 'othdiscr' (10000 trips)\n", + "Done\n" + ] + }, + { + "data": { + "text/plain": " trip_mode drawn exact\n0 WALK_LRF 0.3210 0.322184\n1 WALK_HVY 0.2812 0.278911\n2 TNC_SINGLE 0.2170 0.219172\n3 WALK_LOC 0.1780 0.176455\n4 TNC_SHARED 0.0018 0.002318\n5 WALK 0.0008 0.000713\n6 SHARED2FREE 0.0001 0.000179\n7 SHARED3FREE 0.0001 0.000063\n20 TAXI 0.0000 0.000005", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_modedrawnexact
0WALK_LRF0.32100.322184
1WALK_HVY0.28120.278911
2TNC_SINGLE0.21700.219172
3WALK_LOC0.17800.176455
4TNC_SHARED0.00180.002318
5WALK0.00080.000713
6SHARED2FREE0.00010.000179
7SHARED3FREE0.00010.000063
20TAXI0.00000.000005
\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": "(1e-08, 1)" + }, + "execution_count": 431, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "num_samples_single = 10000\n", + "trip_id_to_check = 1767182865 #bp.index.values #[100:103]\n", + "\n", + "_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=True,\n", + " trip_id_to_check=trip_id_to_check, num_samples=num_samples_single)\n", + "c_ = c_[['trip_mode']].reset_index(drop=True)\n", + "\n", + "temp = bp.loc[bp.index == trip_id_to_check]\n", + "temp.index = [\"exact\"]\n", + "temp = temp.T.reset_index().rename(columns={\"index\": \"trip_mode\"})\n", + "ms = (c_.value_counts() / c_.shape[0]).to_frame(\"drawn\").reset_index().merge(temp, on=\"trip_mode\", how=\"outer\").fillna(0)\n", + "ms = ms.loc[(ms.exact != 0)]\n", + "display(ms)\n", + "c_test = c_.copy()\n", + "c_test.index = np.arange(1, c_test.index.shape[0]+1)\n", + "check_mode = \"WALK_LRF\"\n", + "ms_exact = ms.loc[ms.trip_mode == check_mode].exact.values[0]\n", + "\n", + "c_test['trip_mode'] = np.where(c_test['trip_mode'] == check_mode, 1, 0)\n", + "c_test = c_test.cumsum()\n", + "c_test[\"share\"] = c_test.trip_mode / c_test.index\n", + "c_test[\"diff\"] = np.square(c_test[\"share\"] - ms_exact)\n", + "\n", + "plt.plot(c_test.index.values, c_test['diff'].values)\n", + "plt.plot(np.arange(1,num_samples_single), 1.0/np.arange(1,num_samples_single))\n", + "plt.yscale('log')\n", + "plt.xscale('log')\n", + "plt.ylim([1e-8,1])" + ], "metadata": { "collapsed": false, "pycharm": { From 7694563d6574c78920919a1bb99b26addee5cefb Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 15 Jun 2022 15:48:51 +1000 Subject: [PATCH 034/298] simple siumlate mnl frozen rand ind util --- activitysim/core/logit.py | 13 + activitysim/core/simulate.py | 42 ++- notebooks/frozen_impl_dev.ipynb | 485 +++++++++++++++++++++++++++ notebooks/validate_frozen_impl.ipynb | 30 +- 4 files changed, 550 insertions(+), 20 deletions(-) create mode 100644 notebooks/frozen_impl_dev.ipynb diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index de292dc3a1..aecae4e3f9 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -221,6 +221,7 @@ def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alter next_level_alts = nest_alternatives_by_name[choice_this_level] raise ValueError("This should never happen - no alternative found") + # Note: this is relatively slow due to the apply. # It could *maybe* be sped up by using the fact that the nesting structure is the same for all rows: Add ev1(0,1) to # all entries (as is currently being done). Then, at each level, pick the maximum of the available composite @@ -245,6 +246,18 @@ def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_ return choices +# TODO: integrate with nested impl above +# TODO: make everything in nested and here numpy from beginning to make choices consistent with previous impl ( +# want column index and not alternative name) +def make_choices_ru_frozen_mnl(utilities, trace_label=None): + trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen_mnl') + utilities_incl_unobs = add_ev1_random(utilities) + choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) + assert not np.isnan(choices).any(), "No choice for XXX - implement reporting" + choices = pd.Series(choices, index=utilities_incl_unobs.index) + return choices + + def make_choices(probs, trace_label=None, trace_choosers=None, allow_bad_probs=False): """ Make choices for each chooser from among a set of alternatives. diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index aba8d2340c..f233339082 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -887,7 +887,8 @@ def compute_base_probabilities(nested_probabilities, nests, spec): def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, log_alt_losers=False, want_logsums=False, trace_label=None, - trace_choice_name=None, trace_column_names=None): + trace_choice_name=None, trace_column_names=None, + choose_individual_max_utility=False): """ Run a simulation for when the model spec does not involve alternative specific data, e.g. there are no interactions with alternative @@ -922,6 +923,8 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, This is the column label to be used in trace file csv dump of choices trace_column_names: str or list of str chooser columns to include when tracing expression_values + choose_individual_max_utility: bool + apply frozen randomness at the individual utility level Returns ------- @@ -949,25 +952,29 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, tracing.trace_df(utilities, '%s.utilities' % trace_label, column_labels=['alternative', 'utility']) - probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) - chunk.log_df(trace_label, "probs", probs) + if choose_individual_max_utility: + choices = logit.make_choices_ru_frozen_mnl(utilities, trace_label=trace_label) + rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up + else: + probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) + chunk.log_df(trace_label, "probs", probs) - del utilities - chunk.log_df(trace_label, 'utilities', None) + del utilities + chunk.log_df(trace_label, 'utilities', None) - if have_trace_targets: - # report these now in case make_choices throws error on bad_choices - tracing.trace_df(probs, '%s.probs' % trace_label, - column_labels=['alternative', 'probability']) + if have_trace_targets: + # report these now in case make_choices throws error on bad_choices + tracing.trace_df(probs, '%s.probs' % trace_label, + column_labels=['alternative', 'probability']) - if custom_chooser: - choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, - trace_label=trace_label) - else: - choices, rands = logit.make_choices(probs, trace_label=trace_label) + if custom_chooser: + choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, + trace_label=trace_label) + else: + choices, rands = logit.make_choices(probs, trace_label=trace_label) - del probs - chunk.log_df(trace_label, 'probs', None) + del probs + chunk.log_df(trace_label, 'probs', None) if have_trace_targets: tracing.trace_df(choices, '%s.choices' % trace_label, @@ -1318,7 +1325,8 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, want_logsums=want_logsums, estimator=estimator, trace_label=trace_label, - trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) + trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, + choose_individual_max_utility=choose_individual_max_utility) else: if choose_individual_max_utility: choices = eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, diff --git a/notebooks/frozen_impl_dev.ipynb b/notebooks/frozen_impl_dev.ipynb new file mode 100644 index 0000000000..7bf198c2b5 --- /dev/null +++ b/notebooks/frozen_impl_dev.ipynb @@ -0,0 +1,485 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", + "metadata": {}, + "source": [ + "# validate results\n", + "\n", + "## TODO\n", + "what happened to tracing when I fixed probability calcs - it seems like all trip ids are attached when I add one by\n", + "hand below - why?" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.385153Z", + "iopub.status.busy": "2022-05-01T10:17:57.384881Z", + "iopub.status.idle": "2022-05-01T10:17:57.534433Z", + "shell.execute_reply": "2022-05-01T10:17:57.533096Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%load_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3dba451-1e10-403e-8614-35d57e6577f4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.536623Z", + "iopub.status.busy": "2022-05-01T10:17:57.536012Z", + "iopub.status.idle": "2022-05-01T10:17:57.542755Z", + "shell.execute_reply": "2022-05-01T10:17:57.541685Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:00.350944Z", + "iopub.status.busy": "2022-05-01T10:18:00.350730Z", + "iopub.status.idle": "2022-05-01T10:18:12.760977Z", + "shell.execute_reply": "2022-05-01T10:18:12.760013Z", + "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import argparse\n", + "from datetime import datetime\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from numpy.random import default_rng\n", + "from statsmodels.stats.gof import powerdiscrepancy\n", + "from statsmodels.stats.proportion import multinomial_proportions_confint\n", + "import numba\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from activitysim.cli import run\n", + "from activitysim.core import inject\n", + "\n", + "from fru_utils import run_trip_mode_choice, comp_mode_shares, mode_choice_for_trip" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:16.944537Z", + "iopub.status.busy": "2022-05-01T10:18:16.944291Z", + "iopub.status.idle": "2022-05-01T10:18:17.124764Z", + "shell.execute_reply": "2022-05-01T10:18:17.123725Z", + "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "pd.set_option(\"max_columns\", 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "outputs": [ + { + "data": { + "text/plain": "array([2, 1, 2])" + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.argmax(np.array([[1,2,3],[4,6,5],[7,8,9]]), axis=1)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 34, + "outputs": [ + { + "data": { + "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": "0 c\n1 b\n2 c\ndtype: object" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", + "display(df_)\n", + "ch_ = df_.idxmax(1)\n", + "display(ch_)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:17.839947Z", + "iopub.status.busy": "2022-05-01T10:18:17.839070Z", + "iopub.status.idle": "2022-05-01T10:18:18.019676Z", + "shell.execute_reply": "2022-05-01T10:18:18.018689Z", + "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_example_mtc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:18.894533Z", + "iopub.status.busy": "2022-05-01T10:18:18.894303Z", + "iopub.status.idle": "2022-05-01T10:18:19.078807Z", + "shell.execute_reply": "2022-05-01T10:18:19.077951Z", + "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "os.chdir(example_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:22.111723Z", + "iopub.status.busy": "2022-05-01T10:18:22.111490Z", + "iopub.status.idle": "2022-05-01T10:18:22.297437Z", + "shell.execute_reply": "2022-05-01T10:18:22.296501Z", + "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "run.add_run_args(parser)\n", + "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", + "#run.run(args) # 2mins full example run\n", + "if not inject.is_injectable('preload_injectables'):\n", + " from activitysim import abm # register abm steps and other abm-specific injectables\n", + "run.handle_standard_args(args) # possibly update injectables" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "outputs": [], + "source": [ + "from activitysim.core import inject\n", + "from activitysim.core import pipeline\n", + "from activitysim.core import config\n", + "from activitysim.core import expressions\n", + "from activitysim.core import simulate\n", + "from activitysim.abm.models.util import estimation\n", + "\n", + "def run_mandatory_tour_frequency(choose_individual_max_utility=False):\n", + " resume_after = \"cdap_simulate\"\n", + " model_name = \"mandatory_tour_frequency\"\n", + " chunk_size = 0 # test_mtc means no chunking\n", + "\n", + " pipeline.open_pipeline(resume_after)\n", + " # preload any bulky injectables (e.g. skims) not in pipeline\n", + " inject.get_injectable('preload_injectables', None)\n", + " pipeline._PIPELINE.rng().begin_step(model_name)\n", + " #step_name = model_name\n", + " args = {}\n", + " #checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + " inject.set_step_args(args)\n", + "\n", + " #network_los = inject.get_injectable('network_los')\n", + " persons_merged = inject.get_table('persons_merged')\n", + "\n", + " trace_label = 'mandatory_tour_frequency'\n", + " model_settings_file_name = 'mandatory_tour_frequency.yaml'\n", + " model_settings = config.read_model_settings(model_settings_file_name)\n", + " choosers = persons_merged.to_frame()\n", + " # filter based on results of CDAP\n", + " choosers = choosers[choosers.cdap_activity == 'M']\n", + " print(\"Running mandatory_tour_frequency with %d persons\", len(choosers))\n", + " preprocessor_settings = model_settings.get('preprocessor', None)\n", + " if preprocessor_settings:\n", + " locals_dict = {}\n", + " expressions.assign_columns(\n", + " df=choosers,\n", + " model_settings=preprocessor_settings,\n", + " locals_dict=locals_dict,\n", + " trace_label=trace_label)\n", + "\n", + " estimator = estimation.manager.begin_estimation('mandatory_tour_frequency')\n", + " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + " coefficients_df = simulate.read_model_coefficients(model_settings)\n", + " model_spec = simulate.eval_coefficients(model_spec, coefficients_df, estimator)\n", + " nest_spec = config.get_logit_model_settings(model_settings)\n", + " constants = config.get_model_constants(model_settings)\n", + " choices = simulate.simple_simulate(\n", + " choosers=choosers,\n", + " spec=model_spec,\n", + " nest_spec=nest_spec,\n", + " locals_d=constants,\n", + " chunk_size=chunk_size,\n", + " trace_label=trace_label,\n", + " trace_choice_name='mandatory_tour_frequency',\n", + " estimator=estimator,\n", + " choose_individual_max_utility=choose_individual_max_utility)\n", + "\n", + " # convert indexes to alternative names\n", + " choices = pd.Series(model_spec.columns[choices.values], index=choices.index)\n", + "\n", + " finalise = True\n", + " if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n", + "\n", + " return choices" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "#pipeline.close_pipeline()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 47, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle mandatory_tour_frequency not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running mandatory_tour_frequency with %d persons 89\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle mandatory_tour_frequency not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running mandatory_tour_frequency with %d persons 89\n" + ] + } + ], + "source": [ + "ch_prev = run_mandatory_tour_frequency(choose_individual_max_utility=False)\n", + "ch_new = run_mandatory_tour_frequency(choose_individual_max_utility=True)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 48, + "outputs": [ + { + "data": { + "text/plain": "work1 61\nschool1 22\nwork_and_school 4\nwork2 2\ndtype: int64" + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ch_prev.value_counts()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 49, + "outputs": [ + { + "data": { + "text/plain": "work1 65\nschool1 22\nwork2 2\ndtype: int64" + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ch_new.value_counts()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index c267d16757..62f8405acd 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -28,7 +28,6 @@ }, "outputs": [], "source": [ - "import numba\n", "%load_ext autoreload" ] }, @@ -161,8 +160,6 @@ "source": [ "parser = argparse.ArgumentParser()\n", "run.add_run_args(parser)\n", - "# args = parser.parse_args()\n", - "# parser.parse_args(['--sum', '7', '-1', '42'])\n", "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", "#run.run(args) # 2mins full example run\n", "if not inject.is_injectable('preload_injectables'):\n", @@ -914,6 +911,33 @@ } } }, + { + "cell_type": "code", + "execution_count": 448, + "outputs": [ + { + "data": { + "text/plain": "
", + "image/png": "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\n" + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# remove where both are unavailable\n", + "plt.hist(merged_results.loc[(merged_results.value_obs != 0) & (merged_results.value_sim != 0)].diff_squared.values, bins=100);\n", + "plt.yscale('log')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "markdown", "source": [ From 0c6f4c8eb5a81505469dab3b6d531a4361682054 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 15 Jun 2022 19:03:06 +1000 Subject: [PATCH 035/298] nested now with alternative numbers, not names, change structure to be more consistent with current impl --- activitysim/core/logit.py | 35 +++- activitysim/core/simulate.py | 361 ++++++++++++++++---------------- notebooks/frozen_impl_dev.ipynb | 71 +++++-- 3 files changed, 261 insertions(+), 206 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index aecae4e3f9..f9be97d77a 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -228,9 +228,8 @@ def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alter # alternatives and set the corresponding entry to 1 for each row, set all other alternatives at this level to zero. # Once the tree is walked (all alternatives have been processed), take the product of the alternatives in each # leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. -def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_choosers=None): +def make_choices_ru_frozen_nl(nested_utilities, nest_spec): """ walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" - trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') nest_utils_for_choice = add_ev1_random(nested_utilities) all_alternatives = set(nest.name for nest in each_nest(nest_spec, type='leaf')) @@ -243,22 +242,34 @@ def make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=None, trace_ ) assert not choices.isnull().any(), "No choice for XXX - implement reporting" choices = pd.Series(choices, index=nest_utils_for_choice.index) + + # TODO [janzill Jun2022]: REMOVE HACK, make this numpy and positional indexes from the beginning + choices = choices.map({v: k for k,v in enumerate(nest_utils_for_choice.columns)}) + return choices -# TODO: integrate with nested impl above -# TODO: make everything in nested and here numpy from beginning to make choices consistent with previous impl ( -# want column index and not alternative name) -def make_choices_ru_frozen_mnl(utilities, trace_label=None): - trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen_mnl') +# TODO [janzill Jun2022]: integrate with nested impl above +# TODO [janzill Jun2022]: make everything in nested and here numpy from beginning to make choices consistent with +# previous impl (want column index and not alternative name) +def make_choices_ru_frozen_mnl(utilities): utilities_incl_unobs = add_ev1_random(utilities) choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) assert not np.isnan(choices).any(), "No choice for XXX - implement reporting" choices = pd.Series(choices, index=utilities_incl_unobs.index) return choices +def make_choices_ru_frozen(utilities, nest_spec=None, trace_label=None): + trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen_mnl') + if nest_spec is None: + choices = make_choices_ru_frozen_mnl(utilities) + else: + choices = make_choices_ru_frozen_nl(utilities, nest_spec) + return choices + -def make_choices(probs, trace_label=None, trace_choosers=None, allow_bad_probs=False): +def make_choices(probs, utilities=None, nest_spec=None, trace_label=None, trace_choosers=None, allow_bad_probs=False, + choose_individual_max_utility=False): """ Make choices for each chooser from among a set of alternatives. @@ -286,8 +297,14 @@ def make_choices(probs, trace_label=None, trace_choosers=None, allow_bad_probs=F """ trace_label = tracing.extend_trace_label(trace_label, 'make_choices') - # probs should sum to 1 across each row + if choose_individual_max_utility: + choices = make_choices_ru_frozen(utilities, nest_spec, trace_label) + # TODO: rands + rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) + return choices, rands + + # probs should sum to 1 across each row BAD_PROB_THRESHOLD = 0.001 bad_probs = \ probs.sum(axis=1).sub(np.ones(len(probs.index))).abs() \ diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index f233339082..33ab7fa2da 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -952,29 +952,29 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, tracing.trace_df(utilities, '%s.utilities' % trace_label, column_labels=['alternative', 'utility']) - if choose_individual_max_utility: - choices = logit.make_choices_ru_frozen_mnl(utilities, trace_label=trace_label) - rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up - else: - probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) - chunk.log_df(trace_label, "probs", probs) + #if choose_individual_max_utility: + # choices = logit.make_choices_ru_frozen_mnl(utilities, trace_label=trace_label) + # rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up + #else: + probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) + chunk.log_df(trace_label, "probs", probs) - del utilities - chunk.log_df(trace_label, 'utilities', None) + if have_trace_targets: + # report these now in case make_choices throws error on bad_choices + tracing.trace_df(probs, '%s.probs' % trace_label, + column_labels=['alternative', 'probability']) - if have_trace_targets: - # report these now in case make_choices throws error on bad_choices - tracing.trace_df(probs, '%s.probs' % trace_label, - column_labels=['alternative', 'probability']) + if custom_chooser: + choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, + trace_label=trace_label) + else: + choices, rands = logit.make_choices(probs, utilities=utilities, trace_label=trace_label) - if custom_chooser: - choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, - trace_label=trace_label) - else: - choices, rands = logit.make_choices(probs, trace_label=trace_label) + del utilities + chunk.log_df(trace_label, 'utilities', None) - del probs - chunk.log_df(trace_label, 'probs', None) + del probs + chunk.log_df(trace_label, 'probs', None) if have_trace_targets: tracing.trace_df(choices, '%s.choices' % trace_label, @@ -984,151 +984,152 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, return choices - -def eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, - log_alt_losers=False, - want_logsums=False, trace_label=None, - trace_choice_name=None, trace_column_names=None): - """ - Run a nested-logit simulation for when the model spec does not involve alternative - specific data, e.g. there are no interactions with alternative - properties and no need to sample from alternatives. - - Parameters - ---------- - choosers : pandas.DataFrame - spec : pandas.DataFrame - A table of variable specifications and coefficient values. - Variable expressions should be in the table index and the table - should have a column for each alternative. - nest_spec: - dictionary specifying nesting structure and nesting coefficients - (from the model spec yaml file) - locals_d : Dict or None - This is a dictionary of local variables that will be the environment - for an evaluation of an expression that begins with @ - custom_chooser : function(probs, choosers, spec, trace_label) returns choices, rands - custom alternative to logit.make_choices - estimator : Estimator object - called to report intermediate table results (used for estimation) - trace_label: str - This is the label to be used for trace log file entries and dump file names - when household tracing enabled. No tracing occurs if label is empty or None. - trace_choice_name: str - This is the column label to be used in trace file csv dump of choices - trace_column_names: str or list of str - chooser columns to include when tracing expression_values - - Returns - ------- - choices : pandas.Series - Index will be that of `choosers`, values will match the columns - of `spec`. - """ - - trace_label = tracing.extend_trace_label(trace_label, 'eval_nl_fixed_ru') - assert trace_label - have_trace_targets = tracing.has_trace_targets(choosers) - - logit.validate_nest_spec(nest_spec, trace_label) - - if have_trace_targets: - tracing.trace_df(choosers, '%s.choosers' % trace_label) - - raw_utilities = eval_utilities(spec, choosers, locals_d, - log_alt_losers=log_alt_losers, - trace_label=trace_label, have_trace_targets=have_trace_targets, - estimator=estimator, trace_column_names=trace_column_names) - chunk.log_df(trace_label, "raw_utilities", raw_utilities) - - if have_trace_targets: - tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label, - column_labels=['alternative', 'utility']) - - # utilities of leaves and nests - nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) - chunk.log_df(trace_label, "nested_utilities", nested_utilities) - - del raw_utilities - chunk.log_df(trace_label, 'raw_utilities', None) - - if have_trace_targets: - tracing.trace_df(nested_utilities, '%s.nested_utilities' % trace_label, - column_labels=['alternative', 'utility']) - - - ########### comparison only - # probabilities of alternatives relative to siblings sharing the same nest - nested_probabilities = \ - compute_nested_probabilities(np.exp(nested_utilities), nest_spec, trace_label=trace_label) - chunk.log_df(trace_label, "nested_probabilities", nested_probabilities) - - if have_trace_targets: - tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label, - column_labels=['alternative', 'probability']) - - base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) - chunk.log_df(trace_label, "base_probabilities", base_probabilities) - - del nested_probabilities - chunk.log_df(trace_label, "nested_probabilities", None) - - if have_trace_targets: - tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label, - column_labels=['alternative', 'probability']) - - del(base_probabilities) - chunk.log_df(trace_label, "base_probabilities", None) - ########### end comparison only - - # TODO - check this is correct - if want_logsums: - # logsum of nest root - logsums = pd.Series(nested_utilities.root, index=choosers.index) - chunk.log_df(trace_label, "logsums", logsums) - - # TODO: add checks on utilities? - # # note base_probabilities could all be zero since we allowed all probs for nests to be zero - # # check here to print a clear message but make_choices will raise error if probs don't sum to 1 - # BAD_PROB_THRESHOLD = 0.001 - # no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD - # - # if no_choices.any(): - # - # logit.report_bad_choices( - # no_choices, base_probabilities, - # trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'), - # trace_choosers=choosers, - # msg="base_probabilities do not sum to one") - - # TODO: add custom_chooser - # if custom_chooser: - # choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, - # trace_label=trace_label) - # else: - - choices = logit.make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=trace_label) - - if have_trace_targets: - tracing.trace_df(choices, '%s.choices' % trace_label, - columns=[None, trace_choice_name]) - #tracing.trace_df(rands, '%s.rands' % trace_label, - # columns=[None, 'rand']) - if want_logsums: - tracing.trace_df(logsums, '%s.logsums' % trace_label, - columns=[None, 'logsum']) - - if want_logsums: - choices = choices.to_frame('choice') - choices['logsum'] = logsums - - return choices +# +# def eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, +# log_alt_losers=False, +# want_logsums=False, trace_label=None, +# trace_choice_name=None, trace_column_names=None): +# """ +# Run a nested-logit simulation for when the model spec does not involve alternative +# specific data, e.g. there are no interactions with alternative +# properties and no need to sample from alternatives. +# +# Parameters +# ---------- +# choosers : pandas.DataFrame +# spec : pandas.DataFrame +# A table of variable specifications and coefficient values. +# Variable expressions should be in the table index and the table +# should have a column for each alternative. +# nest_spec: +# dictionary specifying nesting structure and nesting coefficients +# (from the model spec yaml file) +# locals_d : Dict or None +# This is a dictionary of local variables that will be the environment +# for an evaluation of an expression that begins with @ +# custom_chooser : function(probs, choosers, spec, trace_label) returns choices, rands +# custom alternative to logit.make_choices +# estimator : Estimator object +# called to report intermediate table results (used for estimation) +# trace_label: str +# This is the label to be used for trace log file entries and dump file names +# when household tracing enabled. No tracing occurs if label is empty or None. +# trace_choice_name: str +# This is the column label to be used in trace file csv dump of choices +# trace_column_names: str or list of str +# chooser columns to include when tracing expression_values +# +# Returns +# ------- +# choices : pandas.Series +# Index will be that of `choosers`, values will match the columns +# of `spec`. +# """ +# +# trace_label = tracing.extend_trace_label(trace_label, 'eval_nl_fixed_ru') +# assert trace_label +# have_trace_targets = tracing.has_trace_targets(choosers) +# +# logit.validate_nest_spec(nest_spec, trace_label) +# +# if have_trace_targets: +# tracing.trace_df(choosers, '%s.choosers' % trace_label) +# +# raw_utilities = eval_utilities(spec, choosers, locals_d, +# log_alt_losers=log_alt_losers, +# trace_label=trace_label, have_trace_targets=have_trace_targets, +# estimator=estimator, trace_column_names=trace_column_names) +# chunk.log_df(trace_label, "raw_utilities", raw_utilities) +# +# if have_trace_targets: +# tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label, +# column_labels=['alternative', 'utility']) +# +# # utilities of leaves and nests +# nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) +# chunk.log_df(trace_label, "nested_utilities", nested_utilities) +# +# del raw_utilities +# chunk.log_df(trace_label, 'raw_utilities', None) +# +# if have_trace_targets: +# tracing.trace_df(nested_utilities, '%s.nested_utilities' % trace_label, +# column_labels=['alternative', 'utility']) +# +# +# ########### comparison only +# # probabilities of alternatives relative to siblings sharing the same nest +# nested_probabilities = \ +# compute_nested_probabilities(np.exp(nested_utilities), nest_spec, trace_label=trace_label) +# chunk.log_df(trace_label, "nested_probabilities", nested_probabilities) +# +# if have_trace_targets: +# tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label, +# column_labels=['alternative', 'probability']) +# +# base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) +# chunk.log_df(trace_label, "base_probabilities", base_probabilities) +# +# del nested_probabilities +# chunk.log_df(trace_label, "nested_probabilities", None) +# +# if have_trace_targets: +# tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label, +# column_labels=['alternative', 'probability']) +# +# del(base_probabilities) +# chunk.log_df(trace_label, "base_probabilities", None) +# ########### end comparison only +# +# # TODO - check this is correct +# if want_logsums: +# # logsum of nest root +# logsums = pd.Series(nested_utilities.root, index=choosers.index) +# chunk.log_df(trace_label, "logsums", logsums) +# +# # TODO: add checks on utilities? +# # # note base_probabilities could all be zero since we allowed all probs for nests to be zero +# # # check here to print a clear message but make_choices will raise error if probs don't sum to 1 +# # BAD_PROB_THRESHOLD = 0.001 +# # no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD +# # +# # if no_choices.any(): +# # +# # logit.report_bad_choices( +# # no_choices, base_probabilities, +# # trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'), +# # trace_choosers=choosers, +# # msg="base_probabilities do not sum to one") +# +# # TODO: add custom_chooser +# # if custom_chooser: +# # choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, +# # trace_label=trace_label) +# # else: +# +# choices = logit.make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=trace_label) +# +# if have_trace_targets: +# tracing.trace_df(choices, '%s.choices' % trace_label, +# columns=[None, trace_choice_name]) +# #tracing.trace_df(rands, '%s.rands' % trace_label, +# # columns=[None, 'rand']) +# if want_logsums: +# tracing.trace_df(logsums, '%s.logsums' % trace_label, +# columns=[None, 'logsum']) +# +# if want_logsums: +# choices = choices.to_frame('choice') +# choices['logsum'] = logsums +# +# return choices def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, log_alt_losers=False, want_logsums=False, trace_label=None, - trace_choice_name=None, trace_column_names=None): + trace_choice_name=None, trace_column_names=None, + choose_individual_max_utility=False): """ Run a nested-logit simulation for when the model spec does not involve alternative specific data, e.g. there are no interactions with alternative @@ -1189,6 +1190,11 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, nested_exp_utilities = compute_nested_exp_utilities(raw_utilities, nest_spec) chunk.log_df(trace_label, "nested_exp_utilities", nested_exp_utilities) + # TODO [janzill Jun2022: combine with nested_exp_utilities somehow?] + # utilities of leaves and nests + nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) + chunk.log_df(trace_label, "nested_utilities", nested_utilities) + del raw_utilities chunk.log_df(trace_label, 'raw_utilities', None) @@ -1241,7 +1247,11 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, trace_label=trace_label) else: - choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label) + choices, rands = logit.make_choices(base_probabilities, utilities=nested_utilities, nest_spec=nest_spec, + trace_label=trace_label, choose_individual_max_utility=choose_individual_max_utility) + + del(nested_utilities) + chunk.log_df(trace_label, "nested_utilities", None) del base_probabilities chunk.log_df(trace_label, 'base_probabilities', None) @@ -1319,7 +1329,6 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, set_skim_wrapper_targets(choosers, skims) if nest_spec is None: - # TODO: add frozen individual ru for mnl choices = eval_mnl(choosers, spec, locals_d, custom_chooser, log_alt_losers=log_alt_losers, want_logsums=want_logsums, @@ -1328,20 +1337,22 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, choose_individual_max_utility=choose_individual_max_utility) else: - if choose_individual_max_utility: - choices = eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, - log_alt_losers=log_alt_losers, - want_logsums=want_logsums, - estimator=estimator, - trace_label=trace_label, - trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) - else: - choices = eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, - log_alt_losers=log_alt_losers, - want_logsums=want_logsums, - estimator=estimator, - trace_label=trace_label, - trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) + ##make_choices now carries choose_individual_max_utility -> incorporate there + #if choose_individual_max_utility: + # choices = eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, + # log_alt_losers=log_alt_losers, + # want_logsums=want_logsums, + # estimator=estimator, + # trace_label=trace_label, + # trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) + #else: + choices = eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, + log_alt_losers=log_alt_losers, + want_logsums=want_logsums, + estimator=estimator, + trace_label=trace_label, + trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, + choose_individual_max_utility=choose_individual_max_utility) return choices diff --git a/notebooks/frozen_impl_dev.ipynb b/notebooks/frozen_impl_dev.ipynb index 7bf198c2b5..bd8ec4db33 100644 --- a/notebooks/frozen_impl_dev.ipynb +++ b/notebooks/frozen_impl_dev.ipynb @@ -336,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 50, "outputs": [ { "name": "stderr", @@ -380,19 +380,22 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 53, "outputs": [ { "data": { - "text/plain": "work1 61\nschool1 22\nwork_and_school 4\nwork2 2\ndtype: int64" + "text/plain": " count_prev count_new\nschool1 22 22.0\nwork1 61 65.0\nwork2 2 2.0\nwork_and_school 4 0.0", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
count_prevcount_new
school12222.0
work16165.0
work222.0
work_and_school40.0
\n
" }, - "execution_count": 48, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "ch_prev.value_counts()" + "ch_prev.value_counts().to_frame(\"count_prev\").merge(\n", + " ch_new.value_counts().to_frame(\"count_new\"), how=\"outer\", left_index=True, right_index=True\n", + ").fillna(0)" ], "metadata": { "collapsed": false, @@ -403,20 +406,9 @@ }, { "cell_type": "code", - "execution_count": 49, - "outputs": [ - { - "data": { - "text/plain": "work1 65\nschool1 22\nwork2 2\ndtype: int64" - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ch_new.value_counts()" - ], + "execution_count": null, + "outputs": [], + "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -438,9 +430,44 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 58, + "outputs": [ + { + "data": { + "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", + "display(df_)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 64, + "outputs": [ + { + "data": { + "text/plain": "{'a': 0, 'b': 1, 'c': 2}" + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{v: k for k,v in enumerate(df_.columns)}" + ], "metadata": { "collapsed": false, "pycharm": { From f725ca69093e52d25cfe3d98d0bbf7f5b477af64 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 15 Jun 2022 19:05:13 +1000 Subject: [PATCH 036/298] pass through frozen_impl flag --- activitysim/core/simulate.py | 3 ++- notebooks/frozen_impl_dev.ipynb | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 33ab7fa2da..9c4999957a 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -968,7 +968,8 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, trace_label=trace_label) else: - choices, rands = logit.make_choices(probs, utilities=utilities, trace_label=trace_label) + choices, rands = logit.make_choices(probs, utilities=utilities, trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) del utilities chunk.log_df(trace_label, 'utilities', None) diff --git a/notebooks/frozen_impl_dev.ipynb b/notebooks/frozen_impl_dev.ipynb index bd8ec4db33..050c604cb8 100644 --- a/notebooks/frozen_impl_dev.ipynb +++ b/notebooks/frozen_impl_dev.ipynb @@ -237,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 69, "outputs": [], "source": [ "from activitysim.core import inject\n", @@ -322,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 70, "outputs": [], "source": [ "#pipeline.close_pipeline()" @@ -336,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 71, "outputs": [ { "name": "stderr", @@ -380,14 +380,14 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 72, "outputs": [ { "data": { "text/plain": " count_prev count_new\nschool1 22 22.0\nwork1 61 65.0\nwork2 2 2.0\nwork_and_school 4 0.0", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
count_prevcount_new
school12222.0
work16165.0
work222.0
work_and_school40.0
\n
" }, - "execution_count": 53, + "execution_count": 72, "metadata": {}, "output_type": "execute_result" } From 1a786f26ac8bfcae0855a3a64c44f2bb47cde81c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 15 Jun 2022 19:33:05 +1000 Subject: [PATCH 037/298] implement flag in settings, apply to all simple simulate models --- .../abm/models/atwork_subtour_frequency.py | 3 +- activitysim/abm/models/auto_ownership.py | 3 +- activitysim/abm/models/free_parking.py | 3 +- .../abm/models/joint_tour_composition.py | 3 +- .../abm/models/joint_tour_frequency.py | 3 +- .../abm/models/joint_tour_participation.py | 3 +- .../abm/models/mandatory_tour_frequency.py | 3 +- activitysim/abm/models/stop_frequency.py | 3 +- activitysim/abm/models/util/mode.py | 3 +- activitysim/core/logit.py | 1 - activitysim/core/simulate.py | 6 +- .../extensions/telecommute_frequency.py | 3 +- .../extensions/transit_pass_ownership.py | 3 +- .../extensions/transit_pass_subsidy.py | 3 +- .../extensions/work_from_home.py | 3 +- notebooks/frozen_impl_dev.ipynb | 149 ++++++++++++++++++ 16 files changed, 179 insertions(+), 16 deletions(-) diff --git a/activitysim/abm/models/atwork_subtour_frequency.py b/activitysim/abm/models/atwork_subtour_frequency.py index cb9d3ca4c7..ed62db14cd 100644 --- a/activitysim/abm/models/atwork_subtour_frequency.py +++ b/activitysim/abm/models/atwork_subtour_frequency.py @@ -88,7 +88,8 @@ def atwork_subtour_frequency(tours, chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='atwork_subtour_frequency', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/auto_ownership.py b/activitysim/abm/models/auto_ownership.py index 12f8fbd2bb..a6a1e2106a 100644 --- a/activitysim/abm/models/auto_ownership.py +++ b/activitysim/abm/models/auto_ownership.py @@ -57,7 +57,8 @@ def auto_ownership_simulate(households, trace_label=trace_label, trace_choice_name='auto_ownership', log_alt_losers=log_alt_losers, - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if estimator: estimator.write_choices(choices) diff --git a/activitysim/abm/models/free_parking.py b/activitysim/abm/models/free_parking.py index b144ed0543..9f37872f9a 100644 --- a/activitysim/abm/models/free_parking.py +++ b/activitysim/abm/models/free_parking.py @@ -68,7 +68,8 @@ def free_parking( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='free_parking_at_work', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) free_parking_alt = model_settings['FREE_PARKING_ALT'] choices = (choices == free_parking_alt) diff --git a/activitysim/abm/models/joint_tour_composition.py b/activitysim/abm/models/joint_tour_composition.py index f21b8f8677..3e6cbc10f4 100644 --- a/activitysim/abm/models/joint_tour_composition.py +++ b/activitysim/abm/models/joint_tour_composition.py @@ -96,7 +96,8 @@ def joint_tour_composition( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='composition', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/joint_tour_frequency.py b/activitysim/abm/models/joint_tour_frequency.py index d8930d9834..8cf63e332d 100644 --- a/activitysim/abm/models/joint_tour_frequency.py +++ b/activitysim/abm/models/joint_tour_frequency.py @@ -87,7 +87,8 @@ def joint_tour_frequency( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='joint_tour_frequency', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index c69b5093c5..23d13ac9e5 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -314,7 +314,8 @@ def joint_tour_participation( trace_label=trace_label, trace_choice_name='participation', custom_chooser=participants_chooser, - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # choice is boolean (participate or not) choice_col = model_settings.get('participation_choice', 'participate') diff --git a/activitysim/abm/models/mandatory_tour_frequency.py b/activitysim/abm/models/mandatory_tour_frequency.py index bbe1df599e..de3c6f2a5a 100644 --- a/activitysim/abm/models/mandatory_tour_frequency.py +++ b/activitysim/abm/models/mandatory_tour_frequency.py @@ -96,7 +96,8 @@ def mandatory_tour_frequency(persons_merged, chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='mandatory_tour_frequency', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/stop_frequency.py b/activitysim/abm/models/stop_frequency.py index 14ddeb0669..db967f8b77 100644 --- a/activitysim/abm/models/stop_frequency.py +++ b/activitysim/abm/models/stop_frequency.py @@ -143,7 +143,8 @@ def stop_frequency( chunk_size=chunk_size, trace_label=tracing.extend_trace_label(trace_label, segment_name), trace_choice_name='stops', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # convert indexes to alternative names choices = pd.Series(segment_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/util/mode.py b/activitysim/abm/models/util/mode.py index 37afe6d47d..c13b9cc66b 100644 --- a/activitysim/abm/models/util/mode.py +++ b/activitysim/abm/models/util/mode.py @@ -60,7 +60,8 @@ def mode_choice_simulate( trace_label=trace_label, trace_choice_name=trace_choice_name, estimator=estimator, - trace_column_names=trace_column_names) + trace_column_names=trace_column_names, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # for consistency, always return dataframe, whether or not logsums were requested if isinstance(choices, pd.Series): diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index f9be97d77a..516add8aa9 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -303,7 +303,6 @@ def make_choices(probs, utilities=None, nest_spec=None, trace_label=None, trace_ rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) return choices, rands - # probs should sum to 1 across each row BAD_PROB_THRESHOLD = 0.001 bad_probs = \ diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 9c4999957a..0162d20616 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1425,7 +1425,8 @@ def simple_simulate_by_chunk_id(choosers, spec, nest_spec, want_logsums=False, estimator=None, trace_label=None, - trace_choice_name=None): + trace_choice_name=None, + choose_individual_max_utility=False): """ chunk_by_chunk_id wrapper for simple_simulate """ @@ -1443,7 +1444,8 @@ def simple_simulate_by_chunk_id(choosers, spec, nest_spec, want_logsums=want_logsums, estimator=estimator, trace_label=chunk_trace_label, - trace_choice_name=trace_choice_name) + trace_choice_name=trace_choice_name, + choose_individual_max_utility=choose_individual_max_utility) result_list.append(choices) diff --git a/activitysim/examples/example_semcog/extensions/telecommute_frequency.py b/activitysim/examples/example_semcog/extensions/telecommute_frequency.py index bbe72e11f1..6246820e5d 100755 --- a/activitysim/examples/example_semcog/extensions/telecommute_frequency.py +++ b/activitysim/examples/example_semcog/extensions/telecommute_frequency.py @@ -75,7 +75,8 @@ def telecommute_frequency( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='telecommute_frequency', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py b/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py index 1b9d3c1fae..1f111e3dec 100644 --- a/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py +++ b/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py @@ -69,7 +69,8 @@ def transit_pass_ownership( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='transit_pass_ownership', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if estimator: estimator.write_choices(choices) diff --git a/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py b/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py index ddbcfbef32..ae02208c83 100644 --- a/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py +++ b/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py @@ -69,7 +69,8 @@ def transit_pass_subsidy( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='transit_pass_subsidy', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if estimator: estimator.write_choices(choices) diff --git a/activitysim/examples/example_semcog/extensions/work_from_home.py b/activitysim/examples/example_semcog/extensions/work_from_home.py index f6e2728b23..38182fcd39 100755 --- a/activitysim/examples/example_semcog/extensions/work_from_home.py +++ b/activitysim/examples/example_semcog/extensions/work_from_home.py @@ -87,7 +87,8 @@ def work_from_home( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='work_from_home', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if iterations_target_percent is not None: choices_for_filter = choices[choosers[iterations_chooser_filter]] diff --git a/notebooks/frozen_impl_dev.ipynb b/notebooks/frozen_impl_dev.ipynb index 050c604cb8..d4838d8b09 100644 --- a/notebooks/frozen_impl_dev.ipynb +++ b/notebooks/frozen_impl_dev.ipynb @@ -404,6 +404,155 @@ } } }, + { + "cell_type": "code", + "execution_count": 73, + "outputs": [ + { + "data": { + "text/plain": "{'input_table_list': [{'tablename': 'households',\n 'filename': 'households.csv',\n 'index_col': 'household_id',\n 'rename_columns': {'HHID': 'household_id',\n 'PERSONS': 'hhsize',\n 'workers': 'num_workers',\n 'VEHICL': 'auto_ownership',\n 'TAZ': 'home_zone_id'},\n 'keep_columns': ['home_zone_id',\n 'income',\n 'hhsize',\n 'HHT',\n 'auto_ownership',\n 'num_workers']},\n {'tablename': 'persons',\n 'filename': 'persons.csv',\n 'index_col': 'person_id',\n 'rename_columns': {'PERID': 'person_id'},\n 'keep_columns': ['household_id',\n 'age',\n 'PNUM',\n 'sex',\n 'pemploy',\n 'pstudent',\n 'ptype']},\n {'tablename': 'land_use',\n 'filename': 'land_use.csv',\n 'index_col': 'zone_id',\n 'rename_columns': {'TAZ': 'zone_id', 'COUNTY': 'county_id'},\n 'keep_columns': ['DISTRICT',\n 'SD',\n 'county_id',\n 'TOTHH',\n 'TOTPOP',\n 'TOTACRE',\n 'RESACRE',\n 'CIACRE',\n 'TOTEMP',\n 'AGE0519',\n 'RETEMPN',\n 'FPSEMPN',\n 'HEREMPN',\n 'OTHEMPN',\n 'AGREMPN',\n 'MWTEMPN',\n 'PRKCST',\n 'OPRKCST',\n 'area_type',\n 'HSENROLL',\n 'COLLFTE',\n 'COLLPTE',\n 'TOPOLOGY',\n 'TERMINAL']}],\n 'households_sample_size': 100,\n 'check_for_variability': False,\n 'use_shadow_pricing': False,\n 'want_dest_choice_sample_tables': False,\n 'log_alt_losers': False,\n 'chunk_size': 0,\n 'min_available_chunk_ratio': 0.05,\n 'default_initial_rows_per_chunk': 500,\n 'chunk_method': 'hybrid_uss',\n 'chunk_training_mode': 'disabled',\n 'keep_chunk_logs': True,\n 'keep_mem_logs': True,\n 'trace_hh_id': [982875],\n 'trace_od': None,\n 'resume_after': None,\n 'checkpoints': True,\n 'models': ['initialize_landuse',\n 'initialize_households',\n 'compute_accessibility',\n 'school_location',\n 'workplace_location',\n 'auto_ownership_simulate',\n 'free_parking',\n 'cdap_simulate',\n 'mandatory_tour_frequency',\n 'mandatory_tour_scheduling',\n 'joint_tour_frequency',\n 'joint_tour_composition',\n 'joint_tour_participation',\n 'joint_tour_destination',\n 'joint_tour_scheduling',\n 'non_mandatory_tour_frequency',\n 'non_mandatory_tour_destination',\n 'non_mandatory_tour_scheduling',\n 'tour_mode_choice_simulate',\n 'atwork_subtour_frequency',\n 'atwork_subtour_destination',\n 'atwork_subtour_scheduling',\n 'atwork_subtour_mode_choice',\n 'stop_frequency',\n 'trip_purpose',\n 'trip_destination',\n 'trip_purpose_and_destination',\n 'trip_scheduling',\n 'trip_mode_choice',\n 'write_data_dictionary',\n 'track_skim_usage',\n 'write_trip_matrices',\n 'write_tables'],\n 'output_tables': {'h5_store': False,\n 'action': 'include',\n 'prefix': 'final_',\n 'tables': ['checkpoints',\n 'accessibility',\n 'land_use',\n 'households',\n 'persons',\n 'tours',\n 'trips',\n 'joint_tour_participants']},\n 'urban_threshold': 4,\n 'cbd_threshold': 2,\n 'rural_threshold': 6,\n 'min_value_of_time': 1,\n 'max_value_of_time': 50,\n 'distributed_vot_mu': 0.684,\n 'distributed_vot_sigma': 0.85,\n 'household_median_value_of_time': {1: 6.01, 2: 8.81, 3: 10.44, 4: 12.86},\n 'source_file_paths': ['configs/settings.yaml']}" + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inject.get_injectable('settings')" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 74, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle mandatory_tour_frequency not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running mandatory_tour_frequency with %d persons 89\n" + ] + } + ], + "source": [ + "resume_after = \"cdap_simulate\"\n", + "model_name = \"mandatory_tour_frequency\"\n", + "chunk_size = 0 # test_mtc means no chunking\n", + "\n", + "pipeline.open_pipeline(resume_after)\n", + "# preload any bulky injectables (e.g. skims) not in pipeline\n", + "inject.get_injectable('preload_injectables', None)\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "#step_name = model_name\n", + "args = {}\n", + "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "#network_los = inject.get_injectable('network_los')\n", + "persons_merged = inject.get_table('persons_merged')\n", + "\n", + "trace_label = 'mandatory_tour_frequency'\n", + "model_settings_file_name = 'mandatory_tour_frequency.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "choosers = persons_merged.to_frame()\n", + "# filter based on results of CDAP\n", + "choosers = choosers[choosers.cdap_activity == 'M']\n", + "print(\"Running mandatory_tour_frequency with %d persons\", len(choosers))\n", + "preprocessor_settings = model_settings.get('preprocessor', None)\n", + "if preprocessor_settings:\n", + " locals_dict = {}\n", + " expressions.assign_columns(\n", + " df=choosers,\n", + " model_settings=preprocessor_settings,\n", + " locals_dict=locals_dict,\n", + " trace_label=trace_label)\n", + "\n", + "estimator = estimation.manager.begin_estimation('mandatory_tour_frequency')\n", + "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", + "coefficients_df = simulate.read_model_coefficients(model_settings)\n", + "model_spec = simulate.eval_coefficients(model_spec, coefficients_df, estimator)\n", + "nest_spec = config.get_logit_model_settings(model_settings)\n", + "constants = config.get_model_constants(model_settings)\n", + "choices = simulate.simple_simulate(\n", + " choosers=choosers,\n", + " spec=model_spec,\n", + " nest_spec=nest_spec,\n", + " locals_d=constants,\n", + " chunk_size=chunk_size,\n", + " trace_label=trace_label,\n", + " trace_choice_name='mandatory_tour_frequency',\n", + " estimator=estimator,\n", + " choose_individual_max_utility=True)\n", + "\n", + "# convert indexes to alternative names\n", + "choices = pd.Series(model_spec.columns[choices.values], index=choices.index)\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, From 1d4ca9a20ae3c25dbc25c6be76e83ec0cafb86e3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 15 Jun 2022 19:39:22 +1000 Subject: [PATCH 038/298] froxen rand ind util in interaction simulate --- activitysim/core/interaction_simulate.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/activitysim/core/interaction_simulate.py b/activitysim/core/interaction_simulate.py index 292c9ccaef..b0a74ab1e0 100644 --- a/activitysim/core/interaction_simulate.py +++ b/activitysim/core/interaction_simulate.py @@ -240,7 +240,8 @@ def _interaction_simulate( skims=None, locals_d=None, sample_size=None, trace_label=None, trace_choice_name=None, log_alt_losers=False, - estimator=None): + estimator=None, + choose_individual_max_utility=False): """ Run a MNL simulation in the situation in which alternatives must be merged with choosers because there are interaction terms or @@ -379,9 +380,6 @@ def _interaction_simulate( probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) chunk.log_df(trace_label, 'probs', probs) - del utilities - chunk.log_df(trace_label, 'utilities', None) - if have_trace_targets: tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), column_labels=['alternative', 'probability']) @@ -390,7 +388,12 @@ def _interaction_simulate( # positions is series with the chosen alternative represented as a column index in probs # which is an integer between zero and num alternatives in the alternative sample positions, rands = \ - logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers) + logit.make_choices(probs, utilities=utilities, trace_label=trace_label, trace_choosers=choosers, + choose_individual_max_utility=choose_individual_max_utility) + + del utilities + chunk.log_df(trace_label, 'utilities', None) + chunk.log_df(trace_label, 'positions', positions) chunk.log_df(trace_label, 'rands', rands) @@ -420,7 +423,8 @@ def interaction_simulate( log_alt_losers=False, skims=None, locals_d=None, sample_size=None, chunk_size=0, trace_label=None, trace_choice_name=None, - estimator=None): + estimator=None, + choose_individual_max_utility=False): """ Run a simulation in the situation in which alternatives must @@ -486,7 +490,8 @@ def interaction_simulate( trace_label=chunk_trace_label, trace_choice_name=trace_choice_name, log_alt_losers=log_alt_losers, - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=choose_individual_max_utility) result_list.append(choices) From f3e16107d070ba2c787308eab2a2a42baafd2dc6 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 15 Jun 2022 19:41:11 +1000 Subject: [PATCH 039/298] add setting to interaction_simulate call --- activitysim/abm/models/non_mandatory_tour_frequency.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activitysim/abm/models/non_mandatory_tour_frequency.py b/activitysim/abm/models/non_mandatory_tour_frequency.py index 814bec97e6..0cf58e4618 100644 --- a/activitysim/abm/models/non_mandatory_tour_frequency.py +++ b/activitysim/abm/models/non_mandatory_tour_frequency.py @@ -231,7 +231,8 @@ def non_mandatory_tour_frequency(persons, persons_merged, chunk_size=chunk_size, trace_label='non_mandatory_tour_frequency.%s' % segment_name, trace_choice_name='non_mandatory_tour_frequency', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if estimator: estimator.write_choices(choices) From 9ac51c91c1438f183daa0736e451efed782416a0 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 15 Jun 2022 20:22:41 +1000 Subject: [PATCH 040/298] usage of interaction_sample_simulate --- activitysim/abm/models/location_choice.py | 4 +++- activitysim/abm/models/parking_location_choice.py | 3 ++- activitysim/abm/models/trip_destination.py | 4 +++- activitysim/abm/models/trip_scheduling_choice.py | 3 ++- activitysim/abm/models/util/tour_destination.py | 3 ++- activitysim/abm/models/util/tour_od.py | 3 ++- .../abm/models/util/vectorize_tour_scheduling.py | 3 ++- activitysim/core/interaction_sample_simulate.py | 15 ++++++++------- 8 files changed, 24 insertions(+), 14 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 5b3b7d1e47..4968a8dcce 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -528,7 +528,9 @@ def run_location_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name=model_settings['DEST_CHOICE_COLUMN_NAME'], - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) if not want_logsums: # for consistency, always return a dataframe with canonical column name diff --git a/activitysim/abm/models/parking_location_choice.py b/activitysim/abm/models/parking_location_choice.py index 5c13969eed..c81eb89691 100644 --- a/activitysim/abm/models/parking_location_choice.py +++ b/activitysim/abm/models/parking_location_choice.py @@ -124,7 +124,8 @@ def parking_destination_simulate( locals_d=locals_dict, chunk_size=chunk_size, trace_label=trace_label, - trace_choice_name='parking_loc') + trace_choice_name='parking_loc', + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # drop any failed zero_prob destinations if (parking_locations == NO_DESTINATION).any(): diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index e4dbed4444..a5724c3540 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -699,7 +699,9 @@ def trip_destination_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name='trip_dest', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) if not want_logsums: # for consistency, always return a dataframe with canonical column name diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index c950b0726f..b1e6f72626 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -261,7 +261,8 @@ def run_trip_scheduling_choice(spec, tours, skims, locals_dict, locals_d=locals_dict, trace_label=chunk_trace_label, trace_choice_name='trip_schedule_stage_1', - estimator=None + estimator=None, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) ) assert len(choices.index) == len(choosers.index) diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 7482aeb6db..bafa6c5eed 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -632,7 +632,8 @@ def run_destination_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name='destination', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if not want_logsums: # for consistency, always return a dataframe with canonical column name diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 0db7e01d74..1b47e02cdf 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -853,7 +853,8 @@ def run_od_simulate( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='origin_destination', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if not want_logsums: choices = choices.to_frame('choice') diff --git a/activitysim/abm/models/util/vectorize_tour_scheduling.py b/activitysim/abm/models/util/vectorize_tour_scheduling.py index 199059e2af..ca77ab6c08 100644 --- a/activitysim/abm/models/util/vectorize_tour_scheduling.py +++ b/activitysim/abm/models/util/vectorize_tour_scheduling.py @@ -600,7 +600,8 @@ def _schedule_tours( locals_d=locals_d, chunk_size=0, trace_label=tour_trace_label, - estimator=estimator + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) ) chunk.log_df(tour_trace_label, 'choices', choices) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 9f7a275ba5..573ac89b8d 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -22,7 +22,7 @@ def _interaction_sample_simulate( want_logsums, skims, locals_d, trace_label, trace_choice_name, - estimator): + estimator, choose_individual_max_utility): """ Run a MNL simulation in the situation in which alternatives must @@ -215,9 +215,6 @@ def _interaction_sample_simulate( logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) chunk.log_df(trace_label, 'logsums', logsums) - del utilities_df - chunk.log_df(trace_label, 'utilities_df', None) - if have_trace_targets: tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), column_labels=['alternative', 'probability']) @@ -232,7 +229,11 @@ def _interaction_sample_simulate( # positions is series with the chosen alternative represented as a column index in probs # which is an integer between zero and num alternatives in the alternative sample positions, rands = \ - logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers) + logit.make_choices(probs, utilities_df, trace_label=trace_label, trace_choosers=choosers, + choose_individual_max_utility=choose_individual_max_utility) + + del utilities_df + chunk.log_df(trace_label, 'utilities_df', None) chunk.log_df(trace_label, 'positions', positions) chunk.log_df(trace_label, 'rands', rands) @@ -287,7 +288,7 @@ def interaction_sample_simulate( want_logsums=False, skims=None, locals_d=None, chunk_size=0, chunk_tag=None, trace_label=None, trace_choice_name=None, - estimator=None): + estimator=None, choose_individual_max_utility=False): """ Run a simulation in the situation in which alternatives must @@ -357,7 +358,7 @@ def interaction_sample_simulate( want_logsums, skims, locals_d, chunk_trace_label, trace_choice_name, - estimator) + estimator, choose_individual_max_utility) result_list.append(choices) From e65635d1fb6c033b551e37fadc058142dd3c815b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 16 Jun 2022 12:55:01 +1000 Subject: [PATCH 041/298] interaction sample notebook --- .../frozen_impl_interaction_sample_dev.ipynb | 906 ++++++++++++++++++ 1 file changed, 906 insertions(+) create mode 100644 notebooks/frozen_impl_interaction_sample_dev.ipynb diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb new file mode 100644 index 0000000000..4d5db96446 --- /dev/null +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -0,0 +1,906 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", + "metadata": {}, + "source": [ + "# validate results\n", + "\n", + "## TODO\n", + "what happened to tracing when I fixed probability calcs - it seems like all trip ids are attached when I add one by\n", + "hand below - why?" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.385153Z", + "iopub.status.busy": "2022-05-01T10:17:57.384881Z", + "iopub.status.idle": "2022-05-01T10:17:57.534433Z", + "shell.execute_reply": "2022-05-01T10:17:57.533096Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%load_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c3dba451-1e10-403e-8614-35d57e6577f4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:17:57.536623Z", + "iopub.status.busy": "2022-05-01T10:17:57.536012Z", + "iopub.status.idle": "2022-05-01T10:17:57.542755Z", + "shell.execute_reply": "2022-05-01T10:17:57.541685Z", + "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:00.350944Z", + "iopub.status.busy": "2022-05-01T10:18:00.350730Z", + "iopub.status.idle": "2022-05-01T10:18:12.760977Z", + "shell.execute_reply": "2022-05-01T10:18:12.760013Z", + "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import argparse\n", + "from datetime import datetime\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from activitysim.cli import run\n", + "from activitysim.core import inject" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:16.944537Z", + "iopub.status.busy": "2022-05-01T10:18:16.944291Z", + "iopub.status.idle": "2022-05-01T10:18:17.124764Z", + "shell.execute_reply": "2022-05-01T10:18:17.123725Z", + "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "pd.set_option(\"max_columns\", 500)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "outputs": [ + { + "data": { + "text/plain": "array([2, 1, 2])" + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.argmax(np.array([[1,2,3],[4,6,5],[7,8,9]]), axis=1)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 34, + "outputs": [ + { + "data": { + "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": "0 c\n1 b\n2 c\ndtype: object" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", + "display(df_)\n", + "ch_ = df_.idxmax(1)\n", + "display(ch_)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 58, + "outputs": [ + { + "data": { + "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", + "display(df_)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 64, + "outputs": [ + { + "data": { + "text/plain": "{'a': 0, 'b': 1, 'c': 2}" + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "{v: k for k,v in enumerate(df_.columns)}" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:17.839947Z", + "iopub.status.busy": "2022-05-01T10:18:17.839070Z", + "iopub.status.idle": "2022-05-01T10:18:18.019676Z", + "shell.execute_reply": "2022-05-01T10:18:18.018689Z", + "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_example_mtc\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:18.894533Z", + "iopub.status.busy": "2022-05-01T10:18:18.894303Z", + "iopub.status.idle": "2022-05-01T10:18:19.078807Z", + "shell.execute_reply": "2022-05-01T10:18:19.077951Z", + "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "os.chdir(example_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T10:18:22.111723Z", + "iopub.status.busy": "2022-05-01T10:18:22.111490Z", + "iopub.status.idle": "2022-05-01T10:18:22.297437Z", + "shell.execute_reply": "2022-05-01T10:18:22.296501Z", + "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "parser = argparse.ArgumentParser()\n", + "run.add_run_args(parser)\n", + "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", + "#run.run(args) # 2mins full example run\n", + "if not inject.is_injectable('preload_injectables'):\n", + " from activitysim import abm # register abm steps and other abm-specific injectables\n", + "run.handle_standard_args(args) # possibly update injectables" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "outputs": [], + "source": [ + "from activitysim.core import inject\n", + "from activitysim.core import pipeline\n", + "from activitysim.core import config\n", + "from activitysim.core import simulate\n", + "from activitysim.abm.models.util import estimation\n", + "from activitysim.abm.tables import shadow_pricing\n", + "from activitysim.core import interaction_simulate\n", + "from activitysim.core import logit\n", + "from activitysim.core.simulate import set_skim_wrapper_targets\n", + "from activitysim.core import chunk" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 64, + "outputs": [], + "source": [ + "def hack_make_sample_choices(\n", + " choosers, probs,\n", + " alternatives,\n", + " sample_size, alternative_count, alt_col_name,\n", + " allow_zero_probs,\n", + " trace_label):\n", + " assert isinstance(probs, pd.DataFrame)\n", + " assert probs.shape == (len(choosers), alternative_count)\n", + "\n", + " assert isinstance(alternatives, pd.DataFrame)\n", + " assert len(alternatives) == alternative_count\n", + "\n", + " if allow_zero_probs:\n", + " zero_probs = (probs.sum(axis=1) == 0)\n", + " if zero_probs.all():\n", + " return pd.DataFrame(columns=[alt_col_name, 'rand', 'prob', choosers.index.name])\n", + " if zero_probs.any():\n", + " # remove from sample\n", + " probs = probs[~zero_probs]\n", + " choosers = choosers[~zero_probs]\n", + "\n", + " cum_probs_array = probs.values.cumsum(axis=1)\n", + "\n", + " # alt probs in convenient layout to return prob of chose alternative\n", + " # (same layout as cum_probs_arr)\n", + " alt_probs_array = probs.values.flatten()\n", + "\n", + " # get sample_size rands for each chooser\n", + " rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size)\n", + "\n", + " # transform as we iterate over alternatives\n", + " # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr\n", + " # i.e rands[i] is a 2-D array of one alt choice rand for each chooser\n", + " rands = rands.T.reshape(sample_size, -1, 1)\n", + "\n", + " # the alternative value chosen\n", + " choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype)\n", + " # chunk log these later after we populate them...\n", + " # the probability of the chosen alternative\n", + " choice_probs_array = np.empty([sample_size, len(choosers)])\n", + " # chunk log these later after we populate them...\n", + " alts = np.tile(alternatives.index.values, len(choosers))\n", + "\n", + " # FIXME - do this all at once rather than iterate?\n", + " for i in range(sample_size):\n", + " # FIXME - do this in numpy, not pandas?\n", + " # rands for this alt in broadcastable shape\n", + " r = rands[i]\n", + "\n", + " # position of first occurrence of positive value\n", + " positions = np.argmax(cum_probs_array > r, axis=1)\n", + "\n", + " # FIXME - leave positions as numpy array, not pandas series?\n", + " # positions is series with the chosen alternative represented as a column index in probs\n", + " # which is an integer between zero and num alternatives in the alternative sample\n", + " positions = pd.Series(positions, index=probs.index)\n", + "\n", + " # need to get from an integer offset into the alternative sample to the alternative index\n", + " # that is, we want the index value of the row that is offset by rows into the\n", + " # tranche of this choosers alternatives created by cross join of alternatives and choosers\n", + "\n", + " # offsets is the offset into model_design df of first row of chooser alternatives\n", + " offsets = np.arange(len(positions)) * alternative_count\n", + "\n", + " # choices and choice_probs have one element per chooser and is in same order as choosers\n", + " choices_array[i] = np.take(alts, positions + offsets)\n", + " choice_probs_array[i] = np.take(alt_probs_array, positions + offsets)\n", + "\n", + " del positions\n", + " del offsets\n", + "\n", + " del alts\n", + " del cum_probs_array\n", + " del alt_probs_array\n", + "\n", + " # explode to one row per chooser.index, alt_zone_id\n", + " choices_df = pd.DataFrame(\n", + " {alt_col_name: choices_array.flatten(order='F'),\n", + " 'rand': rands.flatten(order='F'),\n", + " 'prob': choice_probs_array.flatten(order='F'),\n", + " choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", + " })\n", + "\n", + " del choices_array\n", + " del rands\n", + " del choice_probs_array\n", + " return choices_df" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 65, + "outputs": [], + "source": [ + "def hack_interaction_sample(\n", + " choosers, alternatives,\n", + " spec, sample_size, alt_col_name,\n", + " allow_zero_probs=False,\n", + " log_alt_losers=False,\n", + " skims=None,\n", + " locals_d=None,\n", + " trace_label=None):\n", + "\n", + " num_choosers = len(choosers.index)\n", + " assert num_choosers > 0\n", + " if len(spec.columns) > 1:\n", + " raise RuntimeError('spec must have only one column')\n", + " # if using skims, copy index into the dataframe, so it will be\n", + " # available as the \"destination\" for set_skim_wrapper_targets\n", + " if skims is not None and alternatives.index.name not in alternatives:\n", + " alternatives = alternatives.copy()\n", + " alternatives[alternatives.index.name] = alternatives.index\n", + "\n", + " chooser_index_id = interaction_simulate.ALT_CHOOSER_ID if log_alt_losers else None\n", + "\n", + " # - cross join choosers and alternatives (cartesian product)\n", + " # for every chooser, there will be a row for each alternative\n", + " # index values (non-unique) are from alternatives df\n", + " alternative_count = alternatives.shape[0]\n", + " interaction_df = \\\n", + " logit.interaction_dataset(choosers, alternatives, sample_size=alternative_count,\n", + " chooser_index_id=chooser_index_id)\n", + "\n", + " assert alternative_count == len(interaction_df.index) / len(choosers.index)\n", + "\n", + " if skims is not None:\n", + " set_skim_wrapper_targets(interaction_df, skims)\n", + "\n", + " # evaluate expressions from the spec multiply by coefficients and sum\n", + " # spec is df with one row per spec expression and one col with utility coefficient\n", + " # column names of interaction_df match spec index values\n", + " # utilities has utility value for element in the cross product of choosers and alternatives\n", + " # interaction_utilities is a df with one utility column and one row per row in interaction_df\n", + " trace_rows = trace_ids = None\n", + "\n", + " # interaction_utilities is a df with one utility column and one row per interaction_df row\n", + " interaction_utilities, trace_eval_results = interaction_simulate.eval_interaction_utilities(\n", + " spec, interaction_df, locals_d, trace_label, trace_rows, estimator=None,\n", + " log_alt_losers=log_alt_losers\n", + " )\n", + " # ########### HWM - high water mark (point of max observed memory usage)\n", + " del interaction_df\n", + "\n", + " # reshape utilities (one utility column and one row per row in interaction_utilities)\n", + " # to a dataframe with one row per chooser and one column per alternative\n", + " utilities = pd.DataFrame(\n", + " interaction_utilities.values.reshape(len(choosers), alternative_count),\n", + " index=choosers.index)\n", + " del interaction_utilities\n", + "\n", + " # convert to probabilities (utilities exponentiated and normalized to probs)\n", + " # probs is same shape as utilities, one row per chooser and one column for alternative\n", + " probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", + " trace_label=trace_label, trace_choosers=choosers)\n", + " del utilities\n", + "\n", + " if sample_size == 0:\n", + " # FIXME return full alternative set rather than sample\n", + " print(\"Estimation mode for %s using unsampled alternatives\" % (trace_label, ))\n", + " index_name = probs.index.name\n", + " choices_df = \\\n", + " pd.melt(probs.reset_index(), id_vars=[index_name])\\\n", + " .sort_values(by=index_name, kind='mergesort')\\\n", + " .set_index(index_name)\\\n", + " .rename(columns={'value': 'prob'})\\\n", + " .drop(columns='variable')\n", + " choices_df['pick_count'] = 1\n", + " choices_df.insert(0, alt_col_name, np.tile(alternatives.index.values, len(choosers.index)))\n", + " return choices_df\n", + " else:\n", + " choices_df = hack_make_sample_choices(\n", + " choosers, probs, alternatives,\n", + " sample_size, alternative_count, alt_col_name,\n", + " allow_zero_probs=allow_zero_probs,\n", + " trace_label=trace_label)\n", + "\n", + " del probs\n", + "\n", + " # pick_count and pick_dup\n", + " # pick_count is number of duplicate picks\n", + " # pick_dup flag is True for all but first of duplicates\n", + " pick_group = choices_df.groupby([choosers.index.name, alt_col_name])\n", + " # number each item in each group from 0 to the length of that group - 1.\n", + " choices_df['pick_count'] = pick_group.cumcount(ascending=True)\n", + " # flag duplicate rows after first\n", + " choices_df['pick_dup'] = choices_df['pick_count'] > 0\n", + " # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", + " choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", + " # drop the duplicates\n", + " choices_df = choices_df[~choices_df['pick_dup']]\n", + " del choices_df['pick_dup']\n", + " # set index after groupby so we can trace on it\n", + " choices_df.set_index(choosers.index.name, inplace=True)\n", + "\n", + " # don't need this after tracing\n", + " del choices_df['rand']\n", + "\n", + " # - NARROW\n", + " choices_df['prob'] = choices_df['prob'].astype(np.float32)\n", + " assert (choices_df['pick_count'].max() < 4294967295) or (choices_df.empty)\n", + " choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32)\n", + "\n", + " return choices_df" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 86, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "estimation bundle school_location not in settings file estimation.yaml\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "running university, 3\n", + "dropping 19 of 25 rows where size_term is zero\n", + "Running school_location with 17 persons\n", + "running highschool, 2\n", + "dropping 23 of 25 rows where size_term is zero\n", + "Running school_location with 5 persons\n", + "running gradeschool, 1\n", + "dropping 0 of 25 rows where size_term is zero\n", + "Running school_location with 17 persons\n", + "CPU times: user 219 ms, sys: 0 ns, total: 219 ms\n", + "Wall time: 264 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "resume_after = \"compute_accessibility\"\n", + "model_name = \"school_location\"\n", + "chunk_size = 0 # test_mtc means no chunking\n", + "\n", + "pipeline.open_pipeline(resume_after)\n", + "# preload any bulky injectables (e.g. skims) not in pipeline\n", + "inject.get_injectable('preload_injectables', None)\n", + "pipeline._PIPELINE.rng().begin_step(model_name)\n", + "#step_name = model_name\n", + "args = {}\n", + "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", + "inject.set_step_args(args)\n", + "\n", + "\n", + "persons_merged = inject.get_table('persons_merged')\n", + "network_los = inject.get_injectable('network_los')\n", + "households = inject.get_table('households')\n", + "persons = inject.get_table('persons')\n", + "locutor = inject.get_injectable('locutor')\n", + "\n", + "trace_label = model_name #'school_location'\n", + "model_settings_file_name = f\"{model_name}.yaml\" #'school_location.yaml'\n", + "model_settings = config.read_model_settings(model_settings_file_name)\n", + "\n", + "estimator = estimation.manager.begin_estimation(model_name)\n", + "\n", + "# iterate_location_choice(\n", + "# model_settings,\n", + "# persons_merged, persons, households,\n", + "# network_los,\n", + "# estimator,\n", + "# chunk_size, trace_hh_id, locutor, trace_label\n", + "# )\n", + "\n", + "chunk_tag = trace_label\n", + "\n", + "# boolean to filter out persons not needing location modeling (e.g. is_worker, is_student)\n", + "chooser_filter_column = model_settings['CHOOSER_FILTER_COLUMN_NAME']\n", + "\n", + "dest_choice_column_name = model_settings['DEST_CHOICE_COLUMN_NAME']\n", + "logsum_column_name = model_settings.get('DEST_CHOICE_LOGSUM_COLUMN_NAME')\n", + "\n", + "sample_table_name = model_settings.get('DEST_CHOICE_SAMPLE_TABLE_NAME')\n", + "want_sample_table = config.setting('want_dest_choice_sample_tables') and sample_table_name is not None\n", + "\n", + "persons_merged_df = persons_merged.to_frame()\n", + "\n", + "persons_merged_df = persons_merged_df[persons_merged_df[chooser_filter_column]]\n", + "\n", + "persons_merged_df.sort_index(inplace=True) # interaction_sample expects chooser index to be monotonic increasing\n", + "\n", + "# chooser segmentation allows different sets coefficients for e.g. different income_segments or tour_types\n", + "chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME']\n", + "\n", + "assert chooser_segment_column in persons_merged_df, f\"CHOOSER_SEGMENT_COLUMN '{chooser_segment_column}' not in \" \\\n", + " f\"persons_merged table.\"\n", + "\n", + "shadow_price_calculator = shadow_pricing.load_shadow_price_calculator(model_settings)\n", + "\n", + "chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME']\n", + "\n", + "# maps segment names to compact (integer) ids\n", + "segment_ids = model_settings['SEGMENT_IDS']\n", + "\n", + "#choices_list = []\n", + "sample_list = []\n", + "for segment_name, segment_id in segment_ids.items():\n", + " print(f\"running {segment_name}, {segment_id}\")\n", + " choosers = persons_merged_df[persons_merged_df[chooser_segment_column] == segment_id]\n", + "\n", + " # size_term and shadow price adjustment - one row per zone\n", + " dest_size_terms = shadow_price_calculator.dest_size_terms(segment_name)\n", + "\n", + " assert dest_size_terms.index.is_monotonic_increasing, f\"shadow_price_calculator.dest_size_terms({segment_name}) \" \\\n", + " f\"not monotonic_increasing\"\n", + " if choosers.shape[0] == 0:\n", + " print(f\"{trace_label} skipping segment {segment_name}: no choosers\")\n", + " continue\n", + "\n", + " print(f\"dropping {(~(dest_size_terms.size_term > 0)).sum()} \"\n", + " f\"of {len(dest_size_terms)} rows where size_term is zero\")\n", + " dest_size_terms = dest_size_terms[dest_size_terms.size_term > 0]\n", + "\n", + " chooser_columns = model_settings['SIMULATE_CHOOSER_COLUMNS']\n", + " choosers_location_sample = choosers[chooser_columns]\n", + " skim_dict = network_los.get_default_skim_dict()\n", + " skims = skim_dict.wrap('home_zone_id', 'zone_id')\n", + " alt_dest_col_name = model_settings['ALT_DEST_COL_NAME']\n", + "\n", + " assert not choosers_location_sample.empty\n", + " print(\"Running %s with %d persons\" % (trace_label, len(choosers_location_sample.index)))\n", + " sample_size = model_settings[\"SAMPLE_SIZE\"]\n", + " locals_d = {\n", + " 'skims': skims,\n", + " 'segment_size': segment_name\n", + " }\n", + " constants = config.get_model_constants(model_settings)\n", + " locals_d.update(constants)\n", + " spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC',\n", + " segment_name=segment_name, estimator=estimator)\n", + " ### choices = interaction_sample(\n", + " alt_col_name=alt_dest_col_name\n", + " allow_zero_probs=False\n", + " log_alt_losers=False\n", + "\n", + " # we return alternatives ordered in (index, alt_col_name)\n", + " # if choosers index is not ordered, it is probably a mistake, since the alts wont line up\n", + " assert alt_col_name is not None\n", + " assert choosers.index.is_monotonic_increasing\n", + "\n", + " # FIXME - legacy logic - not sure this is needed or even correct?\n", + " sample_size = min(sample_size, len(dest_size_terms.index))\n", + "\n", + " result_list = []\n", + " for i, chooser_chunk, chunk_trace_label in chunk.adaptive_chunked_choosers(choosers_location_sample, chunk_size, trace_label,\n", + " chunk_tag):\n", + "\n", + " ### choices = hack_interaction_sample\n", + " # chooser = chooser_chunk\n", + " #alternatives = dest_size_terms\n", + " #trace_label=chunk_trace_label\n", + "\n", + " num_choosers = len(chooser_chunk.index)\n", + " assert num_choosers > 0\n", + " if len(spec.columns) > 1:\n", + " raise RuntimeError('spec must have only one column')\n", + " # if using skims, copy index into the dataframe, so it will be\n", + " # available as the \"destination\" for set_skim_wrapper_targets\n", + " if skims is not None and dest_size_terms.index.name not in dest_size_terms:\n", + " dest_size_terms = dest_size_terms.copy()\n", + " dest_size_terms[dest_size_terms.index.name] = dest_size_terms.index\n", + "\n", + " chooser_index_id = interaction_simulate.ALT_CHOOSER_ID if log_alt_losers else None\n", + "\n", + " # - cross join choosers and alternatives (cartesian product)\n", + " # for every chooser, there will be a row for each alternative\n", + " # index values (non-unique) are from alternatives df\n", + " alternative_count = dest_size_terms.shape[0]\n", + " interaction_df =\\\n", + " logit.interaction_dataset(chooser_chunk, dest_size_terms, sample_size=alternative_count,\n", + " chooser_index_id=chooser_index_id)\n", + "\n", + " assert alternative_count == len(interaction_df.index) / len(chooser_chunk.index)\n", + "\n", + " if skims is not None:\n", + " set_skim_wrapper_targets(interaction_df, skims)\n", + "\n", + " # evaluate expressions from the spec multiply by coefficients and sum\n", + " # spec is df with one row per spec expression and one col with utility coefficient\n", + " # column names of interaction_df match spec index values\n", + " # utilities has utility value for element in the cross product of choosers and alternatives\n", + " # interaction_utilities is a df with one utility column and one row per row in interaction_df\n", + " trace_rows = trace_ids = None\n", + "\n", + " # interaction_utilities is a df with one utility column and one row per interaction_df row\n", + " interaction_utilities, trace_eval_results = interaction_simulate.eval_interaction_utilities(\n", + " spec, interaction_df, locals_d, chunk_trace_label, trace_rows, estimator=None,\n", + " log_alt_losers=log_alt_losers\n", + " )\n", + " # ########### HWM - high water mark (point of max observed memory usage)\n", + " #del interaction_df\n", + "\n", + " # reshape utilities (one utility column and one row per row in interaction_utilities)\n", + " # to a dataframe with one row per chooser and one column per alternative\n", + " utilities = pd.DataFrame(\n", + " interaction_utilities.values.reshape(len(chooser_chunk), alternative_count),\n", + " index=chooser_chunk.index)\n", + " #del interaction_utilities\n", + "\n", + " # convert to probabilities (utilities exponentiated and normalized to probs)\n", + " # probs is same shape as utilities, one row per chooser and one column for alternative\n", + " probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", + " trace_label=chunk_trace_label, trace_choosers=chooser_chunk)\n", + " #del utilities\n", + "\n", + " choices_df = hack_make_sample_choices(\n", + " chooser_chunk, probs, dest_size_terms,\n", + " sample_size, alternative_count, alt_col_name,\n", + " allow_zero_probs=allow_zero_probs,\n", + " trace_label=chunk_trace_label)\n", + "\n", + " # pick_count and pick_dup\n", + " # pick_count is number of duplicate picks\n", + " # pick_dup flag is True for all but first of duplicates\n", + " pick_group = choices_df.groupby([choosers.index.name, alt_col_name])\n", + " # number each item in each group from 0 to the length of that group - 1.\n", + " choices_df['pick_count'] = pick_group.cumcount(ascending=True)\n", + " # flag duplicate rows after first\n", + " choices_df['pick_dup'] = choices_df['pick_count'] > 0\n", + " # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", + " choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", + " # drop the duplicates\n", + " choices_df = choices_df[~choices_df['pick_dup']]\n", + " del choices_df['pick_dup']\n", + " # set index after groupby so we can trace on it\n", + " choices_df.set_index(choosers.index.name, inplace=True)\n", + " # don't need this after tracing\n", + " del choices_df['rand']\n", + " # - NARROW\n", + " choices_df['prob'] = choices_df['prob'].astype(np.float32)\n", + " assert (choices_df['pick_count'].max() < 4294967295) or (choices_df.empty)\n", + " choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32)\n", + "\n", + "\n", + "\n", + "\n", + " if choices_df.shape[0] > 0:\n", + " result_list.append(choices_df)\n", + "\n", + " if len(result_list) > 1:\n", + " choices_df = pd.concat(result_list)\n", + " assert allow_zero_probs or (len(choosers_location_sample.index) == len(np.unique(choices_df.index.values))), \\\n", + " \"what is this\"\n", + " # keep alts in canonical order so choices based on their probs are stable across runs\n", + " choices_df = choices_df.sort_values(by=alt_col_name).sort_index(kind='mergesort')\n", + "\n", + " sample_list.append(choices_df)\n", + "\n", + "finalise = True\n", + "if finalise:\n", + " inject.set_step_args(None)\n", + " #\n", + " pipeline._PIPELINE.rng().end_step(model_name)\n", + " pipeline.add_checkpoint(model_name)\n", + " if not pipeline.intermediate_checkpoint():\n", + " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", + "\n", + " pipeline.close_pipeline()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 91, + "outputs": [ + { + "data": { + "text/plain": "10" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": "person_id\n386008 10\n418442 10\n595686 10\n644292 10\n644478 10\n1958678 10\n2159059 10\n2219998 10\n2458502 10\n2458503 10\n2566700 10\n2566701 10\n2566702 10\n2877287 10\n3596365 10\n3891104 10\n4171620 10\nName: pick_count, dtype: uint32" + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "display(sample_size)\n", + "sample_list[2].groupby('person_id').pick_count.sum()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 71, + "outputs": [ + { + "ename": "AssertionError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mAssertionError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_10980/63825533.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 43\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 44\u001B[0m \u001B[0;31m# interaction_utilities is a df with one utility column and one row per interaction_df row\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 45\u001B[0;31m interaction_utilities, trace_eval_results = interaction_simulate.eval_interaction_utilities(\n\u001B[0m\u001B[1;32m 46\u001B[0m \u001B[0mspec\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0minteraction_df\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mlocals_d\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrace_label\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrace_rows\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mestimator\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mNone\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 47\u001B[0m \u001B[0mlog_alt_losers\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mlog_alt_losers\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/interaction_simulate.py\u001B[0m in \u001B[0;36meval_interaction_utilities\u001B[0;34m(spec, df, locals_d, trace_label, trace_rows, estimator, log_alt_losers)\u001B[0m\n\u001B[1;32m 66\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"Running eval_interaction_utilities on %s rows\"\u001B[0m \u001B[0;34m%\u001B[0m \u001B[0mdf\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mshape\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m0\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 67\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 68\u001B[0;31m \u001B[0;32mwith\u001B[0m \u001B[0mchunk\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchunk_log\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtrace_label\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 69\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 70\u001B[0m \u001B[0;32massert\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mspec\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mcolumns\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m1\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m~/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py\u001B[0m in \u001B[0;36m__enter__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 111\u001B[0m \u001B[0;32mdel\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mkwds\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfunc\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 112\u001B[0m \u001B[0;32mtry\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 113\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mnext\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgen\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 114\u001B[0m \u001B[0;32mexcept\u001B[0m \u001B[0mStopIteration\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 115\u001B[0m \u001B[0;32mraise\u001B[0m \u001B[0mRuntimeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"generator didn't yield\"\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mfrom\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/chunk.py\u001B[0m in \u001B[0;36mchunk_log\u001B[0;34m(trace_label, chunk_tag, base)\u001B[0m\n\u001B[1;32m 927\u001B[0m \u001B[0;31m# avoids breaking the assertion below.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 928\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 929\u001B[0;31m \u001B[0;32massert\u001B[0m \u001B[0mbase\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;34m(\u001B[0m\u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mCHUNK_SIZERS\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 930\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 931\u001B[0m \u001B[0mtrace_label\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;34mf\"{trace_label}.chunk_log\"\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mAssertionError\u001B[0m: " + ] + } + ], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 18, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file From 9950f846fe56926df8cf9fd167155e38fb104678 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 16 Jun 2022 13:28:45 +1000 Subject: [PATCH 042/298] interaction_sample dev work --- .../frozen_impl_interaction_sample_dev.ipynb | 206 +++++++----------- 1 file changed, 78 insertions(+), 128 deletions(-) diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index 4d5db96446..e82c93c057 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -412,129 +412,7 @@ }, { "cell_type": "code", - "execution_count": 65, - "outputs": [], - "source": [ - "def hack_interaction_sample(\n", - " choosers, alternatives,\n", - " spec, sample_size, alt_col_name,\n", - " allow_zero_probs=False,\n", - " log_alt_losers=False,\n", - " skims=None,\n", - " locals_d=None,\n", - " trace_label=None):\n", - "\n", - " num_choosers = len(choosers.index)\n", - " assert num_choosers > 0\n", - " if len(spec.columns) > 1:\n", - " raise RuntimeError('spec must have only one column')\n", - " # if using skims, copy index into the dataframe, so it will be\n", - " # available as the \"destination\" for set_skim_wrapper_targets\n", - " if skims is not None and alternatives.index.name not in alternatives:\n", - " alternatives = alternatives.copy()\n", - " alternatives[alternatives.index.name] = alternatives.index\n", - "\n", - " chooser_index_id = interaction_simulate.ALT_CHOOSER_ID if log_alt_losers else None\n", - "\n", - " # - cross join choosers and alternatives (cartesian product)\n", - " # for every chooser, there will be a row for each alternative\n", - " # index values (non-unique) are from alternatives df\n", - " alternative_count = alternatives.shape[0]\n", - " interaction_df = \\\n", - " logit.interaction_dataset(choosers, alternatives, sample_size=alternative_count,\n", - " chooser_index_id=chooser_index_id)\n", - "\n", - " assert alternative_count == len(interaction_df.index) / len(choosers.index)\n", - "\n", - " if skims is not None:\n", - " set_skim_wrapper_targets(interaction_df, skims)\n", - "\n", - " # evaluate expressions from the spec multiply by coefficients and sum\n", - " # spec is df with one row per spec expression and one col with utility coefficient\n", - " # column names of interaction_df match spec index values\n", - " # utilities has utility value for element in the cross product of choosers and alternatives\n", - " # interaction_utilities is a df with one utility column and one row per row in interaction_df\n", - " trace_rows = trace_ids = None\n", - "\n", - " # interaction_utilities is a df with one utility column and one row per interaction_df row\n", - " interaction_utilities, trace_eval_results = interaction_simulate.eval_interaction_utilities(\n", - " spec, interaction_df, locals_d, trace_label, trace_rows, estimator=None,\n", - " log_alt_losers=log_alt_losers\n", - " )\n", - " # ########### HWM - high water mark (point of max observed memory usage)\n", - " del interaction_df\n", - "\n", - " # reshape utilities (one utility column and one row per row in interaction_utilities)\n", - " # to a dataframe with one row per chooser and one column per alternative\n", - " utilities = pd.DataFrame(\n", - " interaction_utilities.values.reshape(len(choosers), alternative_count),\n", - " index=choosers.index)\n", - " del interaction_utilities\n", - "\n", - " # convert to probabilities (utilities exponentiated and normalized to probs)\n", - " # probs is same shape as utilities, one row per chooser and one column for alternative\n", - " probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", - " trace_label=trace_label, trace_choosers=choosers)\n", - " del utilities\n", - "\n", - " if sample_size == 0:\n", - " # FIXME return full alternative set rather than sample\n", - " print(\"Estimation mode for %s using unsampled alternatives\" % (trace_label, ))\n", - " index_name = probs.index.name\n", - " choices_df = \\\n", - " pd.melt(probs.reset_index(), id_vars=[index_name])\\\n", - " .sort_values(by=index_name, kind='mergesort')\\\n", - " .set_index(index_name)\\\n", - " .rename(columns={'value': 'prob'})\\\n", - " .drop(columns='variable')\n", - " choices_df['pick_count'] = 1\n", - " choices_df.insert(0, alt_col_name, np.tile(alternatives.index.values, len(choosers.index)))\n", - " return choices_df\n", - " else:\n", - " choices_df = hack_make_sample_choices(\n", - " choosers, probs, alternatives,\n", - " sample_size, alternative_count, alt_col_name,\n", - " allow_zero_probs=allow_zero_probs,\n", - " trace_label=trace_label)\n", - "\n", - " del probs\n", - "\n", - " # pick_count and pick_dup\n", - " # pick_count is number of duplicate picks\n", - " # pick_dup flag is True for all but first of duplicates\n", - " pick_group = choices_df.groupby([choosers.index.name, alt_col_name])\n", - " # number each item in each group from 0 to the length of that group - 1.\n", - " choices_df['pick_count'] = pick_group.cumcount(ascending=True)\n", - " # flag duplicate rows after first\n", - " choices_df['pick_dup'] = choices_df['pick_count'] > 0\n", - " # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", - " choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", - " # drop the duplicates\n", - " choices_df = choices_df[~choices_df['pick_dup']]\n", - " del choices_df['pick_dup']\n", - " # set index after groupby so we can trace on it\n", - " choices_df.set_index(choosers.index.name, inplace=True)\n", - "\n", - " # don't need this after tracing\n", - " del choices_df['rand']\n", - "\n", - " # - NARROW\n", - " choices_df['prob'] = choices_df['prob'].astype(np.float32)\n", - " assert (choices_df['pick_count'].max() < 4294967295) or (choices_df.empty)\n", - " choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32)\n", - "\n", - " return choices_df" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 86, + "execution_count": 92, "outputs": [ { "name": "stderr", @@ -556,8 +434,8 @@ "running gradeschool, 1\n", "dropping 0 of 25 rows where size_term is zero\n", "Running school_location with 17 persons\n", - "CPU times: user 219 ms, sys: 0 ns, total: 219 ms\n", - "Wall time: 264 ms\n" + "CPU times: user 188 ms, sys: 31.2 ms, total: 219 ms\n", + "Wall time: 216 ms\n" ] } ], @@ -738,12 +616,14 @@ " trace_label=chunk_trace_label, trace_choosers=chooser_chunk)\n", " #del utilities\n", "\n", - " choices_df = hack_make_sample_choices(\n", + " temp_choices = hack_make_sample_choices(\n", " chooser_chunk, probs, dest_size_terms,\n", " sample_size, alternative_count, alt_col_name,\n", " allow_zero_probs=allow_zero_probs,\n", " trace_label=chunk_trace_label)\n", "\n", + " choices_df = temp_choices.copy()\n", + "\n", " # pick_count and pick_dup\n", " # pick_count is number of duplicate picks\n", " # pick_dup flag is True for all but first of duplicates\n", @@ -767,8 +647,6 @@ " choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32)\n", "\n", "\n", - "\n", - "\n", " if choices_df.shape[0] > 0:\n", " result_list.append(choices_df)\n", "\n", @@ -799,6 +677,78 @@ } } }, + { + "cell_type": "code", + "execution_count": 98, + "outputs": [ + { + "data": { + "text/plain": " alt_dest rand prob person_id\n0 8 0.222922 0.157982 386008\n1 9 0.404176 0.336708 386008\n2 8 0.223154 0.157982 386008\n3 9 0.457402 0.336708 386008\n4 11 0.812506 0.069858 386008\n.. ... ... ... ...\n165 7 0.107363 0.062739 4171620\n166 11 0.517573 0.070848 4171620\n167 10 0.375828 0.097048 4171620\n168 9 0.279810 0.111409 4171620\n169 20 0.848968 0.026642 4171620\n\n[170 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
080.2229220.157982386008
190.4041760.336708386008
280.2231540.157982386008
390.4574020.336708386008
4110.8125060.069858386008
...............
16570.1073630.0627394171620
166110.5175730.0708484171620
167100.3758280.0970484171620
16890.2798100.1114094171620
169200.8489680.0266424171620
\n

170 rows × 4 columns

\n
" + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "temp_choices" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 97, + "outputs": [ + { + "data": { + "text/plain": " alt_dest prob pick_count\nperson_id \n386008 8 0.157982 2\n386008 9 0.336708 5\n386008 10 0.205884 1\n386008 11 0.069858 1\n386008 17 0.009346 1\n... ... ... ...\n4171620 9 0.111409 1\n4171620 10 0.097048 1\n4171620 11 0.070848 1\n4171620 20 0.026642 1\n4171620 25 0.062053 1\n\n[105 rows x 3 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destprobpick_count
person_id
38600880.1579822
38600890.3367085
386008100.2058841
386008110.0698581
386008170.0093461
............
417162090.1114091
4171620100.0970481
4171620110.0708481
4171620200.0266421
4171620250.0620531
\n

105 rows × 3 columns

\n
" + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample_list[-1]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": 91, From 36789a3a2c32f790f3cfafd7a22c2ce155138e56 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 16 Jun 2022 18:54:04 +1000 Subject: [PATCH 043/298] interaction_sample dev work --- .../frozen_impl_interaction_sample_dev.ipynb | 523 ++++++++++++------ 1 file changed, 350 insertions(+), 173 deletions(-) diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index e82c93c057..ad34deebeb 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -98,19 +98,32 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 112, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(3, 3, 2)\n", + "[[2 2]\n", + " [2 1]\n", + " [1 0]]\n" + ] + }, { "data": { - "text/plain": "array([2, 1, 2])" + "text/plain": "array([2, 2, 1, 2, 1, 0])" }, - "execution_count": 35, + "execution_count": 112, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "np.argmax(np.array([[1,2,3],[4,6,5],[7,8,9]]), axis=1)" + "utils = np.array([[1,2,3,4,5,6],[4,6,5,9,9,6],[7,8,9,1,2,3]]).reshape((3,3,2))\n", + "print(utils.shape)\n", + "print(np.argmax(utils, axis=1))\n", + "np.argmax(utils, axis=1).flatten(order=\"F\")" ], "metadata": { "collapsed": false, @@ -121,29 +134,52 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 124, "outputs": [ { "data": { - "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" + "text/plain": "array([[2, 2],\n [2, 1],\n [1, 0]])" }, + "execution_count": 124, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" + } + ], + "source": [ + "ch_array = np.argmax(utils, axis=1)\n", + "ch_array" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 191, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(3, 4)\n" + ] }, { "data": { - "text/plain": "0 c\n1 b\n2 c\ndtype: object" + "text/plain": "array([[0.1 , 0.7 , 0.1 , 0.1 ],\n [0. , 0.5 , 0.25, 0.25],\n [0.3 , 0.3 , 0.2 , 0.2 ]])" }, + "execution_count": 191, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", - "display(df_)\n", - "ch_ = df_.idxmax(1)\n", - "display(ch_)" + "probs = np.array([[0.1,0.7,0.1,0.1], [0.0,0.5,0.25,0.25], [0.3,0.3,0.2,0.2]])\n", + "print(probs.shape)\n", + "probs" ], "metadata": { "collapsed": false, @@ -154,20 +190,19 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 174, "outputs": [ { "data": { - "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" + "text/plain": "(3, 4, 3)" }, + "execution_count": 174, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", - "display(df_)" + "np.repeat(probs[:,:,None], 3, axis=2).shape" ], "metadata": { "collapsed": false, @@ -178,19 +213,19 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 198, "outputs": [ { "data": { - "text/plain": "{'a': 0, 'b': 1, 'c': 2}" + "text/plain": "array([0, 0, 1, 1, 2, 2])" }, - "execution_count": 64, + "execution_count": 198, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "{v: k for k,v in enumerate(df_.columns)}" + "np.repeat(np.arange(0,probs.shape[0]), 2)" ], "metadata": { "collapsed": false, @@ -201,9 +236,20 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 202, + "outputs": [ + { + "data": { + "text/plain": "array([0, 1, 2, 0, 1, 2, 0, 1, 2])" + }, + "execution_count": 202, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.tile(np.arange(0,probs.shape[0]), 3)" + ], "metadata": { "collapsed": false, "pycharm": { @@ -312,18 +358,22 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 218, "outputs": [], "source": [ + "from activitysim.core.logit import inverse_ev1_cdf\n", + "\n", "def hack_make_sample_choices(\n", - " choosers, probs,\n", - " alternatives,\n", - " sample_size, alternative_count, alt_col_name,\n", - " allow_zero_probs,\n", - " trace_label):\n", + " choosers, probs,\n", + " alternatives,\n", + " sample_size, alternative_count, alt_col_name,\n", + " allow_zero_probs,\n", + " trace_label,\n", + " utilities=None,\n", + " choose_individual_max_utility=False\n", + "):\n", " assert isinstance(probs, pd.DataFrame)\n", " assert probs.shape == (len(choosers), alternative_count)\n", - "\n", " assert isinstance(alternatives, pd.DataFrame)\n", " assert len(alternatives) == alternative_count\n", "\n", @@ -335,72 +385,112 @@ " # remove from sample\n", " probs = probs[~zero_probs]\n", " choosers = choosers[~zero_probs]\n", + " # TODO [janzill Jun2022]: probably need to remove these from the utilities as well for consistency?\n", + " # might need this in other places too?\n", + "\n", + " if choose_individual_max_utility:\n", + " assert isinstance(utilities, pd.DataFrame)\n", + " #print(utilities.head(3))\n", + " assert utilities.shape == (len(choosers), alternative_count)\n", + " #print(utilities.shape)\n", + "\n", + " choice_dimension = (len(choosers), alternative_count, sample_size)\n", + " rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count*sample_size)\n", + " #print(f\"after generation rands shape = {rands.shape}\", flush=True)\n", + " rands = rands.reshape(choice_dimension)\n", + " #print(f\"before inverse_ev1 rands shape = {rands.shape}\", flush=True)\n", + " rands = inverse_ev1_cdf(rands)\n", + " print(f\"after inverse_ev1 rands shape = {rands.shape}\", flush=True)\n", + " utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented\n", + " utilities = np.repeat(utilities[:,:,None], sample_size, axis=2)\n", + " print(f\"after utils reshape: {utilities.shape}\", flush=True)\n", + " utilities += rands\n", + " # this gives us len(choosers), sample_size dimensions, with values the chosen alternative\n", + " choices_array = np.argmax(utilities, axis=1)\n", + " print(choices_array.shape)\n", + "\n", + " choosers_index_rep = np.tile(np.arange(0,choices_array.shape[0]), sample_size)\n", + " #np.repeat(np.arange(0,choices_array.shape[0]), sample_size)\n", + " choices_flattened = choices_array.flatten(order='F')\n", + " print(f\"choices flattened shape = {choices_flattened.shape}\")\n", + "\n", + " print(choosers_index_rep.shape, flush=True)\n", + " print(probs.shape, flush=True)\n", + " print(probs.head(3), flush=True)\n", + "\n", + " print(f\"fake rand shape = {np.zeros_like(choosers_index_rep).shape}\", flush=True)\n", + " probs_look_up = probs.to_numpy()[choosers_index_rep, choices_flattened]\n", + "\n", + " print(f\"probs_look_up shape = {probs_look_up.shape}\", flush=True)\n", + "\n", + " # explode to one row per chooser.index, alt_zone_id\n", + " choices_df = pd.DataFrame({\n", + " alt_col_name: choices_flattened,\n", + " #'rand': rands.flatten(order='F'),\n", + " 'rand': np.zeros_like(choosers_index_rep), # TODO: zero out for now\n", + " 'prob': probs_look_up.flatten(order='F'),\n", + " # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction\n", + " #choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", + " choosers.index.name: np.tile(choosers.index.values, sample_size)\n", + " })\n", + "\n", + " else:\n", + " cum_probs_array = probs.values.cumsum(axis=1)\n", + " # alt probs in convenient layout to return prob of chose alternative\n", + " # (same layout as cum_probs_arr)\n", + " alt_probs_array = probs.values.flatten()\n", + " # get sample_size rands for each chooser\n", + " rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size)\n", + " # transform as we iterate over alternatives\n", + " # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr\n", + " # i.e rands[i] is a 2-D array of one alt choice rand for each chooser\n", + " rands = rands.T.reshape(sample_size, -1, 1)\n", + " # the alternative value chosen\n", + " choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype)\n", + " # chunk log these later after we populate them...\n", + " # the probability of the chosen alternative\n", + " choice_probs_array = np.empty([sample_size, len(choosers)])\n", + " # chunk log these later after we populate them...\n", + " alts = np.tile(alternatives.index.values, len(choosers))\n", + " # FIXME - do this all at once rather than iterate?\n", + " for i in range(sample_size):\n", + " # FIXME - do this in numpy, not pandas?\n", + " # rands for this alt in broadcastable shape\n", + " r = rands[i]\n", + "\n", + " # position of first occurrence of positive value\n", + " positions = np.argmax(cum_probs_array > r, axis=1)\n", + "\n", + " # FIXME - leave positions as numpy array, not pandas series?\n", + " # positions is series with the chosen alternative represented as a column index in probs\n", + " # which is an integer between zero and num alternatives in the alternative sample\n", + " positions = pd.Series(positions, index=probs.index)\n", + "\n", + " # need to get from an integer offset into the alternative sample to the alternative index\n", + " # that is, we want the index value of the row that is offset by rows into the\n", + " # tranche of this choosers alternatives created by cross join of alternatives and choosers\n", + "\n", + " # offsets is the offset into model_design df of first row of chooser alternatives\n", + " offsets = np.arange(len(positions)) * alternative_count\n", + "\n", + " # choices and choice_probs have one element per chooser and is in same order as choosers\n", + " choices_array[i] = np.take(alts, positions + offsets)\n", + " choice_probs_array[i] = np.take(alt_probs_array, positions + offsets)\n", + " del positions\n", + " del offsets\n", + "\n", + " del alts\n", + " del cum_probs_array\n", + " del alt_probs_array\n", + "\n", + " # explode to one row per chooser.index, alt_zone_id\n", + " choices_df = pd.DataFrame(\n", + " {alt_col_name: choices_array.flatten(order='F'),\n", + " 'rand': rands.flatten(order='F'),\n", + " 'prob': choice_probs_array.flatten(order='F'),\n", + " choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", + " })\n", "\n", - " cum_probs_array = probs.values.cumsum(axis=1)\n", - "\n", - " # alt probs in convenient layout to return prob of chose alternative\n", - " # (same layout as cum_probs_arr)\n", - " alt_probs_array = probs.values.flatten()\n", - "\n", - " # get sample_size rands for each chooser\n", - " rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size)\n", - "\n", - " # transform as we iterate over alternatives\n", - " # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr\n", - " # i.e rands[i] is a 2-D array of one alt choice rand for each chooser\n", - " rands = rands.T.reshape(sample_size, -1, 1)\n", - "\n", - " # the alternative value chosen\n", - " choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype)\n", - " # chunk log these later after we populate them...\n", - " # the probability of the chosen alternative\n", - " choice_probs_array = np.empty([sample_size, len(choosers)])\n", - " # chunk log these later after we populate them...\n", - " alts = np.tile(alternatives.index.values, len(choosers))\n", - "\n", - " # FIXME - do this all at once rather than iterate?\n", - " for i in range(sample_size):\n", - " # FIXME - do this in numpy, not pandas?\n", - " # rands for this alt in broadcastable shape\n", - " r = rands[i]\n", - "\n", - " # position of first occurrence of positive value\n", - " positions = np.argmax(cum_probs_array > r, axis=1)\n", - "\n", - " # FIXME - leave positions as numpy array, not pandas series?\n", - " # positions is series with the chosen alternative represented as a column index in probs\n", - " # which is an integer between zero and num alternatives in the alternative sample\n", - " positions = pd.Series(positions, index=probs.index)\n", - "\n", - " # need to get from an integer offset into the alternative sample to the alternative index\n", - " # that is, we want the index value of the row that is offset by rows into the\n", - " # tranche of this choosers alternatives created by cross join of alternatives and choosers\n", - "\n", - " # offsets is the offset into model_design df of first row of chooser alternatives\n", - " offsets = np.arange(len(positions)) * alternative_count\n", - "\n", - " # choices and choice_probs have one element per chooser and is in same order as choosers\n", - " choices_array[i] = np.take(alts, positions + offsets)\n", - " choice_probs_array[i] = np.take(alt_probs_array, positions + offsets)\n", - "\n", - " del positions\n", - " del offsets\n", - "\n", - " del alts\n", - " del cum_probs_array\n", - " del alt_probs_array\n", - "\n", - " # explode to one row per chooser.index, alt_zone_id\n", - " choices_df = pd.DataFrame(\n", - " {alt_col_name: choices_array.flatten(order='F'),\n", - " 'rand': rands.flatten(order='F'),\n", - " 'prob': choice_probs_array.flatten(order='F'),\n", - " choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", - " })\n", - "\n", - " del choices_array\n", - " del rands\n", - " del choice_probs_array\n", " return choices_df" ], "metadata": { @@ -412,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 219, "outputs": [ { "name": "stderr", @@ -428,19 +518,85 @@ "running university, 3\n", "dropping 19 of 25 rows where size_term is zero\n", "Running school_location with 17 persons\n", + "after inverse_ev1 rands shape = (17, 6, 6)\n", + "after utils reshape: (17, 6, 6)\n", + "(17, 6)\n", + "choices flattened shape = (102,)\n", + "(102,)\n", + "(17, 6)\n", + " 0 1 2 3 4 5\n", + "person_id \n", + "325623 0.001874 0.005184 0.002695 0.299427 0.508256 0.182563\n", + "386007 0.001584 0.724879 0.145533 0.080880 0.041571 0.005553\n", + "1774265 0.019349 0.146800 0.038209 0.486232 0.273819 0.035591\n", + "fake rand shape = (102,)\n", + "probs_look_up shape = (102,)\n", "running highschool, 2\n", "dropping 23 of 25 rows where size_term is zero\n", "Running school_location with 5 persons\n", + "after inverse_ev1 rands shape = (5, 2, 2)\n", + "after utils reshape: (5, 2, 2)\n", + "(5, 2)\n", + "choices flattened shape = (10,)\n", + "(10,)\n", + "(5, 2)\n", + " 0 1\n", + "person_id \n", + "386062 0.176063 0.823937\n", + "595685 0.057237 0.942763\n", + "2877285 0.126995 0.873005\n", + "fake rand shape = (10,)\n", + "probs_look_up shape = (10,)\n", "running gradeschool, 1\n", "dropping 0 of 25 rows where size_term is zero\n", "Running school_location with 17 persons\n", - "CPU times: user 188 ms, sys: 31.2 ms, total: 219 ms\n", - "Wall time: 216 ms\n" + "after inverse_ev1 rands shape = (17, 25, 10)\n", + "after utils reshape: (17, 25, 10)\n", + "(17, 10)\n", + "choices flattened shape = (170,)\n", + "(170,)\n", + "(17, 25)\n", + " 0 1 2 3 4 5 \\\n", + "person_id \n", + "386008 0.000504 0.001303 0.002645 0.001534 0.007081 0.023018 \n", + "418442 0.000554 0.001434 0.002911 0.001678 0.007704 0.020100 \n", + "595686 0.000877 0.002269 0.004605 0.003020 0.014986 0.025318 \n", + "\n", + " 6 7 8 9 10 11 \\\n", + "person_id \n", + "386008 0.056174 0.157982 0.336708 0.205884 0.069858 0.001223 \n", + "418442 0.042219 0.120700 0.229318 0.288773 0.101255 0.001526 \n", + "595686 0.058311 0.166706 0.116761 0.110817 0.133127 0.003827 \n", + "\n", + " 12 13 14 15 16 17 \\\n", + "person_id \n", + "386008 0.000206 0.000548 0.000380 0.015291 0.009346 0.008790 \n", + "418442 0.000257 0.000683 0.000474 0.019075 0.011592 0.010965 \n", + "595686 0.000525 0.001116 0.000750 0.053224 0.025697 0.033011 \n", + "\n", + " 18 19 20 21 22 23 \\\n", + "person_id \n", + "386008 0.008610 0.032788 0.028567 0.002079 0.000817 0.002728 \n", + "418442 0.012703 0.052444 0.039245 0.002288 0.000899 0.002985 \n", + "595686 0.014173 0.033481 0.147571 0.003620 0.001422 0.004723 \n", + "\n", + " 24 \n", + "person_id \n", + "386008 0.025936 \n", + "418442 0.028217 \n", + "595686 0.040062 \n", + "fake rand shape = (170,)\n", + "probs_look_up shape = (170,)\n", + "CPU times: user 312 ms, sys: 46.9 ms, total: 359 ms\n", + "Wall time: 358 ms\n" ] } ], "source": [ "%%time\n", + "\n", + "choose_individual_max_utility = True\n", + "\n", "resume_after = \"compute_accessibility\"\n", "model_name = \"school_location\"\n", "chunk_size = 0 # test_mtc means no chunking\n", @@ -464,72 +620,48 @@ "trace_label = model_name #'school_location'\n", "model_settings_file_name = f\"{model_name}.yaml\" #'school_location.yaml'\n", "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", "estimator = estimation.manager.begin_estimation(model_name)\n", - "\n", - "# iterate_location_choice(\n", - "# model_settings,\n", - "# persons_merged, persons, households,\n", - "# network_los,\n", - "# estimator,\n", - "# chunk_size, trace_hh_id, locutor, trace_label\n", - "# )\n", - "\n", + "# iterate_location_choice()\n", "chunk_tag = trace_label\n", "\n", "# boolean to filter out persons not needing location modeling (e.g. is_worker, is_student)\n", "chooser_filter_column = model_settings['CHOOSER_FILTER_COLUMN_NAME']\n", - "\n", "dest_choice_column_name = model_settings['DEST_CHOICE_COLUMN_NAME']\n", "logsum_column_name = model_settings.get('DEST_CHOICE_LOGSUM_COLUMN_NAME')\n", - "\n", "sample_table_name = model_settings.get('DEST_CHOICE_SAMPLE_TABLE_NAME')\n", "want_sample_table = config.setting('want_dest_choice_sample_tables') and sample_table_name is not None\n", - "\n", "persons_merged_df = persons_merged.to_frame()\n", - "\n", "persons_merged_df = persons_merged_df[persons_merged_df[chooser_filter_column]]\n", - "\n", "persons_merged_df.sort_index(inplace=True) # interaction_sample expects chooser index to be monotonic increasing\n", "\n", "# chooser segmentation allows different sets coefficients for e.g. different income_segments or tour_types\n", "chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME']\n", - "\n", "assert chooser_segment_column in persons_merged_df, f\"CHOOSER_SEGMENT_COLUMN '{chooser_segment_column}' not in \" \\\n", " f\"persons_merged table.\"\n", - "\n", "shadow_price_calculator = shadow_pricing.load_shadow_price_calculator(model_settings)\n", - "\n", "chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME']\n", - "\n", "# maps segment names to compact (integer) ids\n", "segment_ids = model_settings['SEGMENT_IDS']\n", "\n", - "#choices_list = []\n", "sample_list = []\n", "for segment_name, segment_id in segment_ids.items():\n", " print(f\"running {segment_name}, {segment_id}\")\n", " choosers = persons_merged_df[persons_merged_df[chooser_segment_column] == segment_id]\n", - "\n", " # size_term and shadow price adjustment - one row per zone\n", " dest_size_terms = shadow_price_calculator.dest_size_terms(segment_name)\n", - "\n", " assert dest_size_terms.index.is_monotonic_increasing, f\"shadow_price_calculator.dest_size_terms({segment_name}) \" \\\n", " f\"not monotonic_increasing\"\n", " if choosers.shape[0] == 0:\n", " print(f\"{trace_label} skipping segment {segment_name}: no choosers\")\n", " continue\n", - "\n", " print(f\"dropping {(~(dest_size_terms.size_term > 0)).sum()} \"\n", " f\"of {len(dest_size_terms)} rows where size_term is zero\")\n", " dest_size_terms = dest_size_terms[dest_size_terms.size_term > 0]\n", - "\n", " chooser_columns = model_settings['SIMULATE_CHOOSER_COLUMNS']\n", " choosers_location_sample = choosers[chooser_columns]\n", " skim_dict = network_los.get_default_skim_dict()\n", " skims = skim_dict.wrap('home_zone_id', 'zone_id')\n", " alt_dest_col_name = model_settings['ALT_DEST_COL_NAME']\n", - "\n", " assert not choosers_location_sample.empty\n", " print(\"Running %s with %d persons\" % (trace_label, len(choosers_location_sample.index)))\n", " sample_size = model_settings[\"SAMPLE_SIZE\"]\n", @@ -541,11 +673,10 @@ " locals_d.update(constants)\n", " spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC',\n", " segment_name=segment_name, estimator=estimator)\n", - " ### choices = interaction_sample(\n", + " ### choices = interaction_sample()\n", " alt_col_name=alt_dest_col_name\n", " allow_zero_probs=False\n", " log_alt_losers=False\n", - "\n", " # we return alternatives ordered in (index, alt_col_name)\n", " # if choosers index is not ordered, it is probably a mistake, since the alts wont line up\n", " assert alt_col_name is not None\n", @@ -620,7 +751,10 @@ " chooser_chunk, probs, dest_size_terms,\n", " sample_size, alternative_count, alt_col_name,\n", " allow_zero_probs=allow_zero_probs,\n", - " trace_label=chunk_trace_label)\n", + " trace_label=chunk_trace_label,\n", + " utilities=utilities,\n", + " choose_individual_max_utility=choose_individual_max_utility\n", + " )\n", "\n", " choices_df = temp_choices.copy()\n", "\n", @@ -679,20 +813,58 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 221, + "outputs": [ + { + "data": { + "text/plain": " alt_dest rand prob person_id\n0 8 0 0.336708 386008\n51 6 0 0.056174 386008\n85 8 0 0.336708 386008\n102 8 0 0.336708 386008\n34 7 0 0.157982 386008\n.. ... ... ... ...\n50 15 0 0.182639 4171620\n33 5 0 0.032124 4171620\n16 8 0 0.111409 4171620\n152 9 0 0.097048 4171620\n169 6 0 0.062739 4171620\n\n[170 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
0800.336708386008
51600.056174386008
85800.336708386008
102800.336708386008
34700.157982386008
...............
501500.1826394171620
33500.0321244171620
16800.1114094171620
152900.0970484171620
169600.0627394171620
\n

170 rows × 4 columns

\n
" + }, + "execution_count": 221, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "temp_choices.sort_values(by=[\"person_id\"])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 216, "outputs": [ { "data": { "text/plain": " alt_dest rand prob person_id\n0 8 0.222922 0.157982 386008\n1 9 0.404176 0.336708 386008\n2 8 0.223154 0.157982 386008\n3 9 0.457402 0.336708 386008\n4 11 0.812506 0.069858 386008\n.. ... ... ... ...\n165 7 0.107363 0.062739 4171620\n166 11 0.517573 0.070848 4171620\n167 10 0.375828 0.097048 4171620\n168 9 0.279810 0.111409 4171620\n169 20 0.848968 0.026642 4171620\n\n[170 rows x 4 columns]", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
080.2229220.157982386008
190.4041760.336708386008
280.2231540.157982386008
390.4574020.336708386008
4110.8125060.069858386008
...............
16570.1073630.0627394171620
166110.5175730.0708484171620
167100.3758280.0970484171620
16890.2798100.1114094171620
169200.8489680.0266424171620
\n

170 rows × 4 columns

\n
" }, - "execution_count": 98, + "execution_count": 216, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "temp_choices" + "#temp_choices" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "choices_df" ], "metadata": { "collapsed": false, @@ -703,20 +875,20 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 217, "outputs": [ { "data": { "text/plain": " alt_dest prob pick_count\nperson_id \n386008 8 0.157982 2\n386008 9 0.336708 5\n386008 10 0.205884 1\n386008 11 0.069858 1\n386008 17 0.009346 1\n... ... ... ...\n4171620 9 0.111409 1\n4171620 10 0.097048 1\n4171620 11 0.070848 1\n4171620 20 0.026642 1\n4171620 25 0.062053 1\n\n[105 rows x 3 columns]", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destprobpick_count
person_id
38600880.1579822
38600890.3367085
386008100.2058841
386008110.0698581
386008170.0093461
............
417162090.1114091
4171620100.0970481
4171620110.0708481
4171620200.0266421
4171620250.0620531
\n

105 rows × 3 columns

\n
" }, - "execution_count": 97, + "execution_count": 217, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "sample_list[-1]" + "#choices_df" ], "metadata": { "collapsed": false, @@ -751,28 +923,21 @@ }, { "cell_type": "code", - "execution_count": 91, - "outputs": [ - { - "data": { - "text/plain": "10" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": "person_id\n386008 10\n418442 10\n595686 10\n644292 10\n644478 10\n1958678 10\n2159059 10\n2219998 10\n2458502 10\n2458503 10\n2566700 10\n2566701 10\n2566702 10\n2877287 10\n3596365 10\n3891104 10\n4171620 10\nName: pick_count, dtype: uint32" - }, - "execution_count": 91, - "metadata": {}, - "output_type": "execute_result" + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" } - ], - "source": [ - "display(sample_size)\n", - "sample_list[2].groupby('person_id').pick_count.sum()" - ], + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], "metadata": { "collapsed": false, "pycharm": { @@ -782,23 +947,20 @@ }, { "cell_type": "code", - "execution_count": 71, - "outputs": [ - { - "ename": "AssertionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mAssertionError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m/tmp/ipykernel_10980/63825533.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 43\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 44\u001B[0m \u001B[0;31m# interaction_utilities is a df with one utility column and one row per interaction_df row\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 45\u001B[0;31m interaction_utilities, trace_eval_results = interaction_simulate.eval_interaction_utilities(\n\u001B[0m\u001B[1;32m 46\u001B[0m \u001B[0mspec\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0minteraction_df\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mlocals_d\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrace_label\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mtrace_rows\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mestimator\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mNone\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 47\u001B[0m \u001B[0mlog_alt_losers\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mlog_alt_losers\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/interaction_simulate.py\u001B[0m in \u001B[0;36meval_interaction_utilities\u001B[0;34m(spec, df, locals_d, trace_label, trace_rows, estimator, log_alt_losers)\u001B[0m\n\u001B[1;32m 66\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"Running eval_interaction_utilities on %s rows\"\u001B[0m \u001B[0;34m%\u001B[0m \u001B[0mdf\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mshape\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m0\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 67\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 68\u001B[0;31m \u001B[0;32mwith\u001B[0m \u001B[0mchunk\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchunk_log\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtrace_label\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 69\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 70\u001B[0m \u001B[0;32massert\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mspec\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mcolumns\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m1\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m~/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py\u001B[0m in \u001B[0;36m__enter__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 111\u001B[0m \u001B[0;32mdel\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mkwds\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfunc\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 112\u001B[0m \u001B[0;32mtry\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 113\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mnext\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgen\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 114\u001B[0m \u001B[0;32mexcept\u001B[0m \u001B[0mStopIteration\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 115\u001B[0m \u001B[0;32mraise\u001B[0m \u001B[0mRuntimeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"generator didn't yield\"\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mfrom\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/chunk.py\u001B[0m in \u001B[0;36mchunk_log\u001B[0;34m(trace_label, chunk_tag, base)\u001B[0m\n\u001B[1;32m 927\u001B[0m \u001B[0;31m# avoids breaking the assertion below.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 928\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 929\u001B[0;31m \u001B[0;32massert\u001B[0m \u001B[0mbase\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;34m(\u001B[0m\u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mCHUNK_SIZERS\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 930\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 931\u001B[0m \u001B[0mtrace_label\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;34mf\"{trace_label}.chunk_log\"\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;31mAssertionError\u001B[0m: " - ] + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" } - ], + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], "source": [], "metadata": { "collapsed": false, @@ -807,6 +969,21 @@ } } }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "display(sample_size)\n", + "sample_list[2].groupby('person_id').pick_count.sum()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, From 64c4e6e2c31a3731734487a6b79eba5e114be6e8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 17 Jun 2022 08:57:18 +1000 Subject: [PATCH 044/298] fixes alternative index as chosen value --- .../frozen_impl_interaction_sample_dev.ipynb | 91 +++++++------------ 1 file changed, 32 insertions(+), 59 deletions(-) diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index ad34deebeb..17c559e364 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -358,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 218, + "execution_count": 234, "outputs": [], "source": [ "from activitysim.core.logit import inverse_ev1_cdf\n", @@ -385,8 +385,10 @@ " # remove from sample\n", " probs = probs[~zero_probs]\n", " choosers = choosers[~zero_probs]\n", - " # TODO [janzill Jun2022]: probably need to remove these from the utilities as well for consistency?\n", - " # might need this in other places too?\n", + " # TODO [janzill Jun2022]: do we want this for consistency?\n", + " # might need this in other places too?\n", + " if utilities is not None:\n", + " utilities = utilities[~zero_probs]\n", "\n", " if choose_individual_max_utility:\n", " assert isinstance(utilities, pd.DataFrame)\n", @@ -400,10 +402,10 @@ " rands = rands.reshape(choice_dimension)\n", " #print(f\"before inverse_ev1 rands shape = {rands.shape}\", flush=True)\n", " rands = inverse_ev1_cdf(rands)\n", - " print(f\"after inverse_ev1 rands shape = {rands.shape}\", flush=True)\n", + " #print(f\"after inverse_ev1 rands shape = {rands.shape}\", flush=True)\n", " utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented\n", " utilities = np.repeat(utilities[:,:,None], sample_size, axis=2)\n", - " print(f\"after utils reshape: {utilities.shape}\", flush=True)\n", + " #print(f\"after utils reshape: {utilities.shape}\", flush=True)\n", " utilities += rands\n", " # this gives us len(choosers), sample_size dimensions, with values the chosen alternative\n", " choices_array = np.argmax(utilities, axis=1)\n", @@ -412,20 +414,19 @@ " choosers_index_rep = np.tile(np.arange(0,choices_array.shape[0]), sample_size)\n", " #np.repeat(np.arange(0,choices_array.shape[0]), sample_size)\n", " choices_flattened = choices_array.flatten(order='F')\n", - " print(f\"choices flattened shape = {choices_flattened.shape}\")\n", + " #print(f\"choices flattened shape = {choices_flattened.shape}\")\n", "\n", " print(choosers_index_rep.shape, flush=True)\n", " print(probs.shape, flush=True)\n", " print(probs.head(3), flush=True)\n", - "\n", - " print(f\"fake rand shape = {np.zeros_like(choosers_index_rep).shape}\", flush=True)\n", " probs_look_up = probs.to_numpy()[choosers_index_rep, choices_flattened]\n", + " #print(f\"probs_look_up shape = {probs_look_up.shape}\", flush=True)\n", "\n", - " print(f\"probs_look_up shape = {probs_look_up.shape}\", flush=True)\n", - "\n", + " # choices_flattened are 0-based index into alternatives, need to map to alternative values given by\n", + " # alternatives.index.values (they are in this order by construction)\n", " # explode to one row per chooser.index, alt_zone_id\n", " choices_df = pd.DataFrame({\n", - " alt_col_name: choices_flattened,\n", + " alt_col_name: alternatives.index.values[choices_flattened],\n", " #'rand': rands.flatten(order='F'),\n", " 'rand': np.zeros_like(choosers_index_rep), # TODO: zero out for now\n", " 'prob': probs_look_up.flatten(order='F'),\n", @@ -502,7 +503,7 @@ }, { "cell_type": "code", - "execution_count": 219, + "execution_count": 237, "outputs": [ { "name": "stderr", @@ -518,10 +519,7 @@ "running university, 3\n", "dropping 19 of 25 rows where size_term is zero\n", "Running school_location with 17 persons\n", - "after inverse_ev1 rands shape = (17, 6, 6)\n", - "after utils reshape: (17, 6, 6)\n", "(17, 6)\n", - "choices flattened shape = (102,)\n", "(102,)\n", "(17, 6)\n", " 0 1 2 3 4 5\n", @@ -529,15 +527,10 @@ "325623 0.001874 0.005184 0.002695 0.299427 0.508256 0.182563\n", "386007 0.001584 0.724879 0.145533 0.080880 0.041571 0.005553\n", "1774265 0.019349 0.146800 0.038209 0.486232 0.273819 0.035591\n", - "fake rand shape = (102,)\n", - "probs_look_up shape = (102,)\n", "running highschool, 2\n", "dropping 23 of 25 rows where size_term is zero\n", "Running school_location with 5 persons\n", - "after inverse_ev1 rands shape = (5, 2, 2)\n", - "after utils reshape: (5, 2, 2)\n", "(5, 2)\n", - "choices flattened shape = (10,)\n", "(10,)\n", "(5, 2)\n", " 0 1\n", @@ -545,15 +538,10 @@ "386062 0.176063 0.823937\n", "595685 0.057237 0.942763\n", "2877285 0.126995 0.873005\n", - "fake rand shape = (10,)\n", - "probs_look_up shape = (10,)\n", "running gradeschool, 1\n", "dropping 0 of 25 rows where size_term is zero\n", "Running school_location with 17 persons\n", - "after inverse_ev1 rands shape = (17, 25, 10)\n", - "after utils reshape: (17, 25, 10)\n", "(17, 10)\n", - "choices flattened shape = (170,)\n", "(170,)\n", "(17, 25)\n", " 0 1 2 3 4 5 \\\n", @@ -585,10 +573,8 @@ "386008 0.025936 \n", "418442 0.028217 \n", "595686 0.040062 \n", - "fake rand shape = (170,)\n", - "probs_look_up shape = (170,)\n", - "CPU times: user 312 ms, sys: 46.9 ms, total: 359 ms\n", - "Wall time: 358 ms\n" + "CPU times: user 281 ms, sys: 31.2 ms, total: 312 ms\n", + "Wall time: 334 ms\n" ] } ], @@ -813,14 +799,14 @@ }, { "cell_type": "code", - "execution_count": 221, + "execution_count": 238, "outputs": [ { "data": { - "text/plain": " alt_dest rand prob person_id\n0 8 0 0.336708 386008\n51 6 0 0.056174 386008\n85 8 0 0.336708 386008\n102 8 0 0.336708 386008\n34 7 0 0.157982 386008\n.. ... ... ... ...\n50 15 0 0.182639 4171620\n33 5 0 0.032124 4171620\n16 8 0 0.111409 4171620\n152 9 0 0.097048 4171620\n169 6 0 0.062739 4171620\n\n[170 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
0800.336708386008
51600.056174386008
85800.336708386008
102800.336708386008
34700.157982386008
...............
501500.1826394171620
33500.0321244171620
16800.1114094171620
152900.0970484171620
169600.0627394171620
\n

170 rows × 4 columns

\n
" + "text/plain": " alt_dest rand prob person_id\n0 9 0 0.336708 386008\n51 7 0 0.056174 386008\n85 9 0 0.336708 386008\n102 9 0 0.336708 386008\n34 8 0 0.157982 386008\n.. ... ... ... ...\n50 16 0 0.182639 4171620\n33 6 0 0.032124 4171620\n16 9 0 0.111409 4171620\n152 10 0 0.097048 4171620\n169 7 0 0.062739 4171620\n\n[170 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
0900.336708386008
51700.056174386008
85900.336708386008
102900.336708386008
34800.157982386008
...............
501600.1826394171620
33600.0321244171620
16900.1114094171620
1521000.0970484171620
169700.0627394171620
\n

170 rows × 4 columns

\n
" }, - "execution_count": 221, + "execution_count": 238, "metadata": {}, "output_type": "execute_result" } @@ -899,33 +885,20 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" + "execution_count": 231, + "outputs": [ + { + "data": { + "text/plain": "array([1, 2, 3, 4, 5])" + }, + "execution_count": 231, + "metadata": {}, + "output_type": "execute_result" } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + ], + "source": [ + "dest_size_terms.index.values[[0,1,2,3,4]]" + ], "metadata": { "collapsed": false, "pycharm": { From e3e4326bdc8c86e426bb8e67a01a3b656e375f68 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 17 Jun 2022 09:14:45 +1000 Subject: [PATCH 045/298] interaction sample frozen rand indiv util --- activitysim/core/interaction_sample.py | 200 +++++++++++++++---------- 1 file changed, 124 insertions(+), 76 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index d792401dc2..4278b962c7 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -12,6 +12,7 @@ from . import tracing from . import chunk from .simulate import set_skim_wrapper_targets +from .logit import inverse_ev1_cdf from . import interaction_simulate @@ -27,7 +28,9 @@ def make_sample_choices( alternatives, sample_size, alternative_count, alt_col_name, allow_zero_probs, - trace_label): + trace_label, + utilities=None, + choose_individual_max_utility=False): """ Parameters @@ -62,96 +65,134 @@ def make_sample_choices( # remove from sample probs = probs[~zero_probs] choosers = choosers[~zero_probs] + # TODO [janzill Jun2022]: do we want this for consistency? + # might need this in other places too? + if utilities is not None: + utilities = utilities[~zero_probs] + + if choose_individual_max_utility: + assert isinstance(utilities, pd.DataFrame) + assert utilities.shape == (len(choosers), alternative_count) + choice_dimension = (len(choosers), alternative_count, sample_size) + + rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count*sample_size) + rands = rands.reshape(choice_dimension) + rands = inverse_ev1_cdf(rands) + chunk.log_df(trace_label, 'rands', rands) + + utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented + utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) + utilities += rands + + del rands + chunk.log_df(trace_label, 'rands', None) + + # this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative + choices_array = np.argmax(utilities, axis=1) + + choosers_index_rep = np.tile(np.arange(0, choices_array.shape[0]), sample_size) + choices_flattened = choices_array.flatten(order='F') + + # choices_flattened are 0-based index into alternatives, need to map to alternative values given by + # alternatives.index.values (they are in this order by construction) + # explode to one row per chooser.index, alt_zone_id + choices_df = pd.DataFrame({ + alt_col_name: alternatives.index.values[choices_flattened], + 'rand': np.zeros_like(choosers_index_rep), # TODO [janzill June2022]: zero out for now + 'prob': probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F'), + # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction + choosers.index.name: np.tile(choosers.index.values, sample_size) + }) + else: + cum_probs_array = probs.values.cumsum(axis=1) + chunk.log_df(trace_label, 'cum_probs_array', cum_probs_array) - cum_probs_array = probs.values.cumsum(axis=1) - chunk.log_df(trace_label, 'cum_probs_array', cum_probs_array) - - # alt probs in convenient layout to return prob of chose alternative - # (same layout as cum_probs_arr) - alt_probs_array = probs.values.flatten() - chunk.log_df(trace_label, 'alt_probs_array', alt_probs_array) + # alt probs in convenient layout to return prob of chose alternative + # (same layout as cum_probs_arr) + alt_probs_array = probs.values.flatten() + chunk.log_df(trace_label, 'alt_probs_array', alt_probs_array) - # get sample_size rands for each chooser - rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size) + # get sample_size rands for each chooser + rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size) - # transform as we iterate over alternatives - # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr - # i.e rands[i] is a 2-D array of one alt choice rand for each chooser - rands = rands.T.reshape(sample_size, -1, 1) - chunk.log_df(trace_label, 'rands', rands) + # transform as we iterate over alternatives + # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr + # i.e rands[i] is a 2-D array of one alt choice rand for each chooser + rands = rands.T.reshape(sample_size, -1, 1) + chunk.log_df(trace_label, 'rands', rands) - # the alternative value chosen - # WHY SHOULD CHOICES COL HAVE TO BE TYPE INT??? - # choices_array = np.empty([sample_size, len(choosers)]).astype(int) - choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype) - # chunk log these later after we populate them... + # the alternative value chosen + # WHY SHOULD CHOICES COL HAVE TO BE TYPE INT??? + # choices_array = np.empty([sample_size, len(choosers)]).astype(int) + choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype) + # chunk log these later after we populate them... - # the probability of the chosen alternative - choice_probs_array = np.empty([sample_size, len(choosers)]) - # chunk log these later after we populate them... + # the probability of the chosen alternative + choice_probs_array = np.empty([sample_size, len(choosers)]) + # chunk log these later after we populate them... - alts = np.tile(alternatives.index.values, len(choosers)) - chunk.log_df(trace_label, 'alts', alts) + alts = np.tile(alternatives.index.values, len(choosers)) + chunk.log_df(trace_label, 'alts', alts) - # FIXME - do this all at once rather than iterate? - for i in range(sample_size): + # FIXME - do this all at once rather than iterate? + for i in range(sample_size): - # FIXME - do this in numpy, not pandas? + # FIXME - do this in numpy, not pandas? - # rands for this alt in broadcastable shape - r = rands[i] + # rands for this alt in broadcastable shape + r = rands[i] - # position of first occurrence of positive value - positions = np.argmax(cum_probs_array > r, axis=1) + # position of first occurrence of positive value + positions = np.argmax(cum_probs_array > r, axis=1) - # FIXME - leave positions as numpy array, not pandas series? - # positions is series with the chosen alternative represented as a column index in probs - # which is an integer between zero and num alternatives in the alternative sample - positions = pd.Series(positions, index=probs.index) + # FIXME - leave positions as numpy array, not pandas series? + # positions is series with the chosen alternative represented as a column index in probs + # which is an integer between zero and num alternatives in the alternative sample + positions = pd.Series(positions, index=probs.index) - # need to get from an integer offset into the alternative sample to the alternative index - # that is, we want the index value of the row that is offset by rows into the - # tranche of this choosers alternatives created by cross join of alternatives and choosers + # need to get from an integer offset into the alternative sample to the alternative index + # that is, we want the index value of the row that is offset by rows into the + # tranche of this choosers alternatives created by cross join of alternatives and choosers - # offsets is the offset into model_design df of first row of chooser alternatives - offsets = np.arange(len(positions)) * alternative_count + # offsets is the offset into model_design df of first row of chooser alternatives + offsets = np.arange(len(positions)) * alternative_count - # choices and choice_probs have one element per chooser and is in same order as choosers - choices_array[i] = np.take(alts, positions + offsets) - choice_probs_array[i] = np.take(alt_probs_array, positions + offsets) + # choices and choice_probs have one element per chooser and is in same order as choosers + choices_array[i] = np.take(alts, positions + offsets) + choice_probs_array[i] = np.take(alt_probs_array, positions + offsets) - del positions - del offsets + del positions + del offsets - chunk.log_df(trace_label, 'choices_array', choices_array) - chunk.log_df(trace_label, 'choice_probs_array', choice_probs_array) + chunk.log_df(trace_label, 'choices_array', choices_array) + chunk.log_df(trace_label, 'choice_probs_array', choice_probs_array) - del alts - chunk.log_df(trace_label, 'alts', None) - del cum_probs_array - chunk.log_df(trace_label, 'cum_probs_array', None) - del alt_probs_array - chunk.log_df(trace_label, 'alt_probs_array', None) + del alts + chunk.log_df(trace_label, 'alts', None) + del cum_probs_array + chunk.log_df(trace_label, 'cum_probs_array', None) + del alt_probs_array + chunk.log_df(trace_label, 'alt_probs_array', None) - # explode to one row per chooser.index, alt_zone_id - choices_df = pd.DataFrame( - {alt_col_name: choices_array.flatten(order='F'), - 'rand': rands.flatten(order='F'), - 'prob': choice_probs_array.flatten(order='F'), - choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size) - }) + # explode to one row per chooser.index, alt_zone_id + choices_df = pd.DataFrame( + {alt_col_name: choices_array.flatten(order='F'), + 'rand': rands.flatten(order='F'), + 'prob': choice_probs_array.flatten(order='F'), + choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size) + }) - chunk.log_df(trace_label, 'choices_df', choices_df) + chunk.log_df(trace_label, 'choices_df', choices_df) - del choices_array - chunk.log_df(trace_label, 'choices_array', None) - del rands - chunk.log_df(trace_label, 'rands', None) - del choice_probs_array - chunk.log_df(trace_label, 'choice_probs_array', None) + del choices_array + chunk.log_df(trace_label, 'choices_array', None) + del rands + chunk.log_df(trace_label, 'rands', None) + del choice_probs_array + chunk.log_df(trace_label, 'choice_probs_array', None) - # handing this off to caller - chunk.log_df(trace_label, 'choices_df', None) + # handing this off to caller + chunk.log_df(trace_label, 'choices_df', None) return choices_df @@ -163,7 +204,8 @@ def _interaction_sample( log_alt_losers=False, skims=None, locals_d=None, - trace_label=None): + trace_label=None, + choose_individual_max_utility=False): """ Run a MNL simulation in the situation in which alternatives must be merged with choosers because there are interaction terms or @@ -314,8 +356,8 @@ def _interaction_sample( trace_label=trace_label, trace_choosers=choosers) chunk.log_df(trace_label, 'probs', probs) - del utilities - chunk.log_df(trace_label, 'utilities', None) + #del utilities + #chunk.log_df(trace_label, 'utilities', None) if have_trace_targets: tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), @@ -342,10 +384,15 @@ def _interaction_sample( choosers, probs, alternatives, sample_size, alternative_count, alt_col_name, allow_zero_probs=allow_zero_probs, - trace_label=trace_label) + trace_label=trace_label, + utilities=utilities, + choose_individual_max_utility=choose_individual_max_utility) chunk.log_df(trace_label, 'choices_df', choices_df) + del utilities + chunk.log_df(trace_label, 'utilities', None) + del probs chunk.log_df(trace_label, 'probs', None) @@ -395,7 +442,7 @@ def interaction_sample( allow_zero_probs=False, log_alt_losers=False, skims=None, locals_d=None, chunk_size=0, chunk_tag=None, - trace_label=None): + trace_label=None, choose_individual_max_utility=False): """ Run a simulation in the situation in which alternatives must @@ -476,7 +523,8 @@ def interaction_sample( log_alt_losers=log_alt_losers, skims=skims, locals_d=locals_d, - trace_label=chunk_trace_label) + trace_label=chunk_trace_label, + choose_individual_max_utility=choose_individual_max_utility) if choices.shape[0] > 0: # might not be any if allow_zero_probs From 00208e7bd3bccb20cec71c9df9cd7f1bb91f3a9b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 17 Jun 2022 10:24:49 +1000 Subject: [PATCH 046/298] adds interaction_sample at the module level, not individual model --- activitysim/abm/models/location_choice.py | 44 ++++++++++++------- activitysim/abm/models/trip_destination.py | 44 ++++++++++++------- .../abm/models/util/tour_destination.py | 38 +++++++++++----- 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 4968a8dcce..7be64b848d 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -109,7 +109,8 @@ def _location_sample( model_settings, alt_dest_col_name, chunk_size, chunk_tag, - trace_label): + trace_label, + choose_individual_max_utility): """ select a sample of alternative locations. @@ -161,7 +162,9 @@ def _location_sample( locals_d=locals_d, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility + ) return choices @@ -174,7 +177,8 @@ def location_sample( estimator, model_settings, chunk_size, chunk_tag, - trace_label): + trace_label, + choose_individual_max_utility): # FIXME - MEMORY HACK - only include columns actually used in spec chooser_columns = model_settings['SIMULATE_CHOOSER_COLUMNS'] @@ -198,7 +202,8 @@ def location_sample( model_settings, alt_dest_col_name, chunk_size, chunk_tag, - trace_label) + trace_label, + choose_individual_max_utility) return choices @@ -264,7 +269,8 @@ def location_presample( estimator, model_settings, chunk_size, chunk_tag, - trace_label): + trace_label, + choose_individual_max_utility): trace_label = tracing.extend_trace_label(trace_label, 'presample') @@ -302,7 +308,8 @@ def location_presample( model_settings, DEST_TAZ, chunk_size, chunk_tag, - trace_label) + trace_label, + choose_individual_max_utility) # print(f"taz_sample\n{taz_sample}") # dest_TAZ prob pick_count @@ -329,7 +336,8 @@ def run_location_sample( estimator, model_settings, chunk_size, chunk_tag, - trace_label): + trace_label, + choose_individual_max_utility): """ select a sample of alternative locations. @@ -372,7 +380,8 @@ def run_location_sample( model_settings, chunk_size, chunk_tag=f'{chunk_tag}.presample', - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) else: @@ -385,7 +394,8 @@ def run_location_sample( model_settings, chunk_size, chunk_tag=f'{chunk_tag}.sample', - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) return choices @@ -463,7 +473,7 @@ def run_location_simulate( estimator, model_settings, chunk_size, chunk_tag, - trace_label): + trace_label, choose_individual_max_utility): """ run location model on location_sample annotated with mode_choice logsum to select a dest zone from sample alternatives @@ -529,7 +539,7 @@ def run_location_simulate( trace_label=trace_label, trace_choice_name=model_settings['DEST_CHOICE_COLUMN_NAME'], estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + choose_individual_max_utility=choose_individual_max_utility ) if not want_logsums: @@ -551,7 +561,8 @@ def run_location_choice( estimator, model_settings, chunk_size, chunk_tag, - trace_hh_id, trace_label + trace_hh_id, trace_label, + choose_individual_max_utility, ): """ Run the three-part location choice algorithm to generate a location choice for each chooser @@ -614,7 +625,8 @@ def run_location_choice( model_settings, chunk_size, chunk_tag, # run_location_sample will add appropriate suffix for sample or presample - trace_label=tracing.extend_trace_label(trace_label, 'sample.%s' % segment_name)) + trace_label=tracing.extend_trace_label(trace_label, 'sample.%s' % segment_name), + choose_individual_max_utility=choose_individual_max_utility) # - location_logsums location_sample_df = \ @@ -639,7 +651,8 @@ def run_location_choice( estimator, model_settings, chunk_size, chunk_tag=f'{chunk_tag}.simulate', - trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name)) + trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name), + choose_individual_max_utility=choose_individual_max_utility) if estimator: if trace_hh_id: @@ -783,7 +796,8 @@ def iterate_location_choice( model_settings=model_settings, chunk_size=chunk_size, chunk_tag=chunk_tag, trace_hh_id=trace_hh_id, - trace_label=tracing.extend_trace_label(trace_label, 'i%s' % iteration)) + trace_label=tracing.extend_trace_label(trace_label, 'i%s' % iteration), + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) # choices_df is a pandas DataFrame with columns 'choice' and (optionally) 'logsum' if choices_df is None: diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index a5724c3540..31a99fa67b 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -58,7 +58,8 @@ def _destination_sample( estimator, chunk_size, chunk_tag, - trace_label): + trace_label, + choose_individual_max_utility): """ Note: trips with no viable destination receive no sample rows @@ -109,8 +110,9 @@ def _destination_sample( skims=skims, locals_d=locals_dict, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label - ) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility + ) return choices @@ -124,7 +126,8 @@ def destination_sample( skim_hotel, estimator, chunk_size, - trace_label): + trace_label, + choose_individual_max_utility): chunk_tag = 'trip_destination.sample' @@ -142,7 +145,9 @@ def destination_sample( estimator, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility + ) return choices @@ -362,7 +367,8 @@ def destination_presample( network_los, estimator, chunk_size, trace_hh_id, - trace_label): + trace_label, + choose_individual_max_utility): trace_label = tracing.extend_trace_label(trace_label, 'presample') chunk_tag = 'trip_destination.presample' # distinguish from trip_destination.sample @@ -400,7 +406,8 @@ def destination_presample( estimator, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total maz_sample = choose_MAZ_for_TAZ(taz_sample, size_term_matrix, trips, network_los, alt_dest_col_name, trace_label) @@ -419,7 +426,8 @@ def trip_destination_sample( skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label): + trace_label, + choose_individual_max_utility): """ Returns @@ -463,7 +471,8 @@ def trip_destination_sample( network_los, estimator, chunk_size, trace_hh_id, - trace_label) + trace_label, + choose_individual_max_utility) else: choices = destination_sample( @@ -475,7 +484,8 @@ def trip_destination_sample( skim_hotel, estimator, chunk_size, - trace_label) + trace_label, + choose_individual_max_utility) return choices @@ -654,7 +664,8 @@ def trip_destination_simulate( skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label): + trace_label, + choose_individual_max_utility): """ Chose destination from destination_sample (with od_logsum and dp_logsum columns added) @@ -700,7 +711,7 @@ def trip_destination_simulate( trace_label=trace_label, trace_choice_name='trip_dest', estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + choose_individual_max_utility=choose_individual_max_utility ) if not want_logsums: @@ -750,7 +761,8 @@ def choose_trip_destination( skim_hotel=skim_hotel, estimator=estimator, chunk_size=chunk_size, trace_hh_id=trace_hh_id, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) dropped_trips = ~trips.index.isin(destination_sample.index.unique()) if dropped_trips.any(): @@ -788,7 +800,8 @@ def choose_trip_destination( skim_hotel=skim_hotel, estimator=estimator, chunk_size=chunk_size, trace_hh_id=trace_hh_id, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) dropped_trips = ~trips.index.isin(destinations.index) if dropped_trips.any(): @@ -1041,7 +1054,8 @@ def run_trip_destination( size_term_matrix, skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label=tracing.extend_trace_label(nth_trace_label, primary_purpose)) + trace_label=tracing.extend_trace_label(nth_trace_label, primary_purpose), + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) choices_list.append(choices) if want_sample_table: diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index bafa6c5eed..5d184a9cb6 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -77,7 +77,8 @@ def _destination_sample( alt_dest_col_name, chunk_size, chunk_tag, - trace_label): + trace_label, + choose_individual_max_utility): model_spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC', segment_name=spec_segment_name, estimator=estimator) @@ -110,7 +111,8 @@ def _destination_sample( locals_d=locals_d, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) # remember person_id in chosen alts so we can merge with persons in subsequent steps # (broadcasts person_id onto all alternatives sharing the same tour_id index value) @@ -126,7 +128,8 @@ def destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label): + chunk_size, trace_label, + choose_individual_max_utility): chunk_tag = 'tour_destination.sample' @@ -153,7 +156,8 @@ def destination_sample( model_settings, alt_dest_col_name, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) return choices @@ -393,7 +397,8 @@ def destination_presample( network_los, destination_size_terms, estimator, - chunk_size, trace_label): + chunk_size, trace_label, + choose_individual_max_utility): trace_label = tracing.extend_trace_label(trace_label, 'presample') chunk_tag = 'tour_destination.presample' @@ -425,7 +430,9 @@ def destination_presample( model_settings, DEST_TAZ, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility + ) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total maz_choices = choose_MAZ_for_TAZ(taz_sample, MAZ_size_terms, trace_label) @@ -444,7 +451,8 @@ def run_destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label): + chunk_size, trace_label, + choose_individual_max_utility): # FIXME - MEMORY HACK - only include columns actually used in spec (omit them pre-merge) chooser_columns = model_settings['SIMULATE_CHOOSER_COLUMNS'] @@ -475,7 +483,9 @@ def run_destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label) + chunk_size, trace_label, + choose_individual_max_utility + ) else: choices = destination_sample( @@ -485,7 +495,9 @@ def run_destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label) + chunk_size, trace_label, + choose_individual_max_utility + ) # remember person_id in chosen alts so we can merge with persons in subsequent steps # (broadcasts person_id onto all alternatives sharing the same tour_id index value) @@ -693,7 +705,9 @@ def run_tour_destination( segment_destination_size_terms, estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label(segment_trace_label, 'sample')) + trace_label=tracing.extend_trace_label(segment_trace_label, 'sample'), + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) # - destination_logsums tour_purpose = segment_name # tour_purpose is segment_name @@ -721,7 +735,9 @@ def run_tour_destination( destination_size_terms=segment_destination_size_terms, estimator=estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label(segment_trace_label, 'simulate')) + trace_label=tracing.extend_trace_label(segment_trace_label, 'simulate'), + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) choices_list.append(choices) From 005b1311daa1ccf3bbdae6195d5d4d33db4bfe26 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 17 Jun 2022 10:29:01 +1000 Subject: [PATCH 047/298] adds interaction_sample at the module level, not individual model --- activitysim/abm/models/tour_od_choice.py | 3 +- activitysim/abm/models/util/tour_od.py | 38 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/activitysim/abm/models/tour_od_choice.py b/activitysim/abm/models/tour_od_choice.py index 6816eed385..41705358e0 100644 --- a/activitysim/abm/models/tour_od_choice.py +++ b/activitysim/abm/models/tour_od_choice.py @@ -93,7 +93,8 @@ def tour_od_choice( model_settings, network_los, estimator, - chunk_size, trace_hh_id, trace_label) + chunk_size, trace_hh_id, trace_label, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if estimator: assert estimator.want_unsampled_alternatives diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 1b47e02cdf..731aebe04a 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -115,7 +115,8 @@ def _od_sample( alt_od_col_name, chunk_size, chunk_tag, - trace_label): + trace_label, + choose_individual_max_utility): model_spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC', @@ -172,7 +173,8 @@ def _od_sample( locals_d=locals_d, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label, + choose_individual_max_utility=choose_individual_max_utility) return choices @@ -477,7 +479,8 @@ def od_presample( destination_size_terms, estimator, chunk_size, - trace_label): + trace_label, + choose_individual_max_utility): trace_label = tracing.extend_trace_label(trace_label, 'presample') chunk_tag = 'tour_od.presample' @@ -507,7 +510,8 @@ def od_presample( alt_od_col_name, chunk_size, chunk_tag, - trace_label) + trace_label, + choose_individual_max_utility) orig_MAZ_dest_TAZ_sample[ORIG_MAZ] = orig_MAZ_dest_TAZ_sample[alt_od_col_name].str.split('_').str[0].astype(int) orig_MAZ_dest_TAZ_sample[DEST_TAZ] = orig_MAZ_dest_TAZ_sample[alt_od_col_name].str.split('_').str[1].astype(int) @@ -582,7 +586,8 @@ def run_od_sample( destination_size_terms, estimator, chunk_size, - trace_label): + trace_label, + choose_individual_max_utility): model_spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC', segment_name=spec_segment_name, estimator=estimator) @@ -616,7 +621,9 @@ def run_od_sample( destination_size_terms, estimator, chunk_size, - trace_label) + trace_label, + choose_individual_max_utility + ) else: choices = od_sample( @@ -626,7 +633,9 @@ def run_od_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label) + chunk_size, trace_label, + choose_individual_max_utility + ) return choices @@ -782,7 +791,8 @@ def run_od_simulate( destination_size_terms, estimator, chunk_size, - trace_label): + trace_label, + choose_individual_max_utility): """ run simulate OD choices on tour_od_sample annotated with mode_choice logsum to select a tour OD from sample alternatives @@ -854,7 +864,7 @@ def run_od_simulate( trace_label=trace_label, trace_choice_name='origin_destination', estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + choose_individual_max_utility=choose_individual_max_utility) if not want_logsums: choices = choices.to_frame('choice') @@ -872,7 +882,8 @@ def run_tour_od( model_settings, network_los, estimator, - chunk_size, trace_hh_id, trace_label): + chunk_size, trace_hh_id, trace_label, + choose_individual_max_utility): size_term_calculator = SizeTermCalculator(model_settings['SIZE_TERM_SELECTOR']) preprocessor_settings = model_settings.get('preprocessor', None) @@ -923,7 +934,9 @@ def run_tour_od( estimator, chunk_size=chunk_size, trace_label=tracing.extend_trace_label( - trace_label, 'sample.%s' % segment_name)) + trace_label, 'sample.%s' % segment_name), + choose_individual_max_utility=choose_individual_max_utility + ) if model_settings['ORIG_FILTER'] == 'original_MAZ > 0': pass @@ -962,7 +975,8 @@ def run_tour_od( destination_size_terms=segment_destination_size_terms, estimator=estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name)) + trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name), + choose_individual_max_utility=choose_individual_max_utility) choices_list.append(choices) if estimator: From bbce6aa028ca1f6cdfbaaea7312fc9bd5ad76ce7 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 13:52:33 +1000 Subject: [PATCH 048/298] nested index instead of name --- notebooks/fru_utils.py | 16 +++-- notebooks/validate_frozen_impl.ipynb | 88 +++++++++++++++++++++++----- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py index 645db8f174..d12b1cc7ee 100644 --- a/notebooks/fru_utils.py +++ b/notebooks/fru_utils.py @@ -143,9 +143,11 @@ def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_sa choices.rename(columns={'logsum': logsum_column_name, 'choice': mode_column_name}, inplace=True) - if not choose_individual_max_utility: - alts = spec.columns - choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) + + # THIS SHOULD NOW BE CONSISTENT + #if not choose_individual_max_utility: + alts = spec.columns + choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) finalise = True if finalise: @@ -304,9 +306,11 @@ def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=T choices.rename(columns={'logsum': logsum_column_name, 'choice': mode_column_name}, inplace=True) - if not choose_individual_max_utility: - alts = spec.columns - choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) + + # SHOULD NOW BE CONSISTENT + #if not choose_individual_max_utility: + alts = spec.columns + choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) ################ choices_list.append(choices) choices_df_asim = pd.concat(choices_list) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 62f8405acd..e87bea3e42 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { "execution": { @@ -26,14 +26,23 @@ }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { "execution": { @@ -52,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": 231, + "execution_count": 5, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -86,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -105,7 +114,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -125,7 +134,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -144,7 +153,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -169,7 +178,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "outputs": [], "source": [ "## need to do this before loading checkpoint tables\n", @@ -185,6 +194,61 @@ } } }, + { + "cell_type": "code", + "execution_count": 11, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Couldn't find checkpoint 'trip_scheduling' in checkpoints\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 init\n", + "1 initialize_landuse\n", + "2 initialize_households\n", + "3 compute_accessibility\n", + "4 school_location\n", + "Name: checkpoint_name, dtype: object\n" + ] + }, + { + "ename": "RuntimeError", + "evalue": "Couldn't find checkpoint 'trip_scheduling' in checkpoints", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mIndexError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mload_checkpoint\u001B[0;34m(checkpoint_name)\u001B[0m\n\u001B[1;32m 353\u001B[0m \u001B[0;31m# truncate rows after target checkpoint\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 354\u001B[0;31m \u001B[0mi\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcheckpoints\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mcheckpoints\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mCHECKPOINT_NAME\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0mcheckpoint_name\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mindex\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m0\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 355\u001B[0m \u001B[0mcheckpoints\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcheckpoints\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mloc\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0mi\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/pandas/core/indexes/base.py\u001B[0m in \u001B[0;36m__getitem__\u001B[0;34m(self, key)\u001B[0m\n\u001B[1;32m 4603\u001B[0m \u001B[0mkey\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcom\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mcast_scalar_indexer\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mkey\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mwarn_float\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m-> 4604\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mgetitem\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mkey\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 4605\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mIndexError\u001B[0m: index 0 is out of bounds for axis 0 with size 0", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mRuntimeError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/notebooks/fru_utils.py\u001B[0m in \u001B[0;36mrun_trip_mode_choice\u001B[0;34m(do_these_purposes, choose_individual_max_utility)\u001B[0m\n\u001B[1;32m 190\u001B[0m \u001B[0mchunk_size\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;36m0\u001B[0m \u001B[0;31m# test_mtc means no chunking\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 191\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 192\u001B[0;31m \u001B[0mpipeline\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mopen_pipeline\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mresume_after\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 193\u001B[0m \u001B[0;31m# preload any bulky injectables (e.g. skims) not in pipeline\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 194\u001B[0m \u001B[0minject\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_injectable\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'preload_injectables'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mopen_pipeline\u001B[0;34m(resume_after)\u001B[0m\n\u001B[1;32m 513\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdebug\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"open_pipeline - open existing pipeline\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 514\u001B[0m \u001B[0mopen_pipeline_store\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0moverwrite\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 515\u001B[0;31m \u001B[0mload_checkpoint\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mresume_after\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 516\u001B[0m \u001B[0;32melse\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 517\u001B[0m \u001B[0;31m# open new, empty pipeline\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mload_checkpoint\u001B[0;34m(checkpoint_name)\u001B[0m\n\u001B[1;32m 362\u001B[0m \u001B[0mprint\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mcheckpoints\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mCHECKPOINT_NAME\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 363\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0merror\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmsg\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 364\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mRuntimeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmsg\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 365\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 366\u001B[0m \u001B[0;31m# convert pandas dataframe back to array of checkpoint dicts\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mRuntimeError\u001B[0m: Couldn't find checkpoint 'trip_scheduling' in checkpoints" + ] + } + ], + "source": [ + "%%time\n", + "trips_df_ru = run_trip_mode_choice()\n", + "trips_df = run_trip_mode_choice(choose_individual_max_utility=False)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": 9, @@ -254,11 +318,7 @@ ] } ], - "source": [ - "%%time\n", - "trips_df_ru = run_trip_mode_choice()\n", - "trips_df = run_trip_mode_choice(choose_individual_max_utility=False)" - ] + "source": [] }, { "cell_type": "code", From 7fbba93275cca185207023ea465a3ebb7caa109a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 14:03:39 +1000 Subject: [PATCH 049/298] fies frozen_rand setting call --- .../abm/models/util/tour_destination.py | 5 +- notebooks/validate_frozen_impl.ipynb | 415 +++++++++++++++++- 2 files changed, 397 insertions(+), 23 deletions(-) diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 5d184a9cb6..7e3303c530 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -578,7 +578,8 @@ def run_destination_simulate( network_los, destination_size_terms, estimator, - chunk_size, trace_label): + chunk_size, trace_label, + choose_individual_max_utility): """ run destination_simulate on tour_destination_sample annotated with mode_choice logsum to select a destination from sample alternatives @@ -645,7 +646,7 @@ def run_destination_simulate( trace_label=trace_label, trace_choice_name='destination', estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + choose_individual_max_utility=choose_individual_max_utility) if not want_logsums: # for consistency, always return a dataframe with canonical column name diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index e87bea3e42..23eba842b3 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { "execution": { @@ -26,23 +26,14 @@ }, "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { "execution": { @@ -61,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "execution": { @@ -95,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "execution": { @@ -114,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -134,7 +125,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -153,7 +144,22 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 15, + "outputs": [], + "source": [ + "from activitysim.core import pipeline\n", + "pipeline.close_pipeline()" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 14, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -165,12 +171,379 @@ }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Configured logging using basicConfig\n", + "INFO - Configured logging using basicConfig\n", + "INFO - Read logging configuration from: configs/logging.yaml\n", + "INFO - SETTING configs_dir: ['configs']\n", + "INFO - SETTING settings_file_name: settings.yaml\n", + "INFO - SETTING data_dir: ['data']\n", + "INFO - SETTING output_dir: output\n", + "INFO - SETTING households_sample_size: 100\n", + "INFO - SETTING chunk_size: 0\n", + "INFO - SETTING chunk_method: hybrid_uss\n", + "INFO - SETTING chunk_training_mode: disabled\n", + "INFO - SETTING multiprocess: None\n", + "INFO - SETTING num_processes: None\n", + "INFO - SETTING resume_after: None\n", + "INFO - SETTING trace_hh_id: [982875]\n", + "INFO - ENV MKL_NUM_THREADS: None\n", + "INFO - ENV OMP_NUM_THREADS: None\n", + "INFO - ENV OPENBLAS_NUM_THREADS: None\n", + "INFO - NUMPY blas_opt_info libraries: ['openblas', 'openblas']\n", + "INFO - NUMPY lapack_opt_info libraries: ['openblas', 'openblas']\n", + "INFO - run single process simulation\n", + "INFO - Time to execute open_pipeline : 0.017 seconds (0.0 minutes)\n", + "INFO - preload_injectables\n", + "INFO - Time to execute preload_injectables : 0.089 seconds (0.0 minutes)\n", + "INFO - #run_model running step initialize_landuse\n", + "Running step 'initialize_landuse'\n", + "INFO - Reading CSV file data/land_use.csv\n", + "INFO - loaded land_use (25, 24)\n", + "INFO - initialize_landuse.annotate_tables - annotating land_use SPEC annotate_landuse\n", + "INFO - Network_LOS using skim_dict_factory: NumpyArraySkimFactory\n", + "INFO - allocate_skim_buffer shared False taz shape (826, 25, 25) total size: 2_065_000 (2.1 MB)\n", + "INFO - _read_skims_from_omx data/skims.omx\n", + "INFO - _read_skims_from_omx loaded 826 skims from data/skims.omx\n", + "INFO - writing skim cache taz (826, 25, 25) to output/cache/cached_taz.mmap\n", + "INFO - load_skims_to_buffer taz shape (826, 25, 25)\n", + "INFO - get_skim_data taz SkimData shape (826, 25, 25)\n", + "INFO - SkimDict init taz\n", + "INFO - SkimDict.build_3d_skim_block_offset_table registered 167 3d keys\n", + "Time to execute step 'initialize_landuse': 2.22 s\n", + "Total time to execute iteration 1 with iteration value None: 2.22 s\n", + "INFO - #run_model running step initialize_households\n", + "Running step 'initialize_households'\n", + "WARNING - setting trace_hh_id is wrong type, should be an int, but was \n", + "INFO - Reading CSV file data/households.csv\n", + "INFO - full household list contains 5000 households\n", + "INFO - sampling 100 of 5000 households\n", + "INFO - loaded households (100, 7)\n", + "INFO - Reading CSV file data/persons.csv\n", + "INFO - loaded persons (167, 7)\n", + "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons\n", + "INFO - initialize_households.annotate_tables - annotating households SPEC annotate_households\n", + "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons_after_hh\n", + "Time to execute step 'initialize_households': 0.41 s\n", + "Total time to execute iteration 1 with iteration value None: 0.41 s\n", + "INFO - #run_model running step compute_accessibility\n", + "Running step 'compute_accessibility'\n", + "INFO - Running compute_accessibility with 25 orig zones 25 dest zones\n", + "INFO - compute_accessibility Running adaptive_chunked_choosers with 25 choosers\n", + "INFO - Running chunk 1 of 1 with 25 of 25 choosers\n", + "INFO - Running compute_accessibility with 25 orig zones 25 dest zones\n", + "INFO - compute_accessibility computed accessibilities (25, 10)\n", + "Time to execute step 'compute_accessibility': 0.08 s\n", + "Total time to execute iteration 1 with iteration value None: 0.08 s\n", + "INFO - #run_model running step school_location\n", + "Running step 'school_location'\n", + "INFO - Running school_location.i1.sample.university with 17 persons\n", + "INFO - school_location.i1.sample.university.interaction_sample Running adaptive_chunked_choosers with 17 choosers\n", + "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", + "INFO - Running eval_interaction_utilities on 102 rows\n", + "INFO - Running school_location.i1.logsums.university with 45 rows\n", + "INFO - school_location.i1.logsums.university.compute_logsums Running adaptive_chunked_choosers with 45 choosers\n", + "INFO - Running chunk 1 of 1 with 45 of 45 choosers\n", + "INFO - Running school_location.i1.simulate.university with 17 persons\n", + "INFO - school_location.i1.simulate.university.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 17 choosers and 45 alternatives\n", + "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", + "INFO - Running eval_interaction_utilities on 45 rows\n", + "INFO - Running school_location.i1.sample.highschool with 5 persons\n", + "INFO - school_location.i1.sample.highschool.interaction_sample Running adaptive_chunked_choosers with 5 choosers\n", + "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", + "INFO - Running eval_interaction_utilities on 10 rows\n", + "INFO - Running school_location.i1.logsums.highschool with 5 rows\n", + "INFO - school_location.i1.logsums.highschool.compute_logsums Running adaptive_chunked_choosers with 5 choosers\n", + "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", + "INFO - Running school_location.i1.simulate.highschool with 5 persons\n", + "INFO - school_location.i1.simulate.highschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 5 choosers and 5 alternatives\n", + "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", + "INFO - Running eval_interaction_utilities on 5 rows\n", + "INFO - Running school_location.i1.sample.gradeschool with 17 persons\n", + "INFO - school_location.i1.sample.gradeschool.interaction_sample Running adaptive_chunked_choosers with 17 choosers\n", + "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", + "INFO - Running eval_interaction_utilities on 425 rows\n", + "INFO - Running school_location.i1.logsums.gradeschool with 165 rows\n", + "INFO - school_location.i1.logsums.gradeschool.compute_logsums Running adaptive_chunked_choosers with 165 choosers\n", + "INFO - Running chunk 1 of 1 with 165 of 165 choosers\n", + "INFO - Running school_location.i1.simulate.gradeschool with 17 persons\n", + "INFO - school_location.i1.simulate.gradeschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 17 choosers and 165 alternatives\n", + "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", + "INFO - Running eval_interaction_utilities on 165 rows\n", + "INFO - write_trace_files iteration 1\n", + "INFO - school_location_logsum top 10 value counts:\n", + "10.852186 1\n", + "12.155904 1\n", + "20.491487 1\n", + "10.513007 1\n", + "20.774530 1\n", + "20.404239 1\n", + "20.284262 1\n", + "10.804509 1\n", + "10.180578 1\n", + "20.918083 1\n", + "Name: logsum, dtype: int64\n", + "Time to execute step 'school_location': 3.48 s\n", + "Total time to execute iteration 1 with iteration value None: 3.48 s\n", + "INFO - #run_model running step workplace_location\n", + "Running step 'workplace_location'\n", + "INFO - Running workplace_location.i1.sample.work_low with 37 persons\n", + "INFO - workplace_location.i1.sample.work_low.interaction_sample Running adaptive_chunked_choosers with 37 choosers\n", + "INFO - Running chunk 1 of 1 with 37 of 37 choosers\n", + "INFO - Running eval_interaction_utilities on 925 rows\n", + "INFO - Running workplace_location.i1.logsums.work_low with 511 rows\n", + "INFO - workplace_location.i1.logsums.work_low.compute_logsums Running adaptive_chunked_choosers with 511 choosers\n", + "INFO - Running chunk 1 of 1 with 511 of 511 choosers\n", + "INFO - Running workplace_location.i1.simulate.work_low with 37 persons\n", + "INFO - workplace_location.i1.simulate.work_low.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 37 choosers and 511 alternatives\n", + "INFO - Running chunk 1 of 1 with 37 of 37 choosers\n", + "INFO - Running eval_interaction_utilities on 511 rows\n", + "INFO - Running workplace_location.i1.sample.work_med with 26 persons\n", + "INFO - workplace_location.i1.sample.work_med.interaction_sample Running adaptive_chunked_choosers with 26 choosers\n", + "INFO - Running chunk 1 of 1 with 26 of 26 choosers\n", + "INFO - Running eval_interaction_utilities on 650 rows\n", + "INFO - Running workplace_location.i1.logsums.work_med with 356 rows\n", + "INFO - workplace_location.i1.logsums.work_med.compute_logsums Running adaptive_chunked_choosers with 356 choosers\n", + "INFO - Running chunk 1 of 1 with 356 of 356 choosers\n", + "INFO - Running workplace_location.i1.simulate.work_med with 26 persons\n", + "INFO - workplace_location.i1.simulate.work_med.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 26 choosers and 356 alternatives\n", + "INFO - Running chunk 1 of 1 with 26 of 26 choosers\n", + "INFO - Running eval_interaction_utilities on 356 rows\n", + "INFO - Running workplace_location.i1.sample.work_high with 16 persons\n", + "INFO - workplace_location.i1.sample.work_high.interaction_sample Running adaptive_chunked_choosers with 16 choosers\n", + "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", + "INFO - Running eval_interaction_utilities on 400 rows\n", + "INFO - Running workplace_location.i1.logsums.work_high with 224 rows\n", + "INFO - workplace_location.i1.logsums.work_high.compute_logsums Running adaptive_chunked_choosers with 224 choosers\n", + "INFO - Running chunk 1 of 1 with 224 of 224 choosers\n", + "INFO - Running workplace_location.i1.simulate.work_high with 16 persons\n", + "INFO - workplace_location.i1.simulate.work_high.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 16 choosers and 224 alternatives\n", + "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", + "INFO - Running eval_interaction_utilities on 224 rows\n", + "INFO - Running workplace_location.i1.sample.work_veryhigh with 18 persons\n", + "INFO - workplace_location.i1.sample.work_veryhigh.interaction_sample Running adaptive_chunked_choosers with 18 choosers\n", + "INFO - Running chunk 1 of 1 with 18 of 18 choosers\n", + "INFO - Running eval_interaction_utilities on 450 rows\n", + "INFO - Running workplace_location.i1.logsums.work_veryhigh with 259 rows\n", + "INFO - workplace_location.i1.logsums.work_veryhigh.compute_logsums Running adaptive_chunked_choosers with 259 choosers\n", + "INFO - Running chunk 1 of 1 with 259 of 259 choosers\n", + "INFO - Running workplace_location.i1.simulate.work_veryhigh with 18 persons\n", + "INFO - workplace_location.i1.simulate.work_veryhigh.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 18 choosers and 259 alternatives\n", + "INFO - Running chunk 1 of 1 with 18 of 18 choosers\n", + "INFO - Running eval_interaction_utilities on 259 rows\n", + "INFO - write_trace_files iteration 1\n", + "INFO - workplace_location_logsum top 10 value counts:\n", + "15.462302 1\n", + "14.407126 1\n", + "15.624373 1\n", + "15.654193 1\n", + "15.696199 1\n", + "15.701733 1\n", + "15.527734 1\n", + "15.547906 1\n", + "13.788314 1\n", + "13.903170 1\n", + "Name: logsum, dtype: int64\n", + "Time to execute step 'workplace_location': 5.28 s\n", + "Total time to execute iteration 1 with iteration value None: 5.28 s\n", + "INFO - #run_model running step auto_ownership_simulate\n", + "Running step 'auto_ownership_simulate'\n", + "INFO - Running auto_ownership_simulate with 100 households\n", + "INFO - auto_ownership_simulate.simple_simulate Running adaptive_chunked_choosers with 100 choosers\n", + "INFO - Running chunk 1 of 1 with 100 of 100 choosers\n", + "INFO - auto_ownership top 10 value counts:\n", + "0 67\n", + "1 32\n", + "2 1\n", + "Name: auto_ownership, dtype: int64\n", + "Time to execute step 'auto_ownership_simulate': 0.15 s\n", + "Total time to execute iteration 1 with iteration value None: 0.15 s\n", + "INFO - #run_model running step free_parking\n", + "Running step 'free_parking'\n", + "INFO - Running free_parking with 97 persons\n", + "INFO - free_parking.simple_simulate Running adaptive_chunked_choosers with 97 choosers\n", + "INFO - Running chunk 1 of 1 with 97 of 97 choosers\n", + "INFO - free_parking top 10 value counts:\n", + "False 163\n", + "True 4\n", + "Name: free_parking_at_work, dtype: int64\n", + "Time to execute step 'free_parking': 0.11 s\n", + "Total time to execute iteration 1 with iteration value None: 0.11 s\n", + "INFO - #run_model running step cdap_simulate\n", + "Running step 'cdap_simulate'\n", + "INFO - Pre-building cdap specs\n", + "INFO - Time to execute build_cdap_spec hh_size 2 : 0.115 seconds (0.0 minutes)\n", + "INFO - Time to execute build_cdap_spec hh_size 3 : 0.495 seconds (0.0 minutes)\n", + "INFO - Time to execute build_cdap_spec hh_size 4 : 1.316 seconds (0.0 minutes)\n", + "INFO - Time to execute build_cdap_spec hh_size 5 : 2.535 seconds (0.0 minutes)\n", + "INFO - Running cdap_simulate with 167 persons\n", + "INFO - cdap.cdap Running chunk 1 of 1 with 100 of 100 choosers\n", + "INFO - cdap_activity top 10 value counts:\n", + "M 89\n", + "N 50\n", + "H 28\n", + "Name: cdap_activity, dtype: int64\n", + "INFO - cdap crosstabs:\n", + "cdap_activity H M N All\n", + "ptype \n", + "1 3 43 6 52\n", + "2 8 19 7 34\n", + "3 1 11 5 17\n", + "4 4 0 17 21\n", + "5 8 0 13 21\n", + "6 4 1 0 5\n", + "7 0 9 2 11\n", + "8 0 6 0 6\n", + "All 28 89 50 167\n", + "Time to execute step 'cdap_simulate': 7.89 s\n", + "Total time to execute iteration 1 with iteration value None: 7.89 s\n", + "INFO - #run_model running step mandatory_tour_frequency\n", + "Running step 'mandatory_tour_frequency'\n", + "INFO - Running mandatory_tour_frequency with 89 persons\n", + "INFO - mandatory_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 89 choosers\n", + "INFO - Running chunk 1 of 1 with 89 of 89 choosers\n", + "INFO - mandatory_tour_frequency top 10 value counts:\n", + " 78\n", + "work1 68\n", + "school1 21\n", + "Name: mandatory_tour_frequency, dtype: int64\n", + "Time to execute step 'mandatory_tour_frequency': 0.68 s\n", + "Total time to execute iteration 1 with iteration value None: 0.68 s\n", + "INFO - #run_model running step mandatory_tour_scheduling\n", + "Running step 'mandatory_tour_scheduling'\n", + "DEBUG - @inject timetable\n", + "INFO - Running mandatory_tour_scheduling with 89 tours\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 68 tour choices\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work Running adaptive_chunked_choosers with 68 choosers\n", + "INFO - Running chunk 1 of 1 with 68 of 68 choosers\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 68 tour choices\n", + "INFO - tdd_alt_segments specified for representative logsums\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 12920 to 1020 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums compute_logsums for 1020 choosers 1020 alts\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums Running adaptive_chunked_choosers with 1020 choosers\n", + "INFO - Running chunk 1 of 1 with 1020 of 1020 choosers\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 68 choosers and 12920 alternatives\n", + "INFO - Running chunk 1 of 1 with 68 of 68 choosers\n", + "INFO - Running eval_interaction_utilities on 12920 rows\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 16 tour choices\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school Running adaptive_chunked_choosers with 16 choosers\n", + "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 16 tour choices\n", + "INFO - tdd_alt_segments specified for representative logsums\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 3040 to 240 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums compute_logsums for 240 choosers 240 alts\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums Running adaptive_chunked_choosers with 240 choosers\n", + "INFO - Running chunk 1 of 1 with 240 of 240 choosers\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 16 choosers and 3040 alternatives\n", + "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", + "INFO - Running eval_interaction_utilities on 3040 rows\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 5 tour choices\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ Running adaptive_chunked_choosers with 5 choosers\n", + "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 5 tour choices\n", + "INFO - tdd_alt_segments specified for representative logsums\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 950 to 75 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums compute_logsums for 75 choosers 75 alts\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums Running adaptive_chunked_choosers with 75 choosers\n", + "INFO - Running chunk 1 of 1 with 75 of 75 choosers\n", + "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 5 choosers and 950 alternatives\n", + "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", + "INFO - Running eval_interaction_utilities on 950 rows\n", + "Time to execute step 'mandatory_tour_scheduling': 3.83 s\n", + "Total time to execute iteration 1 with iteration value None: 3.83 s\n", + "INFO - #run_model running step joint_tour_frequency\n", + "Running step 'joint_tour_frequency'\n", + "INFO - Running joint_tour_frequency with 36 multi-person households\n", + "DEBUG - @inject timetable\n", + "INFO - joint_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 36 choosers\n", + "INFO - Running chunk 1 of 1 with 36 of 36 choosers\n", + "INFO - joint_tour_frequency top 10 value counts:\n", + "0_tours 96\n", + "1_Disc 2\n", + "1_Shop 1\n", + "1_Eat 1\n", + "Name: joint_tour_frequency, dtype: int64\n", + "Time to execute step 'joint_tour_frequency': 0.38 s\n", + "Total time to execute iteration 1 with iteration value None: 0.38 s\n", + "INFO - #run_model running step joint_tour_composition\n", + "Running step 'joint_tour_composition'\n", + "INFO - Running joint_tour_composition with 4 joint tours\n", + "DEBUG - @inject timetable\n", + "INFO - joint_tour_composition.simple_simulate Running adaptive_chunked_choosers with 4 choosers\n", + "INFO - Running chunk 1 of 1 with 4 of 4 choosers\n", + "INFO - joint_tour_composition top 10 value counts:\n", + "adults 2\n", + "children 1\n", + "mixed 1\n", + "Name: composition, dtype: int64\n", + "Time to execute step 'joint_tour_composition': 0.24 s\n", + "Total time to execute iteration 1 with iteration value None: 0.24 s\n", + "INFO - #run_model running step joint_tour_participation\n", + "Running step 'joint_tour_participation'\n", + "INFO - Running joint_tours_participation with 8 potential participants (candidates)\n", + "DEBUG - @inject timetable\n", + "INFO - joint_tour_participation Running chunk 1 of 1 with 4 of 4 choosers\n", + "INFO - joint_tour_participation.eval_mnl.participants_chooser 4 joint tours to satisfy.\n", + "INFO - joint_tour_participation.eval_mnl.participants_chooser 1 iterations to satisfy all joint tours.\n", + "Time to execute step 'joint_tour_participation': 0.46 s\n", + "Total time to execute iteration 1 with iteration value None: 0.46 s\n", + "INFO - #run_model running step joint_tour_destination\n", + "Running step 'joint_tour_destination'\n", + "INFO - running joint_tour_destination.shopping.sample with 1 tours\n", + "INFO - joint_tour_destination.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 1 choosers\n", + "INFO - Running chunk 1 of 1 with 1 of 1 choosers\n", + "INFO - Running eval_interaction_utilities on 25 rows\n", + "INFO - Running joint_tour_destination.shopping.logsums with 11 rows\n", + "INFO - joint_tour_destination.shopping.logsums.compute_logsums Running adaptive_chunked_choosers with 11 choosers\n", + "INFO - Running chunk 1 of 1 with 11 of 11 choosers\n", + "INFO - Time to execute all models until this error : 29.773 seconds (0.5 minutes)\n", + "ERROR - activitysim run encountered an unrecoverable error\n", + "Traceback (most recent call last):\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/cli/run.py\", line 261, in run\n", + " pipeline.run(models=config.setting('models'), resume_after=resume_after)\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\", line 617, in run\n", + " run_model(model)\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\", line 476, in run_model\n", + " orca.run([step_name])\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\", line 2168, in run\n", + " step()\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\", line 973, in __call__\n", + " return self._func(**kwargs)\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/joint_tour_destination.py\", line 68, in joint_tour_destination\n", + " choices_df, save_sample_df = tour_destination.run_tour_destination(\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/util/tour_destination.py\", line 727, in run_tour_destination\n", + " run_destination_simulate(\n", + "TypeError: run_destination_simulate() got an unexpected keyword argument 'choose_individual_max_utility'\n" + ] + }, + { + "ename": "TypeError", + "evalue": "run_destination_simulate() got an unexpected keyword argument 'choose_individual_max_utility'", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mTypeError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_2932/4273387002.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 2\u001B[0m \u001B[0mrun\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_run_args\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mparser\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0margs\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mparser\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mparse_args\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m'-c'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'configs'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'-o'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'output'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'-d'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'data'\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m----> 4\u001B[0;31m \u001B[0mrun\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;31m# 2mins full example run\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 5\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0minject\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mis_injectable\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'preload_injectables'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 6\u001B[0m \u001B[0;32mfrom\u001B[0m \u001B[0mactivitysim\u001B[0m \u001B[0;32mimport\u001B[0m \u001B[0mabm\u001B[0m \u001B[0;31m# register abm steps and other abm-specific injectables\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/cli/run.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(args)\u001B[0m\n\u001B[1;32m 259\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'run single process simulation'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 260\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 261\u001B[0;31m \u001B[0mpipeline\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodels\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msetting\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'models'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mresume_after\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mresume_after\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 262\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 263\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msetting\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'cleanup_pipeline_after_run'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(models, resume_after)\u001B[0m\n\u001B[1;32m 615\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mmodel\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mmodels\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 616\u001B[0m \u001B[0mt1\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mprint_elapsed_time\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 617\u001B[0;31m \u001B[0mrun_model\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodel\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 618\u001B[0m \u001B[0mmem\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mtrace_memory_info\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34mf\"pipeline.run after {model}\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 619\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mrun_model\u001B[0;34m(model_name)\u001B[0m\n\u001B[1;32m 474\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34mf\"#run_model running step {step_name}\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 475\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 476\u001B[0;31m \u001B[0morca\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mstep_name\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 477\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 478\u001B[0m \u001B[0mt0\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mprint_elapsed_time\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"#run_model completed step '%s'\"\u001B[0m \u001B[0;34m%\u001B[0m \u001B[0mmodel_name\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mt0\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mdebug\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(steps, iter_vars, data_out, out_interval, out_base_tables, out_run_tables, compress, out_base_local, out_run_local)\u001B[0m\n\u001B[1;32m 2166\u001B[0m \u001B[0mstep\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mget_step\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mstep_name\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 2167\u001B[0m \u001B[0mt2\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mtime\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mtime\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m-> 2168\u001B[0;31m \u001B[0mstep\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 2169\u001B[0m print(\"Time to execute step '{}': {:.2f} s\".format(\n\u001B[1;32m 2170\u001B[0m step_name, time.time() - t2))\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\u001B[0m in \u001B[0;36m__call__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 971\u001B[0m kwargs = _collect_variables(names=self._argspec.args,\n\u001B[1;32m 972\u001B[0m expressions=self._argspec.defaults)\n\u001B[0;32m--> 973\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_func\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m**\u001B[0m\u001B[0mkwargs\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 974\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 975\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m_tables_used\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/joint_tour_destination.py\u001B[0m in \u001B[0;36mjoint_tour_destination\u001B[0;34m(tours, persons_merged, households_merged, network_los, chunk_size, trace_hh_id)\u001B[0m\n\u001B[1;32m 66\u001B[0m \u001B[0mestimator\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mwrite_model_settings\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodel_settings\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmodel_settings_file_name\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 67\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 68\u001B[0;31m choices_df, save_sample_df = tour_destination.run_tour_destination(\n\u001B[0m\u001B[1;32m 69\u001B[0m \u001B[0mtours\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 70\u001B[0m \u001B[0mpersons_merged\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/util/tour_destination.py\u001B[0m in \u001B[0;36mrun_tour_destination\u001B[0;34m(tours, persons_merged, want_logsums, want_sample_table, model_settings, network_los, estimator, chunk_size, trace_hh_id, trace_label)\u001B[0m\n\u001B[1;32m 725\u001B[0m \u001B[0mspec_segment_name\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0msegment_name\u001B[0m \u001B[0;31m# spec_segment_name is segment_name\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 726\u001B[0m \u001B[0mchoices\u001B[0m \u001B[0;34m=\u001B[0m\u001B[0;31m \u001B[0m\u001B[0;31m\\\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 727\u001B[0;31m run_destination_simulate(\n\u001B[0m\u001B[1;32m 728\u001B[0m \u001B[0mspec_segment_name\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 729\u001B[0m \u001B[0mchoosers\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mTypeError\u001B[0m: run_destination_simulate() got an unexpected keyword argument 'choose_individual_max_utility'" + ] + } + ], "source": [ "parser = argparse.ArgumentParser()\n", "run.add_run_args(parser)\n", "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", - "#run.run(args) # 2mins full example run\n", + "run.run(args) # 2mins full example run\n", "if not inject.is_injectable('preload_injectables'):\n", " from activitysim import abm # register abm steps and other abm-specific injectables\n", "run.handle_standard_args(args) # possibly update injectables" @@ -178,7 +551,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "outputs": [], "source": [ "## need to do this before loading checkpoint tables\n", @@ -196,7 +569,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "outputs": [ { "name": "stderr", From 8c1d1ed2b2d71c0e3673b115d121df04157ed6a1 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 14:30:45 +1000 Subject: [PATCH 050/298] pass arguments in --- activitysim/abm/models/trip_destination.py | 3 +- notebooks/validate_frozen_impl.ipynb | 334 ++------------------- 2 files changed, 21 insertions(+), 316 deletions(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 31a99fa67b..1042197b83 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -745,7 +745,8 @@ def choose_trip_destination( size_term_matrix, skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label): + trace_label, + choose_individual_max_utility): logger.info("choose_trip_destination %s with %d trips", trace_label, trips.shape[0]) diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb index 23eba842b3..cf89790669 100644 --- a/notebooks/validate_frozen_impl.ipynb +++ b/notebooks/validate_frozen_impl.ipynb @@ -144,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 19, "outputs": [], "source": [ "from activitysim.core import pipeline\n", @@ -159,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 18, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -197,310 +197,11 @@ "INFO - NUMPY blas_opt_info libraries: ['openblas', 'openblas']\n", "INFO - NUMPY lapack_opt_info libraries: ['openblas', 'openblas']\n", "INFO - run single process simulation\n", - "INFO - Time to execute open_pipeline : 0.017 seconds (0.0 minutes)\n", - "INFO - preload_injectables\n", - "INFO - Time to execute preload_injectables : 0.089 seconds (0.0 minutes)\n", + "INFO - Time to execute open_pipeline : 0.163 seconds (0.0 minutes)\n", + "INFO - Time to execute preload_injectables : 0.08 seconds (0.0 minutes)\n", "INFO - #run_model running step initialize_landuse\n", "Running step 'initialize_landuse'\n", - "INFO - Reading CSV file data/land_use.csv\n", - "INFO - loaded land_use (25, 24)\n", - "INFO - initialize_landuse.annotate_tables - annotating land_use SPEC annotate_landuse\n", - "INFO - Network_LOS using skim_dict_factory: NumpyArraySkimFactory\n", - "INFO - allocate_skim_buffer shared False taz shape (826, 25, 25) total size: 2_065_000 (2.1 MB)\n", - "INFO - _read_skims_from_omx data/skims.omx\n", - "INFO - _read_skims_from_omx loaded 826 skims from data/skims.omx\n", - "INFO - writing skim cache taz (826, 25, 25) to output/cache/cached_taz.mmap\n", - "INFO - load_skims_to_buffer taz shape (826, 25, 25)\n", - "INFO - get_skim_data taz SkimData shape (826, 25, 25)\n", - "INFO - SkimDict init taz\n", - "INFO - SkimDict.build_3d_skim_block_offset_table registered 167 3d keys\n", - "Time to execute step 'initialize_landuse': 2.22 s\n", - "Total time to execute iteration 1 with iteration value None: 2.22 s\n", - "INFO - #run_model running step initialize_households\n", - "Running step 'initialize_households'\n", - "WARNING - setting trace_hh_id is wrong type, should be an int, but was \n", - "INFO - Reading CSV file data/households.csv\n", - "INFO - full household list contains 5000 households\n", - "INFO - sampling 100 of 5000 households\n", - "INFO - loaded households (100, 7)\n", - "INFO - Reading CSV file data/persons.csv\n", - "INFO - loaded persons (167, 7)\n", - "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons\n", - "INFO - initialize_households.annotate_tables - annotating households SPEC annotate_households\n", - "INFO - initialize_households.annotate_tables - annotating persons SPEC annotate_persons_after_hh\n", - "Time to execute step 'initialize_households': 0.41 s\n", - "Total time to execute iteration 1 with iteration value None: 0.41 s\n", - "INFO - #run_model running step compute_accessibility\n", - "Running step 'compute_accessibility'\n", - "INFO - Running compute_accessibility with 25 orig zones 25 dest zones\n", - "INFO - compute_accessibility Running adaptive_chunked_choosers with 25 choosers\n", - "INFO - Running chunk 1 of 1 with 25 of 25 choosers\n", - "INFO - Running compute_accessibility with 25 orig zones 25 dest zones\n", - "INFO - compute_accessibility computed accessibilities (25, 10)\n", - "Time to execute step 'compute_accessibility': 0.08 s\n", - "Total time to execute iteration 1 with iteration value None: 0.08 s\n", - "INFO - #run_model running step school_location\n", - "Running step 'school_location'\n", - "INFO - Running school_location.i1.sample.university with 17 persons\n", - "INFO - school_location.i1.sample.university.interaction_sample Running adaptive_chunked_choosers with 17 choosers\n", - "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", - "INFO - Running eval_interaction_utilities on 102 rows\n", - "INFO - Running school_location.i1.logsums.university with 45 rows\n", - "INFO - school_location.i1.logsums.university.compute_logsums Running adaptive_chunked_choosers with 45 choosers\n", - "INFO - Running chunk 1 of 1 with 45 of 45 choosers\n", - "INFO - Running school_location.i1.simulate.university with 17 persons\n", - "INFO - school_location.i1.simulate.university.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 17 choosers and 45 alternatives\n", - "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", - "INFO - Running eval_interaction_utilities on 45 rows\n", - "INFO - Running school_location.i1.sample.highschool with 5 persons\n", - "INFO - school_location.i1.sample.highschool.interaction_sample Running adaptive_chunked_choosers with 5 choosers\n", - "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", - "INFO - Running eval_interaction_utilities on 10 rows\n", - "INFO - Running school_location.i1.logsums.highschool with 5 rows\n", - "INFO - school_location.i1.logsums.highschool.compute_logsums Running adaptive_chunked_choosers with 5 choosers\n", - "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", - "INFO - Running school_location.i1.simulate.highschool with 5 persons\n", - "INFO - school_location.i1.simulate.highschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 5 choosers and 5 alternatives\n", - "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", - "INFO - Running eval_interaction_utilities on 5 rows\n", - "INFO - Running school_location.i1.sample.gradeschool with 17 persons\n", - "INFO - school_location.i1.sample.gradeschool.interaction_sample Running adaptive_chunked_choosers with 17 choosers\n", - "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", - "INFO - Running eval_interaction_utilities on 425 rows\n", - "INFO - Running school_location.i1.logsums.gradeschool with 165 rows\n", - "INFO - school_location.i1.logsums.gradeschool.compute_logsums Running adaptive_chunked_choosers with 165 choosers\n", - "INFO - Running chunk 1 of 1 with 165 of 165 choosers\n", - "INFO - Running school_location.i1.simulate.gradeschool with 17 persons\n", - "INFO - school_location.i1.simulate.gradeschool.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 17 choosers and 165 alternatives\n", - "INFO - Running chunk 1 of 1 with 17 of 17 choosers\n", - "INFO - Running eval_interaction_utilities on 165 rows\n", - "INFO - write_trace_files iteration 1\n", - "INFO - school_location_logsum top 10 value counts:\n", - "10.852186 1\n", - "12.155904 1\n", - "20.491487 1\n", - "10.513007 1\n", - "20.774530 1\n", - "20.404239 1\n", - "20.284262 1\n", - "10.804509 1\n", - "10.180578 1\n", - "20.918083 1\n", - "Name: logsum, dtype: int64\n", - "Time to execute step 'school_location': 3.48 s\n", - "Total time to execute iteration 1 with iteration value None: 3.48 s\n", - "INFO - #run_model running step workplace_location\n", - "Running step 'workplace_location'\n", - "INFO - Running workplace_location.i1.sample.work_low with 37 persons\n", - "INFO - workplace_location.i1.sample.work_low.interaction_sample Running adaptive_chunked_choosers with 37 choosers\n", - "INFO - Running chunk 1 of 1 with 37 of 37 choosers\n", - "INFO - Running eval_interaction_utilities on 925 rows\n", - "INFO - Running workplace_location.i1.logsums.work_low with 511 rows\n", - "INFO - workplace_location.i1.logsums.work_low.compute_logsums Running adaptive_chunked_choosers with 511 choosers\n", - "INFO - Running chunk 1 of 1 with 511 of 511 choosers\n", - "INFO - Running workplace_location.i1.simulate.work_low with 37 persons\n", - "INFO - workplace_location.i1.simulate.work_low.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 37 choosers and 511 alternatives\n", - "INFO - Running chunk 1 of 1 with 37 of 37 choosers\n", - "INFO - Running eval_interaction_utilities on 511 rows\n", - "INFO - Running workplace_location.i1.sample.work_med with 26 persons\n", - "INFO - workplace_location.i1.sample.work_med.interaction_sample Running adaptive_chunked_choosers with 26 choosers\n", - "INFO - Running chunk 1 of 1 with 26 of 26 choosers\n", - "INFO - Running eval_interaction_utilities on 650 rows\n", - "INFO - Running workplace_location.i1.logsums.work_med with 356 rows\n", - "INFO - workplace_location.i1.logsums.work_med.compute_logsums Running adaptive_chunked_choosers with 356 choosers\n", - "INFO - Running chunk 1 of 1 with 356 of 356 choosers\n", - "INFO - Running workplace_location.i1.simulate.work_med with 26 persons\n", - "INFO - workplace_location.i1.simulate.work_med.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 26 choosers and 356 alternatives\n", - "INFO - Running chunk 1 of 1 with 26 of 26 choosers\n", - "INFO - Running eval_interaction_utilities on 356 rows\n", - "INFO - Running workplace_location.i1.sample.work_high with 16 persons\n", - "INFO - workplace_location.i1.sample.work_high.interaction_sample Running adaptive_chunked_choosers with 16 choosers\n", - "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", - "INFO - Running eval_interaction_utilities on 400 rows\n", - "INFO - Running workplace_location.i1.logsums.work_high with 224 rows\n", - "INFO - workplace_location.i1.logsums.work_high.compute_logsums Running adaptive_chunked_choosers with 224 choosers\n", - "INFO - Running chunk 1 of 1 with 224 of 224 choosers\n", - "INFO - Running workplace_location.i1.simulate.work_high with 16 persons\n", - "INFO - workplace_location.i1.simulate.work_high.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 16 choosers and 224 alternatives\n", - "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", - "INFO - Running eval_interaction_utilities on 224 rows\n", - "INFO - Running workplace_location.i1.sample.work_veryhigh with 18 persons\n", - "INFO - workplace_location.i1.sample.work_veryhigh.interaction_sample Running adaptive_chunked_choosers with 18 choosers\n", - "INFO - Running chunk 1 of 1 with 18 of 18 choosers\n", - "INFO - Running eval_interaction_utilities on 450 rows\n", - "INFO - Running workplace_location.i1.logsums.work_veryhigh with 259 rows\n", - "INFO - workplace_location.i1.logsums.work_veryhigh.compute_logsums Running adaptive_chunked_choosers with 259 choosers\n", - "INFO - Running chunk 1 of 1 with 259 of 259 choosers\n", - "INFO - Running workplace_location.i1.simulate.work_veryhigh with 18 persons\n", - "INFO - workplace_location.i1.simulate.work_veryhigh.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 18 choosers and 259 alternatives\n", - "INFO - Running chunk 1 of 1 with 18 of 18 choosers\n", - "INFO - Running eval_interaction_utilities on 259 rows\n", - "INFO - write_trace_files iteration 1\n", - "INFO - workplace_location_logsum top 10 value counts:\n", - "15.462302 1\n", - "14.407126 1\n", - "15.624373 1\n", - "15.654193 1\n", - "15.696199 1\n", - "15.701733 1\n", - "15.527734 1\n", - "15.547906 1\n", - "13.788314 1\n", - "13.903170 1\n", - "Name: logsum, dtype: int64\n", - "Time to execute step 'workplace_location': 5.28 s\n", - "Total time to execute iteration 1 with iteration value None: 5.28 s\n", - "INFO - #run_model running step auto_ownership_simulate\n", - "Running step 'auto_ownership_simulate'\n", - "INFO - Running auto_ownership_simulate with 100 households\n", - "INFO - auto_ownership_simulate.simple_simulate Running adaptive_chunked_choosers with 100 choosers\n", - "INFO - Running chunk 1 of 1 with 100 of 100 choosers\n", - "INFO - auto_ownership top 10 value counts:\n", - "0 67\n", - "1 32\n", - "2 1\n", - "Name: auto_ownership, dtype: int64\n", - "Time to execute step 'auto_ownership_simulate': 0.15 s\n", - "Total time to execute iteration 1 with iteration value None: 0.15 s\n", - "INFO - #run_model running step free_parking\n", - "Running step 'free_parking'\n", - "INFO - Running free_parking with 97 persons\n", - "INFO - free_parking.simple_simulate Running adaptive_chunked_choosers with 97 choosers\n", - "INFO - Running chunk 1 of 1 with 97 of 97 choosers\n", - "INFO - free_parking top 10 value counts:\n", - "False 163\n", - "True 4\n", - "Name: free_parking_at_work, dtype: int64\n", - "Time to execute step 'free_parking': 0.11 s\n", - "Total time to execute iteration 1 with iteration value None: 0.11 s\n", - "INFO - #run_model running step cdap_simulate\n", - "Running step 'cdap_simulate'\n", - "INFO - Pre-building cdap specs\n", - "INFO - Time to execute build_cdap_spec hh_size 2 : 0.115 seconds (0.0 minutes)\n", - "INFO - Time to execute build_cdap_spec hh_size 3 : 0.495 seconds (0.0 minutes)\n", - "INFO - Time to execute build_cdap_spec hh_size 4 : 1.316 seconds (0.0 minutes)\n", - "INFO - Time to execute build_cdap_spec hh_size 5 : 2.535 seconds (0.0 minutes)\n", - "INFO - Running cdap_simulate with 167 persons\n", - "INFO - cdap.cdap Running chunk 1 of 1 with 100 of 100 choosers\n", - "INFO - cdap_activity top 10 value counts:\n", - "M 89\n", - "N 50\n", - "H 28\n", - "Name: cdap_activity, dtype: int64\n", - "INFO - cdap crosstabs:\n", - "cdap_activity H M N All\n", - "ptype \n", - "1 3 43 6 52\n", - "2 8 19 7 34\n", - "3 1 11 5 17\n", - "4 4 0 17 21\n", - "5 8 0 13 21\n", - "6 4 1 0 5\n", - "7 0 9 2 11\n", - "8 0 6 0 6\n", - "All 28 89 50 167\n", - "Time to execute step 'cdap_simulate': 7.89 s\n", - "Total time to execute iteration 1 with iteration value None: 7.89 s\n", - "INFO - #run_model running step mandatory_tour_frequency\n", - "Running step 'mandatory_tour_frequency'\n", - "INFO - Running mandatory_tour_frequency with 89 persons\n", - "INFO - mandatory_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 89 choosers\n", - "INFO - Running chunk 1 of 1 with 89 of 89 choosers\n", - "INFO - mandatory_tour_frequency top 10 value counts:\n", - " 78\n", - "work1 68\n", - "school1 21\n", - "Name: mandatory_tour_frequency, dtype: int64\n", - "Time to execute step 'mandatory_tour_frequency': 0.68 s\n", - "Total time to execute iteration 1 with iteration value None: 0.68 s\n", - "INFO - #run_model running step mandatory_tour_scheduling\n", - "Running step 'mandatory_tour_scheduling'\n", - "DEBUG - @inject timetable\n", - "INFO - Running mandatory_tour_scheduling with 89 tours\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 68 tour choices\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work Running adaptive_chunked_choosers with 68 choosers\n", - "INFO - Running chunk 1 of 1 with 68 of 68 choosers\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work schedule_tours running 68 tour choices\n", - "INFO - tdd_alt_segments specified for representative logsums\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 12920 to 1020 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums compute_logsums for 1020 choosers 1020 alts\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.compute_logsums.logsums Running adaptive_chunked_choosers with 1020 choosers\n", - "INFO - Running chunk 1 of 1 with 1020 of 1020 choosers\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.work.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 68 choosers and 12920 alternatives\n", - "INFO - Running chunk 1 of 1 with 68 of 68 choosers\n", - "INFO - Running eval_interaction_utilities on 12920 rows\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 16 tour choices\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school Running adaptive_chunked_choosers with 16 choosers\n", - "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school schedule_tours running 16 tour choices\n", - "INFO - tdd_alt_segments specified for representative logsums\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 3040 to 240 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums compute_logsums for 240 choosers 240 alts\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.compute_logsums.logsums Running adaptive_chunked_choosers with 240 choosers\n", - "INFO - Running chunk 1 of 1 with 240 of 240 choosers\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.school.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 16 choosers and 3040 alternatives\n", - "INFO - Running chunk 1 of 1 with 16 of 16 choosers\n", - "INFO - Running eval_interaction_utilities on 3040 rows\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 5 tour choices\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ Running adaptive_chunked_choosers with 5 choosers\n", - "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ schedule_tours running 5 tour choices\n", - "INFO - tdd_alt_segments specified for representative logsums\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums compute_logsums deduped_alt_tdds reduced number of rows by 92.11% from 950 to 75 compared to USE_BRUTE_FORCE_TO_COMPUTE_LOGSUMS\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums compute_logsums for 75 choosers 75 alts\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.compute_logsums.logsums Running adaptive_chunked_choosers with 75 choosers\n", - "INFO - Running chunk 1 of 1 with 75 of 75 choosers\n", - "INFO - mandatory_tour_scheduling.vectorize_tour_scheduling.tour_1.univ.interaction_sample_simulate Running adaptive_chunked_choosers_and_alts with 5 choosers and 950 alternatives\n", - "INFO - Running chunk 1 of 1 with 5 of 5 choosers\n", - "INFO - Running eval_interaction_utilities on 950 rows\n", - "Time to execute step 'mandatory_tour_scheduling': 3.83 s\n", - "Total time to execute iteration 1 with iteration value None: 3.83 s\n", - "INFO - #run_model running step joint_tour_frequency\n", - "Running step 'joint_tour_frequency'\n", - "INFO - Running joint_tour_frequency with 36 multi-person households\n", - "DEBUG - @inject timetable\n", - "INFO - joint_tour_frequency.simple_simulate Running adaptive_chunked_choosers with 36 choosers\n", - "INFO - Running chunk 1 of 1 with 36 of 36 choosers\n", - "INFO - joint_tour_frequency top 10 value counts:\n", - "0_tours 96\n", - "1_Disc 2\n", - "1_Shop 1\n", - "1_Eat 1\n", - "Name: joint_tour_frequency, dtype: int64\n", - "Time to execute step 'joint_tour_frequency': 0.38 s\n", - "Total time to execute iteration 1 with iteration value None: 0.38 s\n", - "INFO - #run_model running step joint_tour_composition\n", - "Running step 'joint_tour_composition'\n", - "INFO - Running joint_tour_composition with 4 joint tours\n", - "DEBUG - @inject timetable\n", - "INFO - joint_tour_composition.simple_simulate Running adaptive_chunked_choosers with 4 choosers\n", - "INFO - Running chunk 1 of 1 with 4 of 4 choosers\n", - "INFO - joint_tour_composition top 10 value counts:\n", - "adults 2\n", - "children 1\n", - "mixed 1\n", - "Name: composition, dtype: int64\n", - "Time to execute step 'joint_tour_composition': 0.24 s\n", - "Total time to execute iteration 1 with iteration value None: 0.24 s\n", - "INFO - #run_model running step joint_tour_participation\n", - "Running step 'joint_tour_participation'\n", - "INFO - Running joint_tours_participation with 8 potential participants (candidates)\n", - "DEBUG - @inject timetable\n", - "INFO - joint_tour_participation Running chunk 1 of 1 with 4 of 4 choosers\n", - "INFO - joint_tour_participation.eval_mnl.participants_chooser 4 joint tours to satisfy.\n", - "INFO - joint_tour_participation.eval_mnl.participants_chooser 1 iterations to satisfy all joint tours.\n", - "Time to execute step 'joint_tour_participation': 0.46 s\n", - "Total time to execute iteration 1 with iteration value None: 0.46 s\n", - "INFO - #run_model running step joint_tour_destination\n", - "Running step 'joint_tour_destination'\n", - "INFO - running joint_tour_destination.shopping.sample with 1 tours\n", - "INFO - joint_tour_destination.shopping.sample.interaction_sample Running adaptive_chunked_choosers with 1 choosers\n", - "INFO - Running chunk 1 of 1 with 1 of 1 choosers\n", - "INFO - Running eval_interaction_utilities on 25 rows\n", - "INFO - Running joint_tour_destination.shopping.logsums with 11 rows\n", - "INFO - joint_tour_destination.shopping.logsums.compute_logsums Running adaptive_chunked_choosers with 11 choosers\n", - "INFO - Running chunk 1 of 1 with 11 of 11 choosers\n", - "INFO - Time to execute all models until this error : 29.773 seconds (0.5 minutes)\n", + "INFO - Time to execute all models until this error : 0.393 seconds (0.0 minutes)\n", "ERROR - activitysim run encountered an unrecoverable error\n", "Traceback (most recent call last):\n", " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/cli/run.py\", line 261, in run\n", @@ -513,29 +214,32 @@ " step()\n", " File \"/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\", line 973, in __call__\n", " return self._func(**kwargs)\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/joint_tour_destination.py\", line 68, in joint_tour_destination\n", - " choices_df, save_sample_df = tour_destination.run_tour_destination(\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/util/tour_destination.py\", line 727, in run_tour_destination\n", - " run_destination_simulate(\n", - "TypeError: run_destination_simulate() got an unexpected keyword argument 'choose_individual_max_utility'\n" + " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/initialize.py\", line 100, in initialize_landuse\n", + " with chunk.chunk_log(trace_label, base=True):\n", + " File \"/home/jan/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py\", line 113, in __enter__\n", + " return next(self.gen)\n", + " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/chunk.py\", line 929, in chunk_log\n", + " assert base == (len(CHUNK_SIZERS) == 0)\n", + "AssertionError\n" ] }, { - "ename": "TypeError", - "evalue": "run_destination_simulate() got an unexpected keyword argument 'choose_individual_max_utility'", + "ename": "AssertionError", + "evalue": "", "output_type": "error", "traceback": [ "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mTypeError\u001B[0m Traceback (most recent call last)", + "\u001B[0;31mAssertionError\u001B[0m Traceback (most recent call last)", "\u001B[0;32m/tmp/ipykernel_2932/4273387002.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 2\u001B[0m \u001B[0mrun\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_run_args\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mparser\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0margs\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mparser\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mparse_args\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m'-c'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'configs'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'-o'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'output'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'-d'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'data'\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m----> 4\u001B[0;31m \u001B[0mrun\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;31m# 2mins full example run\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 5\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0minject\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mis_injectable\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'preload_injectables'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 6\u001B[0m \u001B[0;32mfrom\u001B[0m \u001B[0mactivitysim\u001B[0m \u001B[0;32mimport\u001B[0m \u001B[0mabm\u001B[0m \u001B[0;31m# register abm steps and other abm-specific injectables\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/cli/run.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(args)\u001B[0m\n\u001B[1;32m 259\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'run single process simulation'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 260\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 261\u001B[0;31m \u001B[0mpipeline\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodels\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msetting\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'models'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mresume_after\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mresume_after\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 262\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 263\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msetting\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'cleanup_pipeline_after_run'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(models, resume_after)\u001B[0m\n\u001B[1;32m 615\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mmodel\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mmodels\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 616\u001B[0m \u001B[0mt1\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mprint_elapsed_time\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 617\u001B[0;31m \u001B[0mrun_model\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodel\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 618\u001B[0m \u001B[0mmem\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mtrace_memory_info\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34mf\"pipeline.run after {model}\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 619\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mrun_model\u001B[0;34m(model_name)\u001B[0m\n\u001B[1;32m 474\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34mf\"#run_model running step {step_name}\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 475\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 476\u001B[0;31m \u001B[0morca\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mstep_name\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 477\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 478\u001B[0m \u001B[0mt0\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mprint_elapsed_time\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"#run_model completed step '%s'\"\u001B[0m \u001B[0;34m%\u001B[0m \u001B[0mmodel_name\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mt0\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mdebug\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(steps, iter_vars, data_out, out_interval, out_base_tables, out_run_tables, compress, out_base_local, out_run_local)\u001B[0m\n\u001B[1;32m 2166\u001B[0m \u001B[0mstep\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mget_step\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mstep_name\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 2167\u001B[0m \u001B[0mt2\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mtime\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mtime\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m-> 2168\u001B[0;31m \u001B[0mstep\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 2169\u001B[0m print(\"Time to execute step '{}': {:.2f} s\".format(\n\u001B[1;32m 2170\u001B[0m step_name, time.time() - t2))\n", "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\u001B[0m in \u001B[0;36m__call__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 971\u001B[0m kwargs = _collect_variables(names=self._argspec.args,\n\u001B[1;32m 972\u001B[0m expressions=self._argspec.defaults)\n\u001B[0;32m--> 973\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_func\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m**\u001B[0m\u001B[0mkwargs\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 974\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 975\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m_tables_used\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/joint_tour_destination.py\u001B[0m in \u001B[0;36mjoint_tour_destination\u001B[0;34m(tours, persons_merged, households_merged, network_los, chunk_size, trace_hh_id)\u001B[0m\n\u001B[1;32m 66\u001B[0m \u001B[0mestimator\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mwrite_model_settings\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodel_settings\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmodel_settings_file_name\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 67\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 68\u001B[0;31m choices_df, save_sample_df = tour_destination.run_tour_destination(\n\u001B[0m\u001B[1;32m 69\u001B[0m \u001B[0mtours\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 70\u001B[0m \u001B[0mpersons_merged\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/util/tour_destination.py\u001B[0m in \u001B[0;36mrun_tour_destination\u001B[0;34m(tours, persons_merged, want_logsums, want_sample_table, model_settings, network_los, estimator, chunk_size, trace_hh_id, trace_label)\u001B[0m\n\u001B[1;32m 725\u001B[0m \u001B[0mspec_segment_name\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0msegment_name\u001B[0m \u001B[0;31m# spec_segment_name is segment_name\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 726\u001B[0m \u001B[0mchoices\u001B[0m \u001B[0;34m=\u001B[0m\u001B[0;31m \u001B[0m\u001B[0;31m\\\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 727\u001B[0;31m run_destination_simulate(\n\u001B[0m\u001B[1;32m 728\u001B[0m \u001B[0mspec_segment_name\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 729\u001B[0m \u001B[0mchoosers\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;31mTypeError\u001B[0m: run_destination_simulate() got an unexpected keyword argument 'choose_individual_max_utility'" + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/initialize.py\u001B[0m in \u001B[0;36minitialize_landuse\u001B[0;34m()\u001B[0m\n\u001B[1;32m 98\u001B[0m \u001B[0mtrace_label\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;34m'initialize_landuse'\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 99\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 100\u001B[0;31m \u001B[0;32mwith\u001B[0m \u001B[0mchunk\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchunk_log\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtrace_label\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mbase\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 101\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 102\u001B[0m \u001B[0mmodel_settings\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mread_model_settings\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'initialize_landuse.yaml'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmandatory\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m~/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py\u001B[0m in \u001B[0;36m__enter__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 111\u001B[0m \u001B[0;32mdel\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mkwds\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfunc\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 112\u001B[0m \u001B[0;32mtry\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 113\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mnext\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgen\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 114\u001B[0m \u001B[0;32mexcept\u001B[0m \u001B[0mStopIteration\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 115\u001B[0m \u001B[0;32mraise\u001B[0m \u001B[0mRuntimeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"generator didn't yield\"\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mfrom\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/chunk.py\u001B[0m in \u001B[0;36mchunk_log\u001B[0;34m(trace_label, chunk_tag, base)\u001B[0m\n\u001B[1;32m 927\u001B[0m \u001B[0;31m# avoids breaking the assertion below.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 928\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 929\u001B[0;31m \u001B[0;32massert\u001B[0m \u001B[0mbase\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;34m(\u001B[0m\u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mCHUNK_SIZERS\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 930\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 931\u001B[0m \u001B[0mtrace_label\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;34mf\"{trace_label}.chunk_log\"\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mAssertionError\u001B[0m: " ] } ], From 9685aca3ef75019d4ee75acdbf3383bc4b0e9397 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 15:06:50 +1000 Subject: [PATCH 051/298] make place of call consistent --- activitysim/abm/models/trip_mode_choice.py | 5 ++++- activitysim/abm/models/util/mode.py | 9 ++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/activitysim/abm/models/trip_mode_choice.py b/activitysim/abm/models/trip_mode_choice.py index 59e9fc5e5e..9c90e9d8ff 100644 --- a/activitysim/abm/models/trip_mode_choice.py +++ b/activitysim/abm/models/trip_mode_choice.py @@ -194,7 +194,8 @@ def trip_mode_choice( logsum_column_name=logsum_column_name, trace_label=segment_trace_label, trace_choice_name='trip_mode_choice', - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) if trace_hh_id: # trace the coefficients @@ -244,6 +245,8 @@ def trip_mode_choice( tracing.print_summary('trip_mode_choice choices', trips_df[mode_column_name], value_counts=True) + print(mode_column_name) + assert not trips_df[mode_column_name].isnull().any() pipeline.replace_table("trips", trips_df) diff --git a/activitysim/abm/models/util/mode.py b/activitysim/abm/models/util/mode.py index c13b9cc66b..ec3f39dd8f 100644 --- a/activitysim/abm/models/util/mode.py +++ b/activitysim/abm/models/util/mode.py @@ -25,7 +25,8 @@ def mode_choice_simulate( trace_label, trace_choice_name, trace_column_names=None, - estimator=None): + estimator=None, + choose_individual_max_utility=False): """ common method for both tour_mode_choice and trip_mode_choice @@ -61,7 +62,7 @@ def mode_choice_simulate( trace_choice_name=trace_choice_name, estimator=estimator, trace_column_names=trace_column_names, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + choose_individual_max_utility=choose_individual_max_utility) # for consistency, always return dataframe, whether or not logsums were requested if isinstance(choices, pd.Series): @@ -145,6 +146,8 @@ def run_tour_mode_choice_simulate( trace_label=trace_label, trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, - estimator=estimator) + estimator=estimator, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) return choices From 19d1222b07f962680ce5838b0350a4744d9516c9 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 15:22:54 +1000 Subject: [PATCH 052/298] remove debug print statement --- activitysim/abm/models/trip_mode_choice.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/activitysim/abm/models/trip_mode_choice.py b/activitysim/abm/models/trip_mode_choice.py index 9c90e9d8ff..623c4b34df 100644 --- a/activitysim/abm/models/trip_mode_choice.py +++ b/activitysim/abm/models/trip_mode_choice.py @@ -245,8 +245,6 @@ def trip_mode_choice( tracing.print_summary('trip_mode_choice choices', trips_df[mode_column_name], value_counts=True) - print(mode_column_name) - assert not trips_df[mode_column_name].isnull().any() pipeline.replace_table("trips", trips_df) From 61a314f84ffc66600f7f50ecbafe1d6b1a5630ca Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 15:30:07 +1000 Subject: [PATCH 053/298] move reporting in make choices --- activitysim/core/logit.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 516add8aa9..b8a2eca3f1 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -297,12 +297,6 @@ def make_choices(probs, utilities=None, nest_spec=None, trace_label=None, trace_ """ trace_label = tracing.extend_trace_label(trace_label, 'make_choices') - if choose_individual_max_utility: - choices = make_choices_ru_frozen(utilities, nest_spec, trace_label) - # TODO: rands - rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) - return choices, rands - # probs should sum to 1 across each row BAD_PROB_THRESHOLD = 0.001 bad_probs = \ @@ -316,6 +310,12 @@ def make_choices(probs, utilities=None, nest_spec=None, trace_label=None, trace_ msg="probabilities do not add up to 1", trace_choosers=trace_choosers) + if choose_individual_max_utility: + choices = make_choices_ru_frozen(utilities, nest_spec, trace_label) + # TODO: rands - log all zeros for now + rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) + return choices, rands + rands = pipeline.get_rn_generator().random_for_df(probs) probs_arr = probs.values.cumsum(axis=1) - rands From 8c167658bfc7cc9650271c5b1bb86a5a6f83c2d9 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 15:56:29 +1000 Subject: [PATCH 054/298] make choice indexing for nested logit consistent with mnl and cumsum MC --- activitysim/core/logit.py | 18 ++++++++++++------ notebooks/fru_utils.py | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index b8a2eca3f1..ad24b464be 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -228,7 +228,7 @@ def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alter # alternatives and set the corresponding entry to 1 for each row, set all other alternatives at this level to zero. # Once the tree is walked (all alternatives have been processed), take the product of the alternatives in each # leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. -def make_choices_ru_frozen_nl(nested_utilities, nest_spec): +def make_choices_ru_frozen_nl(nested_utilities, alt_order_array, nest_spec): """ walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" nest_utils_for_choice = add_ev1_random(nested_utilities) @@ -243,8 +243,11 @@ def make_choices_ru_frozen_nl(nested_utilities, nest_spec): assert not choices.isnull().any(), "No choice for XXX - implement reporting" choices = pd.Series(choices, index=nest_utils_for_choice.index) - # TODO [janzill Jun2022]: REMOVE HACK, make this numpy and positional indexes from the beginning - choices = choices.map({v: k for k,v in enumerate(nest_utils_for_choice.columns)}) + # In order for choice indexing to be consistent with MNL and cumsum MC choices, we need to index in the order + # alternatives were originally created before adding nest nodes that are not elemental alternatives + choices = choices.map({v: k for k, v in enumerate(alt_order_array)}) + ## the following is wrong, the order might be changed + #choices = choices.map({v: k for k, v in enumerate(nest_utils_for_choice.columns)}) return choices @@ -259,12 +262,13 @@ def make_choices_ru_frozen_mnl(utilities): choices = pd.Series(choices, index=utilities_incl_unobs.index) return choices -def make_choices_ru_frozen(utilities, nest_spec=None, trace_label=None): +def make_choices_ru_frozen(utilities, alt_order_array, nest_spec=None, trace_label=None): trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen_mnl') if nest_spec is None: + assert (alt_order_array == utilities.columns.values).all() choices = make_choices_ru_frozen_mnl(utilities) else: - choices = make_choices_ru_frozen_nl(utilities, nest_spec) + choices = make_choices_ru_frozen_nl(utilities, alt_order_array, nest_spec) return choices @@ -311,7 +315,9 @@ def make_choices(probs, utilities=None, nest_spec=None, trace_label=None, trace_ trace_choosers=trace_choosers) if choose_individual_max_utility: - choices = make_choices_ru_frozen(utilities, nest_spec, trace_label) + # TODO: index of choices for nested utilities is different than unnested - this needs to be consistent for + # turning indexes into alternative names; use probs.columns for now. + choices = make_choices_ru_frozen(utilities, probs.columns.values, nest_spec, trace_label) # TODO: rands - log all zeros for now rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) return choices, rands diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py index d12b1cc7ee..77a4e920c2 100644 --- a/notebooks/fru_utils.py +++ b/notebooks/fru_utils.py @@ -554,7 +554,6 @@ def get_stuff(do_these_purposes=None): nested_probs_list.append(nested_probs) base_probs_list.append(base_probs) - choices_df_asim = pd.concat(choices_list) # update trips table with choices (and potionally logssums) @@ -579,4 +578,5 @@ def get_stuff(do_these_purposes=None): print("Done") - return trips_df, raw_util_list, nest_list, nu_list, nest_spec_list, nested_probs_list, base_probs_list \ No newline at end of file + return trips_df, raw_util_list, nest_list, nu_list, nest_spec_list, nested_probs_list, base_probs_list, \ + dict(list(zip(list(range(len(alts))), alts))) \ No newline at end of file From 087ec0ebfcd25c4dc4425b87161993dcb2deb638 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 16:20:34 +1000 Subject: [PATCH 055/298] use fru in models that use logit.make_choices w/o simulate wrapper --- activitysim/abm/models/joint_tour_participation.py | 4 +++- activitysim/abm/models/non_mandatory_tour_frequency.py | 4 +++- activitysim/abm/models/trip_departure_choice.py | 3 ++- activitysim/abm/models/trip_purpose.py | 4 +++- activitysim/abm/models/util/cdap.py | 10 ++++++++-- .../abm/models/util/probabilistic_scheduling.py | 5 ++++- activitysim/core/pathbuilder.py | 5 ++++- 7 files changed, 27 insertions(+), 8 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 23d13ac9e5..898d87bc48 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -175,7 +175,9 @@ def participants_chooser(probs, choosers, spec, trace_label): print(unsatisfied_candidates.head(20)) assert False - choices, rands = logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers) + choices, rands = logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers, + choose_individual_max_utility=config.setting( + "freeze_unobserved_utilities", False)) participate = (choices == PARTICIPATE_CHOICE) # satisfaction indexed by tour_id diff --git a/activitysim/abm/models/non_mandatory_tour_frequency.py b/activitysim/abm/models/non_mandatory_tour_frequency.py index 0cf58e4618..fb45c13513 100644 --- a/activitysim/abm/models/non_mandatory_tour_frequency.py +++ b/activitysim/abm/models/non_mandatory_tour_frequency.py @@ -111,7 +111,9 @@ def extend_tour_counts(persons, tour_counts, alternatives, trace_hh_id, trace_la choices, rands = logit.make_choices( choosers[PROBABILITY_COLUMNS], trace_label=tour_type_trace_label, - trace_choosers=choosers) + trace_choosers=choosers, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) # - extend tour_count (0-based prob alternative choice equals magnitude of extension) if choices.any(): diff --git a/activitysim/abm/models/trip_departure_choice.py b/activitysim/abm/models/trip_departure_choice.py index 80eb9b912a..cfff604ffe 100644 --- a/activitysim/abm/models/trip_departure_choice.py +++ b/activitysim/abm/models/trip_departure_choice.py @@ -286,7 +286,8 @@ def choose_tour_leg_pattern(trip_segment, # positions is series with the chosen alternative represented as a column index in probs # which is an integer between zero and num alternatives in the alternative sample positions, rands = \ - logit.make_choices(probs, trace_label=trace_label, trace_choosers=trip_segment) + logit.make_choices(probs, trace_label=trace_label, trace_choosers=trip_segment, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) chunk.log_df(trace_label, 'positions', positions) chunk.log_df(trace_label, 'rands', rands) diff --git a/activitysim/abm/models/trip_purpose.py b/activitysim/abm/models/trip_purpose.py index f0787ccf55..7838ac0149 100644 --- a/activitysim/abm/models/trip_purpose.py +++ b/activitysim/abm/models/trip_purpose.py @@ -108,7 +108,9 @@ def choose_intermediate_trip_purpose( choices, rands = logit.make_choices( choosers[purpose_cols], - trace_label=trace_label, trace_choosers=choosers) + trace_label=trace_label, trace_choosers=choosers, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) if have_trace_targets: tracing.trace_df(choices, '%s.choices' % trace_label, columns=[None, 'trip_purpose']) diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index fdc34961ae..1146f9732c 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -640,7 +640,10 @@ def household_activity_choices(indiv_utils, interaction_coefficients, hhsize, # select an activity pattern alternative for each household based on probability # result is a series indexed on _hh_index_ with the (0 based) index of the column from probs - idx_choices, rands = logit.make_choices(probs, trace_label=trace_label) + idx_choices, rands = logit.make_choices(probs, trace_label=trace_label, + choose_individual_max_utility=config.setting( + "freeze_unobserved_utilities", False) + ) # convert choice expressed as index into alternative name from util column label choices = pd.Series(utils.columns[idx_choices].values, index=utils.index) @@ -762,7 +765,10 @@ def extra_hh_member_choices(persons, cdap_fixed_relative_proportions, locals_d, # select an activity pattern alternative for each person based on probability # idx_choices is a series (indexed on _persons_index_ ) with the chosen alternative represented # as the integer (0 based) index of the chosen column from probs - idx_choices, rands = logit.make_choices(probs, trace_label=trace_label) + idx_choices, rands = logit.make_choices(probs, trace_label=trace_label, + choose_individual_max_utility=config.setting( + "freeze_unobserved_utilities", False) + ) # convert choice from column index to activity name choices = pd.Series(probs.columns[idx_choices].values, index=probs.index) diff --git a/activitysim/abm/models/util/probabilistic_scheduling.py b/activitysim/abm/models/util/probabilistic_scheduling.py index 73500f58e8..94675bccd3 100644 --- a/activitysim/abm/models/util/probabilistic_scheduling.py +++ b/activitysim/abm/models/util/probabilistic_scheduling.py @@ -248,7 +248,10 @@ def make_scheduling_choices( if trace_hh_id and tracing.has_trace_targets(choosers_df): tracing.trace_df(chooser_probs, '%s.chooser_probs' % trace_label) - raw_choices, rands = logit.make_choices(chooser_probs, trace_label=trace_label, trace_choosers=choosers) + raw_choices, rands = logit.make_choices(chooser_probs, trace_label=trace_label, trace_choosers=choosers, + choose_individual_max_utility=config.setting( + "freeze_unobserved_utilities", False) + ) chunk.log_df(trace_label, "choices", raw_choices) chunk.log_df(trace_label, "rands", rands) diff --git a/activitysim/core/pathbuilder.py b/activitysim/core/pathbuilder.py index d4cee77504..2cc4862d42 100644 --- a/activitysim/core/pathbuilder.py +++ b/activitysim/core/pathbuilder.py @@ -744,7 +744,10 @@ def build_virtual_path(self, recipe, path_type, orig, dest, tod, demographic_seg self.trace_df(probs, trace_label, 'probs') else: - choices, rands = logit.make_choices(probs, allow_bad_probs=True, trace_label=trace_label) + choices, rands = logit.make_choices(probs, allow_bad_probs=True, trace_label=trace_label, + choose_individual_max_utility=config.setting( + "freeze_unobserved_utilities", False) + ) chunk.log_df(trace_label, "rands", rands) del rands From c26c80c08985b547bd8adb8c89473c7218398470 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 19 Jun 2022 16:58:25 +1000 Subject: [PATCH 056/298] no fru for probabilitic lookup tables --- activitysim/abm/models/joint_tour_participation.py | 9 +++++---- .../abm/models/non_mandatory_tour_frequency.py | 5 +++-- activitysim/abm/models/trip_departure_choice.py | 8 ++++---- activitysim/abm/models/trip_purpose.py | 5 +++-- activitysim/abm/models/util/cdap.py | 13 +++++-------- .../abm/models/util/probabilistic_scheduling.py | 5 +---- activitysim/core/pathbuilder.py | 8 ++++---- activitysim/core/simulate.py | 4 +++- 8 files changed, 28 insertions(+), 29 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 898d87bc48..24f7295042 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -108,7 +108,7 @@ def get_tour_satisfaction(candidates, participate): return satisfaction -def participants_chooser(probs, choosers, spec, trace_label): +def participants_chooser(probs, choosers, spec, trace_label, utilities): """ custom alternative to logit.make_choices for simulate.simple_simulate @@ -175,9 +175,10 @@ def participants_chooser(probs, choosers, spec, trace_label): print(unsatisfied_candidates.head(20)) assert False - choices, rands = logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers, - choose_individual_max_utility=config.setting( - "freeze_unobserved_utilities", False)) + choices, rands = logit.make_choices( + probs, utilities=utilities, trace_label=trace_label, trace_choosers=choosers, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) participate = (choices == PARTICIPATE_CHOICE) # satisfaction indexed by tour_id diff --git a/activitysim/abm/models/non_mandatory_tour_frequency.py b/activitysim/abm/models/non_mandatory_tour_frequency.py index fb45c13513..5816d3349d 100644 --- a/activitysim/abm/models/non_mandatory_tour_frequency.py +++ b/activitysim/abm/models/non_mandatory_tour_frequency.py @@ -111,8 +111,9 @@ def extend_tour_counts(persons, tour_counts, alternatives, trace_hh_id, trace_la choices, rands = logit.make_choices( choosers[PROBABILITY_COLUMNS], trace_label=tour_type_trace_label, - trace_choosers=choosers, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + trace_choosers=choosers + # [janzill Jun2022]: don't change for probs tables + #choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) ) # - extend tour_count (0-based prob alternative choice equals magnitude of extension) diff --git a/activitysim/abm/models/trip_departure_choice.py b/activitysim/abm/models/trip_departure_choice.py index cfff604ffe..37b3b48223 100644 --- a/activitysim/abm/models/trip_departure_choice.py +++ b/activitysim/abm/models/trip_departure_choice.py @@ -275,9 +275,6 @@ def choose_tour_leg_pattern(trip_segment, chunk.log_df(trace_label, 'probs', probs) - del utilities_df - chunk.log_df(trace_label, 'utilities_df', None) - if have_trace_targets: tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), column_labels=['alternative', 'probability']) @@ -286,12 +283,15 @@ def choose_tour_leg_pattern(trip_segment, # positions is series with the chosen alternative represented as a column index in probs # which is an integer between zero and num alternatives in the alternative sample positions, rands = \ - logit.make_choices(probs, trace_label=trace_label, trace_choosers=trip_segment, + logit.make_choices(probs, utilities=utilities_df, trace_label=trace_label, trace_choosers=trip_segment, choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) chunk.log_df(trace_label, 'positions', positions) chunk.log_df(trace_label, 'rands', rands) + del utilities_df + chunk.log_df(trace_label, 'utilities_df', None) + del probs chunk.log_df(trace_label, 'probs', None) diff --git a/activitysim/abm/models/trip_purpose.py b/activitysim/abm/models/trip_purpose.py index 7838ac0149..1058e0685f 100644 --- a/activitysim/abm/models/trip_purpose.py +++ b/activitysim/abm/models/trip_purpose.py @@ -108,8 +108,9 @@ def choose_intermediate_trip_purpose( choices, rands = logit.make_choices( choosers[purpose_cols], - trace_label=trace_label, trace_choosers=choosers, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + trace_label=trace_label, trace_choosers=choosers + # [janzill Jun2022]: don't change for probs tables + #choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) ) if have_trace_targets: diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index 1146f9732c..3af60c183d 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -640,10 +640,10 @@ def household_activity_choices(indiv_utils, interaction_coefficients, hhsize, # select an activity pattern alternative for each household based on probability # result is a series indexed on _hh_index_ with the (0 based) index of the column from probs - idx_choices, rands = logit.make_choices(probs, trace_label=trace_label, - choose_individual_max_utility=config.setting( - "freeze_unobserved_utilities", False) - ) + idx_choices, rands = logit.make_choices( + probs, utilities=utils, trace_label=trace_label, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) # convert choice expressed as index into alternative name from util column label choices = pd.Series(utils.columns[idx_choices].values, index=utils.index) @@ -765,10 +765,7 @@ def extra_hh_member_choices(persons, cdap_fixed_relative_proportions, locals_d, # select an activity pattern alternative for each person based on probability # idx_choices is a series (indexed on _persons_index_ ) with the chosen alternative represented # as the integer (0 based) index of the chosen column from probs - idx_choices, rands = logit.make_choices(probs, trace_label=trace_label, - choose_individual_max_utility=config.setting( - "freeze_unobserved_utilities", False) - ) + idx_choices, rands = logit.make_choices(probs, trace_label=trace_label) # no fru for prob tables # convert choice from column index to activity name choices = pd.Series(probs.columns[idx_choices].values, index=probs.index) diff --git a/activitysim/abm/models/util/probabilistic_scheduling.py b/activitysim/abm/models/util/probabilistic_scheduling.py index 94675bccd3..cc5869ba57 100644 --- a/activitysim/abm/models/util/probabilistic_scheduling.py +++ b/activitysim/abm/models/util/probabilistic_scheduling.py @@ -248,10 +248,7 @@ def make_scheduling_choices( if trace_hh_id and tracing.has_trace_targets(choosers_df): tracing.trace_df(chooser_probs, '%s.chooser_probs' % trace_label) - raw_choices, rands = logit.make_choices(chooser_probs, trace_label=trace_label, trace_choosers=choosers, - choose_individual_max_utility=config.setting( - "freeze_unobserved_utilities", False) - ) + raw_choices, rands = logit.make_choices(chooser_probs, trace_label=trace_label, trace_choosers=choosers) # no fru chunk.log_df(trace_label, "choices", raw_choices) chunk.log_df(trace_label, "rands", rands) diff --git a/activitysim/core/pathbuilder.py b/activitysim/core/pathbuilder.py index 2cc4862d42..28d0707575 100644 --- a/activitysim/core/pathbuilder.py +++ b/activitysim/core/pathbuilder.py @@ -744,10 +744,10 @@ def build_virtual_path(self, recipe, path_type, orig, dest, tod, demographic_seg self.trace_df(probs, trace_label, 'probs') else: - choices, rands = logit.make_choices(probs, allow_bad_probs=True, trace_label=trace_label, - choose_individual_max_utility=config.setting( - "freeze_unobserved_utilities", False) - ) + choices, rands = logit.make_choices( + probs, utilities=utilities_df, allow_bad_probs=True, trace_label=trace_label, + choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + ) chunk.log_df(trace_label, "rands", rands) del rands diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 0162d20616..039182decd 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -965,8 +965,10 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, column_labels=['alternative', 'probability']) if custom_chooser: + # TODO [janzill Jun2022]: is this used somewhere outside Asim such that adding utilities would be a breaking + # change? choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, - trace_label=trace_label) + trace_label=trace_label, utilities=utilities) else: choices, rands = logit.make_choices(probs, utilities=utilities, trace_label=trace_label, choose_individual_max_utility=choose_individual_max_utility) From d14fac389e298e4334fdecd41a14b7642856fc01 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 14:34:40 +1000 Subject: [PATCH 057/298] memory saving work - delete dfs wherever possible --- .../abm/models/joint_tour_participation.py | 12 +- activitysim/core/logit.py | 38 +-- activitysim/core/simulate.py | 220 ++++++++++-------- 3 files changed, 146 insertions(+), 124 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 24f7295042..af3a32e543 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -108,7 +108,7 @@ def get_tour_satisfaction(candidates, participate): return satisfaction -def participants_chooser(probs, choosers, spec, trace_label, utilities): +def participants_chooser(probs, choosers, spec, trace_label): """ custom alternative to logit.make_choices for simulate.simple_simulate @@ -175,10 +175,7 @@ def participants_chooser(probs, choosers, spec, trace_label, utilities): print(unsatisfied_candidates.head(20)) assert False - choices, rands = logit.make_choices( - probs, utilities=utilities, trace_label=trace_label, trace_choosers=choosers, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) - ) + choices, rands = logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers) participate = (choices == PARTICIPATE_CHOICE) # satisfaction indexed by tour_id @@ -317,8 +314,7 @@ def joint_tour_participation( trace_label=trace_label, trace_choice_name='participation', custom_chooser=participants_chooser, - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) # choice is boolean (participate or not) choice_col = model_settings.get('participation_choice', 'participate') @@ -383,4 +379,4 @@ def joint_tour_participation( label="joint_tour_participation.participants") tracing.trace_df(joint_tours, - label="joint_tour_participation.joint_tours") + label="joint_tour_participation.joint_tours") \ No newline at end of file diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index ad24b464be..1fe6065fd4 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -272,36 +272,52 @@ def make_choices_ru_frozen(utilities, alt_order_array, nest_spec=None, trace_lab return choices -def make_choices(probs, utilities=None, nest_spec=None, trace_label=None, trace_choosers=None, allow_bad_probs=False, - choose_individual_max_utility=False): +# TODO: memory usage +def make_choices_utility_based( + utilities, + # for nested: need mapping of index to alternative name to "fake" indexes if I want to keep with current + # structure, OR need to make returning names optional. sharrow impl will make our life so much easier + name_mapping=None, + nest_spec=None, + trace_label=None, + trace_choosers=None, + allow_bad_probs=False, +): + trace_label = tracing.extend_trace_label(trace_label, 'make_choices_utility_based') + + # TODO: index of choices for nested utilities is different than unnested - this needs to be consistent for + # turning indexes into alternative names to keep code changes to minimum for now + choices = make_choices_ru_frozen(utilities, name_mapping, nest_spec, trace_label) + # TODO: rands - log all zeros for now + rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) + return choices, rands + + +def make_choices(probs, trace_label=None, trace_choosers=None, allow_bad_probs=False): """ Make choices for each chooser from among a set of alternatives. - Parameters ---------- probs : pandas.DataFrame Rows for choosers and columns for the alternatives from which they are choosing. Values are expected to be valid probabilities across each row, e.g. they should sum to 1. - trace_choosers : pandas.dataframe the choosers df (for interaction_simulate) to facilitate the reporting of hh_id by report_bad_choices because it can't deduce hh_id from the interaction_dataset which is indexed on index values from alternatives df - Returns ------- choices : pandas.Series Maps chooser IDs (from `probs` index) to a choice, where the choice is an index into the columns of `probs`. - rands : pandas.Series The random numbers used to make the choices (for debugging, tracing) - """ trace_label = tracing.extend_trace_label(trace_label, 'make_choices') # probs should sum to 1 across each row + BAD_PROB_THRESHOLD = 0.001 bad_probs = \ probs.sum(axis=1).sub(np.ones(len(probs.index))).abs() \ @@ -314,14 +330,6 @@ def make_choices(probs, utilities=None, nest_spec=None, trace_label=None, trace_ msg="probabilities do not add up to 1", trace_choosers=trace_choosers) - if choose_individual_max_utility: - # TODO: index of choices for nested utilities is different than unnested - this needs to be consistent for - # turning indexes into alternative names; use probs.columns for now. - choices = make_choices_ru_frozen(utilities, probs.columns.values, nest_spec, trace_label) - # TODO: rands - log all zeros for now - rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) - return choices, rands - rands = pipeline.get_rn_generator().random_for_df(probs) probs_arr = probs.values.cumsum(axis=1) - rands diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 039182decd..434c7216f1 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -887,8 +887,7 @@ def compute_base_probabilities(nested_probabilities, nests, spec): def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, log_alt_losers=False, want_logsums=False, trace_label=None, - trace_choice_name=None, trace_column_names=None, - choose_individual_max_utility=False): + trace_choice_name=None, trace_column_names=None): """ Run a simulation for when the model spec does not involve alternative specific data, e.g. there are no interactions with alternative @@ -952,32 +951,37 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, tracing.trace_df(utilities, '%s.utilities' % trace_label, column_labels=['alternative', 'utility']) - #if choose_individual_max_utility: - # choices = logit.make_choices_ru_frozen_mnl(utilities, trace_label=trace_label) - # rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up - #else: - probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) - chunk.log_df(trace_label, "probs", probs) + if config.setting("freeze_unobserved_utilities", False): + if custom_chooser: + # TODO JAN: ADD HERE - need to figure out what to do with custom chooser though. leave out for now, + # we only need to fix up joit_tour_participation for now + raise "Not implemented yet" + + choices = logit.make_choices_utility_based(utilities, trace_label=trace_label) + rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up + + del utilities + chunk.log_df(trace_label, 'utilities', None) - if have_trace_targets: - # report these now in case make_choices throws error on bad_choices - tracing.trace_df(probs, '%s.probs' % trace_label, - column_labels=['alternative', 'probability']) - - if custom_chooser: - # TODO [janzill Jun2022]: is this used somewhere outside Asim such that adding utilities would be a breaking - # change? - choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, - trace_label=trace_label, utilities=utilities) else: - choices, rands = logit.make_choices(probs, utilities=utilities, trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) + chunk.log_df(trace_label, "probs", probs) - del utilities - chunk.log_df(trace_label, 'utilities', None) + if have_trace_targets: + # report these now in case make_choices throws error on bad_choices + tracing.trace_df(probs, '%s.probs' % trace_label, + column_labels=['alternative', 'probability']) + + del utilities + chunk.log_df(trace_label, 'utilities', None) + + if custom_chooser: + choices, rands = custom_chooser(probs=probs, choosers=choosers, spec=spec, trace_label=trace_label) + else: + choices, rands = logit.make_choices(probs, trace_label=trace_label) - del probs - chunk.log_df(trace_label, 'probs', None) + del probs + chunk.log_df(trace_label, 'probs', None) if have_trace_targets: tracing.trace_df(choices, '%s.choices' % trace_label, @@ -1189,75 +1193,106 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label, column_labels=['alternative', 'utility']) - # exponentiated utilities of leaves and nests - nested_exp_utilities = compute_nested_exp_utilities(raw_utilities, nest_spec) - chunk.log_df(trace_label, "nested_exp_utilities", nested_exp_utilities) - # TODO [janzill Jun2022: combine with nested_exp_utilities somehow?] - # utilities of leaves and nests - nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) - chunk.log_df(trace_label, "nested_utilities", nested_utilities) + if config.setting("freeze_unobserved_utilities", False): + if custom_chooser: + # TODO JAN: ADD HERE - need to figure out what to do with custom chooser though. leave out for now, + # we only need to fix up joit_tour_participation for now + raise "Not implemented yet" - del raw_utilities - chunk.log_df(trace_label, 'raw_utilities', None) + # TODO [janzill Jun2022]: combine with nested_exp_utilities? + # utilities of leaves and nests + nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) + chunk.log_df(trace_label, "nested_utilities", nested_utilities) - if have_trace_targets: - tracing.trace_df(nested_exp_utilities, '%s.nested_exp_utilities' % trace_label, - column_labels=['alternative', 'utility']) + # TODO [janzill Jun2022]: this can be done from utils directly, but use existing methodology for prototype + if want_logsums: + # logsum of nest root + # exponentiated utilities of leaves and nests + nested_exp_utilities = compute_nested_exp_utilities(raw_utilities, nest_spec) + chunk.log_df(trace_label, "nested_exp_utilities", nested_exp_utilities) + logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) + chunk.log_df(trace_label, "logsums", logsums) + + # TODO: index of choices for nested utilities is different than unnested - this needs to be consistent for + # turning indexes into alternative names to keep code changes to minimum for now + name_mapping = raw_utilities.columns.values + + del raw_utilities + chunk.log_df(trace_label, 'raw_utilities', None) + + choices = logit.make_choices_utility_based( + nested_utilities, + name_mapping=name_mapping, + nest_spec=nest_spec, + trace_label=trace_label + ) + rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up + + del(nested_utilities) + chunk.log_df(trace_label, "nested_utilities", None) - # probabilities of alternatives relative to siblings sharing the same nest - nested_probabilities = \ - compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label) - chunk.log_df(trace_label, "nested_probabilities", nested_probabilities) + else: + # exponentiated utilities of leaves and nests + nested_exp_utilities = compute_nested_exp_utilities(raw_utilities, nest_spec) + chunk.log_df(trace_label, "nested_exp_utilities", nested_exp_utilities) - if want_logsums: - # logsum of nest root - logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) - chunk.log_df(trace_label, "logsums", logsums) + del raw_utilities + chunk.log_df(trace_label, 'raw_utilities', None) - del nested_exp_utilities - chunk.log_df(trace_label, 'nested_exp_utilities', None) + if have_trace_targets: + tracing.trace_df(nested_exp_utilities, '%s.nested_exp_utilities' % trace_label, + column_labels=['alternative', 'utility']) - if have_trace_targets: - tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label, - column_labels=['alternative', 'probability']) + # probabilities of alternatives relative to siblings sharing the same nest + nested_probabilities = \ + compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label) + chunk.log_df(trace_label, "nested_probabilities", nested_probabilities) + + if want_logsums: + # logsum of nest root + logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) + chunk.log_df(trace_label, "logsums", logsums) - # global (flattened) leaf probabilities based on relative nest coefficients (in spec order) - base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) - chunk.log_df(trace_label, "base_probabilities", base_probabilities) + del nested_exp_utilities + chunk.log_df(trace_label, 'nested_exp_utilities', None) - del nested_probabilities - chunk.log_df(trace_label, 'nested_probabilities', None) + if have_trace_targets: + tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label, + column_labels=['alternative', 'probability']) - if have_trace_targets: - tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label, - column_labels=['alternative', 'probability']) + # global (flattened) leaf probabilities based on relative nest coefficients (in spec order) + base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) + chunk.log_df(trace_label, "base_probabilities", base_probabilities) - # note base_probabilities could all be zero since we allowed all probs for nests to be zero - # check here to print a clear message but make_choices will raise error if probs don't sum to 1 - BAD_PROB_THRESHOLD = 0.001 - no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD + del nested_probabilities + chunk.log_df(trace_label, 'nested_probabilities', None) - if no_choices.any(): + if have_trace_targets: + tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label, + column_labels=['alternative', 'probability']) - logit.report_bad_choices( - no_choices, base_probabilities, - trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'), - trace_choosers=choosers, - msg="base_probabilities do not sum to one") + # note base_probabilities could all be zero since we allowed all probs for nests to be zero + # check here to print a clear message but make_choices will raise error if probs don't sum to 1 + BAD_PROB_THRESHOLD = 0.001 + no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD - if custom_chooser: - choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, - trace_label=trace_label) - else: - choices, rands = logit.make_choices(base_probabilities, utilities=nested_utilities, nest_spec=nest_spec, - trace_label=trace_label, choose_individual_max_utility=choose_individual_max_utility) + if no_choices.any(): - del(nested_utilities) - chunk.log_df(trace_label, "nested_utilities", None) + logit.report_bad_choices( + no_choices, base_probabilities, + trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'), + trace_choosers=choosers, + msg="base_probabilities do not sum to one") - del base_probabilities - chunk.log_df(trace_label, 'base_probabilities', None) + if custom_chooser: + choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, + trace_label=trace_label) + else: + choices, rands = logit.make_choices(base_probabilities,nest_spec=nest_spec, trace_label=trace_label) + + del base_probabilities + chunk.log_df(trace_label, 'base_probabilities', None) if have_trace_targets: tracing.trace_df(choices, '%s.choices' % trace_label, @@ -1280,9 +1315,7 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, log_alt_losers=False, want_logsums=False, estimator=None, - trace_label=None, trace_choice_name=None, trace_column_names=None, - choose_individual_max_utility=False - ): + trace_label=None, trace_choice_name=None, trace_column_names=None): """ Run an MNL or NL simulation for when the model spec does not involve alternative specific data, e.g. there are no interactions with alternative @@ -1337,25 +1370,14 @@ def _simple_simulate(choosers, spec, nest_spec, skims=None, locals_d=None, want_logsums=want_logsums, estimator=estimator, trace_label=trace_label, - trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, - choose_individual_max_utility=choose_individual_max_utility) + trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) else: - ##make_choices now carries choose_individual_max_utility -> incorporate there - #if choose_individual_max_utility: - # choices = eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, - # log_alt_losers=log_alt_losers, - # want_logsums=want_logsums, - # estimator=estimator, - # trace_label=trace_label, - # trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) - #else: choices = eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, log_alt_losers=log_alt_losers, want_logsums=want_logsums, estimator=estimator, trace_label=trace_label, - trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, - choose_individual_max_utility=choose_individual_max_utility) + trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) return choices @@ -1378,8 +1400,7 @@ def simple_simulate(choosers, spec, nest_spec, log_alt_losers=False, want_logsums=False, estimator=None, - trace_label=None, trace_choice_name=None, trace_column_names=None, - choose_individual_max_utility=False): + trace_label=None, trace_choice_name=None, trace_column_names=None): """ Run an MNL or NL simulation for when the model spec does not involve alternative specific data, e.g. there are no interactions with alternative @@ -1405,8 +1426,7 @@ def simple_simulate(choosers, spec, nest_spec, estimator=estimator, trace_label=chunk_trace_label, trace_choice_name=trace_choice_name, - trace_column_names=trace_column_names, - choose_individual_max_utility=choose_individual_max_utility) + trace_column_names=trace_column_names) result_list.append(choices) @@ -1427,8 +1447,7 @@ def simple_simulate_by_chunk_id(choosers, spec, nest_spec, want_logsums=False, estimator=None, trace_label=None, - trace_choice_name=None, - choose_individual_max_utility=False): + trace_choice_name=None): """ chunk_by_chunk_id wrapper for simple_simulate """ @@ -1446,8 +1465,7 @@ def simple_simulate_by_chunk_id(choosers, spec, nest_spec, want_logsums=want_logsums, estimator=estimator, trace_label=chunk_trace_label, - trace_choice_name=trace_choice_name, - choose_individual_max_utility=choose_individual_max_utility) + trace_choice_name=trace_choice_name) result_list.append(choices) From f890799f8ae869acec3b82e18bd3737e6f3142f2 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 14:36:24 +1000 Subject: [PATCH 058/298] fake rand tracing --- activitysim/core/simulate.py | 146 +---------------------------------- 1 file changed, 2 insertions(+), 144 deletions(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 434c7216f1..183bea60cf 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -957,8 +957,7 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, # we only need to fix up joit_tour_participation for now raise "Not implemented yet" - choices = logit.make_choices_utility_based(utilities, trace_label=trace_label) - rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up + choices, rands = logit.make_choices_utility_based(utilities, trace_label=trace_label) del utilities chunk.log_df(trace_label, 'utilities', None) @@ -991,146 +990,6 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, return choices -# -# def eval_nl_fixed_ru(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, -# log_alt_losers=False, -# want_logsums=False, trace_label=None, -# trace_choice_name=None, trace_column_names=None): -# """ -# Run a nested-logit simulation for when the model spec does not involve alternative -# specific data, e.g. there are no interactions with alternative -# properties and no need to sample from alternatives. -# -# Parameters -# ---------- -# choosers : pandas.DataFrame -# spec : pandas.DataFrame -# A table of variable specifications and coefficient values. -# Variable expressions should be in the table index and the table -# should have a column for each alternative. -# nest_spec: -# dictionary specifying nesting structure and nesting coefficients -# (from the model spec yaml file) -# locals_d : Dict or None -# This is a dictionary of local variables that will be the environment -# for an evaluation of an expression that begins with @ -# custom_chooser : function(probs, choosers, spec, trace_label) returns choices, rands -# custom alternative to logit.make_choices -# estimator : Estimator object -# called to report intermediate table results (used for estimation) -# trace_label: str -# This is the label to be used for trace log file entries and dump file names -# when household tracing enabled. No tracing occurs if label is empty or None. -# trace_choice_name: str -# This is the column label to be used in trace file csv dump of choices -# trace_column_names: str or list of str -# chooser columns to include when tracing expression_values -# -# Returns -# ------- -# choices : pandas.Series -# Index will be that of `choosers`, values will match the columns -# of `spec`. -# """ -# -# trace_label = tracing.extend_trace_label(trace_label, 'eval_nl_fixed_ru') -# assert trace_label -# have_trace_targets = tracing.has_trace_targets(choosers) -# -# logit.validate_nest_spec(nest_spec, trace_label) -# -# if have_trace_targets: -# tracing.trace_df(choosers, '%s.choosers' % trace_label) -# -# raw_utilities = eval_utilities(spec, choosers, locals_d, -# log_alt_losers=log_alt_losers, -# trace_label=trace_label, have_trace_targets=have_trace_targets, -# estimator=estimator, trace_column_names=trace_column_names) -# chunk.log_df(trace_label, "raw_utilities", raw_utilities) -# -# if have_trace_targets: -# tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label, -# column_labels=['alternative', 'utility']) -# -# # utilities of leaves and nests -# nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) -# chunk.log_df(trace_label, "nested_utilities", nested_utilities) -# -# del raw_utilities -# chunk.log_df(trace_label, 'raw_utilities', None) -# -# if have_trace_targets: -# tracing.trace_df(nested_utilities, '%s.nested_utilities' % trace_label, -# column_labels=['alternative', 'utility']) -# -# -# ########### comparison only -# # probabilities of alternatives relative to siblings sharing the same nest -# nested_probabilities = \ -# compute_nested_probabilities(np.exp(nested_utilities), nest_spec, trace_label=trace_label) -# chunk.log_df(trace_label, "nested_probabilities", nested_probabilities) -# -# if have_trace_targets: -# tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label, -# column_labels=['alternative', 'probability']) -# -# base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) -# chunk.log_df(trace_label, "base_probabilities", base_probabilities) -# -# del nested_probabilities -# chunk.log_df(trace_label, "nested_probabilities", None) -# -# if have_trace_targets: -# tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label, -# column_labels=['alternative', 'probability']) -# -# del(base_probabilities) -# chunk.log_df(trace_label, "base_probabilities", None) -# ########### end comparison only -# -# # TODO - check this is correct -# if want_logsums: -# # logsum of nest root -# logsums = pd.Series(nested_utilities.root, index=choosers.index) -# chunk.log_df(trace_label, "logsums", logsums) -# -# # TODO: add checks on utilities? -# # # note base_probabilities could all be zero since we allowed all probs for nests to be zero -# # # check here to print a clear message but make_choices will raise error if probs don't sum to 1 -# # BAD_PROB_THRESHOLD = 0.001 -# # no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD -# # -# # if no_choices.any(): -# # -# # logit.report_bad_choices( -# # no_choices, base_probabilities, -# # trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'), -# # trace_choosers=choosers, -# # msg="base_probabilities do not sum to one") -# -# # TODO: add custom_chooser -# # if custom_chooser: -# # choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, -# # trace_label=trace_label) -# # else: -# -# choices = logit.make_choices_ru_frozen(nested_utilities, nest_spec, trace_label=trace_label) -# -# if have_trace_targets: -# tracing.trace_df(choices, '%s.choices' % trace_label, -# columns=[None, trace_choice_name]) -# #tracing.trace_df(rands, '%s.rands' % trace_label, -# # columns=[None, 'rand']) -# if want_logsums: -# tracing.trace_df(logsums, '%s.logsums' % trace_label, -# columns=[None, 'logsum']) -# -# if want_logsums: -# choices = choices.to_frame('choice') -# choices['logsum'] = logsums -# -# return choices - def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, log_alt_losers=False, @@ -1221,13 +1080,12 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, del raw_utilities chunk.log_df(trace_label, 'raw_utilities', None) - choices = logit.make_choices_utility_based( + choices, rands = logit.make_choices_utility_based( nested_utilities, name_mapping=name_mapping, nest_spec=nest_spec, trace_label=trace_label ) - rands = pd.Series([0, 0, 0]) # TODO: fix me, bring back up del(nested_utilities) chunk.log_df(trace_label, "nested_utilities", None) From 043a0bdf1b29cd4c46db9b40e9a30b5b7bb7a84d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 14:53:12 +1000 Subject: [PATCH 059/298] working through all make_choices calls to implement memory saving hack until sharrow arrives --- .../models/non_mandatory_tour_frequency.py | 2 - .../abm/models/trip_departure_choice.py | 49 +++++++++++-------- activitysim/abm/models/trip_purpose.py | 7 +-- activitysim/abm/models/util/cdap.py | 21 ++++---- .../models/util/probabilistic_scheduling.py | 2 +- activitysim/core/simulate.py | 2 +- 6 files changed, 43 insertions(+), 40 deletions(-) diff --git a/activitysim/abm/models/non_mandatory_tour_frequency.py b/activitysim/abm/models/non_mandatory_tour_frequency.py index 5816d3349d..e0dd4b92c1 100644 --- a/activitysim/abm/models/non_mandatory_tour_frequency.py +++ b/activitysim/abm/models/non_mandatory_tour_frequency.py @@ -112,8 +112,6 @@ def extend_tour_counts(persons, tour_counts, alternatives, trace_hh_id, trace_la choosers[PROBABILITY_COLUMNS], trace_label=tour_type_trace_label, trace_choosers=choosers - # [janzill Jun2022]: don't change for probs tables - #choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) ) # - extend tour_count (0-based prob alternative choice equals magnitude of extension) diff --git a/activitysim/abm/models/trip_departure_choice.py b/activitysim/abm/models/trip_departure_choice.py index 37b3b48223..f19f4241c3 100644 --- a/activitysim/abm/models/trip_departure_choice.py +++ b/activitysim/abm/models/trip_departure_choice.py @@ -268,32 +268,41 @@ def choose_tour_leg_pattern(trip_segment, tracing.trace_df(utilities_df, tracing.extend_trace_label(trace_label, 'utilities'), column_labels=['alternative', 'utility']) - # convert to probabilities (utilities exponentiated and normalized to probs) - # probs is same shape as utilities, one row per chooser and one column for alternative - probs = logit.utils_to_probs(utilities_df, - trace_label=trace_label, trace_choosers=trip_segment) + if config.setting("freeze_unobserved_utilities", False): + # make choices + # positions is series with the chosen alternative represented as a column index in probs + # which is an integer between zero and num alternatives in the alternative sample + positions, rands = logit.make_choices_utility_based( + utilities_df, trace_label=trace_label, trace_choosers=trip_segment + ) + + del utilities_df + chunk.log_df(trace_label, 'utilities_df', None) + else: + # convert to probabilities (utilities exponentiated and normalized to probs) + # probs is same shape as utilities, one row per chooser and one column for alternative + probs = logit.utils_to_probs(utilities_df, + trace_label=trace_label, trace_choosers=trip_segment) - chunk.log_df(trace_label, 'probs', probs) + chunk.log_df(trace_label, 'probs', probs) - if have_trace_targets: - tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), - column_labels=['alternative', 'probability']) + if have_trace_targets: + tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), + column_labels=['alternative', 'probability']) - # make choices - # positions is series with the chosen alternative represented as a column index in probs - # which is an integer between zero and num alternatives in the alternative sample - positions, rands = \ - logit.make_choices(probs, utilities=utilities_df, trace_label=trace_label, trace_choosers=trip_segment, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + del utilities_df + chunk.log_df(trace_label, 'utilities_df', None) - chunk.log_df(trace_label, 'positions', positions) - chunk.log_df(trace_label, 'rands', rands) + # make choices + # positions is series with the chosen alternative represented as a column index in probs + # which is an integer between zero and num alternatives in the alternative sample + positions, rands = logit.make_choices(probs, trace_label=trace_label, trace_choosers=trip_segment) - del utilities_df - chunk.log_df(trace_label, 'utilities_df', None) + del probs + chunk.log_df(trace_label, 'probs', None) - del probs - chunk.log_df(trace_label, 'probs', None) + chunk.log_df(trace_label, 'positions', positions) + chunk.log_df(trace_label, 'rands', rands) # shouldn't have chosen any of the dummy pad utilities assert positions.max() < max_sample_count diff --git a/activitysim/abm/models/trip_purpose.py b/activitysim/abm/models/trip_purpose.py index 1058e0685f..ab4dc28007 100644 --- a/activitysim/abm/models/trip_purpose.py +++ b/activitysim/abm/models/trip_purpose.py @@ -106,12 +106,7 @@ def choose_intermediate_trip_purpose( print(choosers[probs_cols]) estimator.write_table(choosers[probs_cols], 'probs', append=True) - choices, rands = logit.make_choices( - choosers[purpose_cols], - trace_label=trace_label, trace_choosers=choosers - # [janzill Jun2022]: don't change for probs tables - #choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) - ) + choices, rands = logit.make_choices(choosers[purpose_cols], trace_label=trace_label, trace_choosers=choosers) if have_trace_targets: tracing.trace_df(choices, '%s.choices' % trace_label, columns=[None, 'trip_purpose']) diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index 3af60c183d..d0bf565931 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -636,14 +636,14 @@ def household_activity_choices(indiv_utils, interaction_coefficients, hhsize, if len(utils.index) == 0: return pd.Series(dtype='float64') - probs = logit.utils_to_probs(utils, trace_label=trace_label) + if config.setting("freeze_unobserved_utilities", False): + idx_choices, rands = logit.make_choices(utils, trace_label=trace_label) + else: + probs = logit.utils_to_probs(utils, trace_label=trace_label) - # select an activity pattern alternative for each household based on probability - # result is a series indexed on _hh_index_ with the (0 based) index of the column from probs - idx_choices, rands = logit.make_choices( - probs, utilities=utils, trace_label=trace_label, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) - ) + # select an activity pattern alternative for each household based on probability + # result is a series indexed on _hh_index_ with the (0 based) index of the column from probs + idx_choices, rands = logit.make_choices(probs, trace_label=trace_label) # convert choice expressed as index into alternative name from util column label choices = pd.Series(utils.columns[idx_choices].values, index=utils.index) @@ -656,8 +656,9 @@ def household_activity_choices(indiv_utils, interaction_coefficients, hhsize, tracing.trace_df(utils, '%s.hhsize%d_utils' % (trace_label, hhsize), column_labels=['expression', 'household']) - tracing.trace_df(probs, '%s.hhsize%d_probs' % (trace_label, hhsize), - column_labels=['expression', 'household']) + if not config.setting("freeze_unobserved_utilities", False): + tracing.trace_df(probs, '%s.hhsize%d_probs' % (trace_label, hhsize), + column_labels=['expression', 'household']) tracing.trace_df(choices, '%s.hhsize%d_activity_choices' % (trace_label, hhsize), column_labels=['expression', 'household']) tracing.trace_df(rands, '%s.hhsize%d_rands' % (trace_label, hhsize), @@ -765,7 +766,7 @@ def extra_hh_member_choices(persons, cdap_fixed_relative_proportions, locals_d, # select an activity pattern alternative for each person based on probability # idx_choices is a series (indexed on _persons_index_ ) with the chosen alternative represented # as the integer (0 based) index of the chosen column from probs - idx_choices, rands = logit.make_choices(probs, trace_label=trace_label) # no fru for prob tables + idx_choices, rands = logit.make_choices(probs, trace_label=trace_label) # convert choice from column index to activity name choices = pd.Series(probs.columns[idx_choices].values, index=probs.index) diff --git a/activitysim/abm/models/util/probabilistic_scheduling.py b/activitysim/abm/models/util/probabilistic_scheduling.py index cc5869ba57..73500f58e8 100644 --- a/activitysim/abm/models/util/probabilistic_scheduling.py +++ b/activitysim/abm/models/util/probabilistic_scheduling.py @@ -248,7 +248,7 @@ def make_scheduling_choices( if trace_hh_id and tracing.has_trace_targets(choosers_df): tracing.trace_df(chooser_probs, '%s.chooser_probs' % trace_label) - raw_choices, rands = logit.make_choices(chooser_probs, trace_label=trace_label, trace_choosers=choosers) # no fru + raw_choices, rands = logit.make_choices(chooser_probs, trace_label=trace_label, trace_choosers=choosers) chunk.log_df(trace_label, "choices", raw_choices) chunk.log_df(trace_label, "rands", rands) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 183bea60cf..5ce21c359d 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -954,7 +954,7 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, if config.setting("freeze_unobserved_utilities", False): if custom_chooser: # TODO JAN: ADD HERE - need to figure out what to do with custom chooser though. leave out for now, - # we only need to fix up joit_tour_participation for now + # we only need to fix up joint_tour_participation for now raise "Not implemented yet" choices, rands = logit.make_choices_utility_based(utilities, trace_label=trace_label) From f9b1e2e09f6d6094aeaf02eb21009afa57ade4e6 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 15:59:30 +1000 Subject: [PATCH 060/298] working through all make_choices calls to implement memory saving hack until sharrow arrives --- .../core/interaction_sample_simulate.py | 69 +++++++++++-------- activitysim/core/interaction_simulate.py | 47 ++++++++----- activitysim/core/pathbuilder.py | 5 +- 3 files changed, 71 insertions(+), 50 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 573ac89b8d..ad81696213 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -8,6 +8,7 @@ from . import logit from . import tracing from . import chunk +from . import config from .simulate import set_skim_wrapper_targets from . import interaction_simulate @@ -22,7 +23,7 @@ def _interaction_sample_simulate( want_logsums, skims, locals_d, trace_label, trace_choice_name, - estimator, choose_individual_max_utility): + estimator): """ Run a MNL simulation in the situation in which alternatives must @@ -205,35 +206,49 @@ def _interaction_sample_simulate( tracing.trace_df(utilities_df, tracing.extend_trace_label(trace_label, 'utilities'), column_labels=['alternative', 'utility']) - # convert to probabilities (utilities exponentiated and normalized to probs) - # probs is same shape as utilities, one row per chooser and one column for alternative - probs = logit.utils_to_probs(utilities_df, allow_zero_probs=allow_zero_probs, - trace_label=trace_label, trace_choosers=choosers) - chunk.log_df(trace_label, 'probs', probs) + if config.setting("freeze_unobserved_utilities", False): + # positions is series with the chosen alternative represented as a column index in utilities_df + # which is an integer between zero and num alternatives in the alternative sample + positions, rands = logit.make_choices_utility_based( + utilities_df, trace_label=trace_label, trace_choosers=choosers + ) - if want_logsums: - logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) - chunk.log_df(trace_label, 'logsums', logsums) + if want_logsums: + logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) + chunk.log_df(trace_label, 'logsums', logsums) - if have_trace_targets: - tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), - column_labels=['alternative', 'probability']) + del utilities_df + chunk.log_df(trace_label, 'utilities_df', None) + + else: + # convert to probabilities (utilities exponentiated and normalized to probs) + # probs is same shape as utilities, one row per chooser and one column for alternative + probs = logit.utils_to_probs(utilities_df, allow_zero_probs=allow_zero_probs, + trace_label=trace_label, trace_choosers=choosers) + chunk.log_df(trace_label, 'probs', probs) + + if want_logsums: + logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) + chunk.log_df(trace_label, 'logsums', logsums) + + del utilities_df + chunk.log_df(trace_label, 'utilities_df', None) - if allow_zero_probs: - zero_probs = (probs.sum(axis=1) == 0) - if zero_probs.any(): - # FIXME this is kind of gnarly, but we force choice of first alt - probs.loc[zero_probs, 0] = 1.0 + if have_trace_targets: + tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), + column_labels=['alternative', 'probability']) - # make choices - # positions is series with the chosen alternative represented as a column index in probs - # which is an integer between zero and num alternatives in the alternative sample - positions, rands = \ - logit.make_choices(probs, utilities_df, trace_label=trace_label, trace_choosers=choosers, - choose_individual_max_utility=choose_individual_max_utility) + if allow_zero_probs: + zero_probs = (probs.sum(axis=1) == 0) + if zero_probs.any(): + # FIXME this is kind of gnarly, but we force choice of first alt + probs.loc[zero_probs, 0] = 1.0 - del utilities_df - chunk.log_df(trace_label, 'utilities_df', None) + # make choices + # positions is series with the chosen alternative represented as a column index in probs + # which is an integer between zero and num alternatives in the alternative sample + positions, rands = \ + logit.make_choices(probs, utilities_df, trace_label=trace_label, trace_choosers=choosers) chunk.log_df(trace_label, 'positions', positions) chunk.log_df(trace_label, 'rands', rands) @@ -288,7 +303,7 @@ def interaction_sample_simulate( want_logsums=False, skims=None, locals_d=None, chunk_size=0, chunk_tag=None, trace_label=None, trace_choice_name=None, - estimator=None, choose_individual_max_utility=False): + estimator=None): """ Run a simulation in the situation in which alternatives must @@ -358,7 +373,7 @@ def interaction_sample_simulate( want_logsums, skims, locals_d, chunk_trace_label, trace_choice_name, - estimator, choose_individual_max_utility) + estimator) result_list.append(choices) diff --git a/activitysim/core/interaction_simulate.py b/activitysim/core/interaction_simulate.py index b0a74ab1e0..70067ebb95 100644 --- a/activitysim/core/interaction_simulate.py +++ b/activitysim/core/interaction_simulate.py @@ -240,8 +240,7 @@ def _interaction_simulate( skims=None, locals_d=None, sample_size=None, trace_label=None, trace_choice_name=None, log_alt_losers=False, - estimator=None, - choose_individual_max_utility=False): + estimator=None): """ Run a MNL simulation in the situation in which alternatives must be merged with choosers because there are interaction terms or @@ -375,24 +374,34 @@ def _interaction_simulate( tracing.dump_df(DUMP, utilities, trace_label, 'utilities') - # convert to probabilities (utilities exponentiated and normalized to probs) - # probs is same shape as utilities, one row per chooser and one column for alternative - probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) - chunk.log_df(trace_label, 'probs', probs) + if config.setting("freeze_unobserved_utilities", False): + positions, rands = logit.make_choices_utility_based( + utilities, trace_label=trace_label, trace_choosers=choosers + ) - if have_trace_targets: - tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), - column_labels=['alternative', 'probability']) - - # make choices - # positions is series with the chosen alternative represented as a column index in probs - # which is an integer between zero and num alternatives in the alternative sample - positions, rands = \ - logit.make_choices(probs, utilities=utilities, trace_label=trace_label, trace_choosers=choosers, - choose_individual_max_utility=choose_individual_max_utility) - - del utilities - chunk.log_df(trace_label, 'utilities', None) + del utilities + chunk.log_df(trace_label, 'utilities', None) + + else: + # convert to probabilities (utilities exponentiated and normalized to probs) + # probs is same shape as utilities, one row per chooser and one column for alternative + probs = logit.utils_to_probs(utilities, trace_label=trace_label, trace_choosers=choosers) + chunk.log_df(trace_label, 'probs', probs) + + if have_trace_targets: + tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), + column_labels=['alternative', 'probability']) + + del utilities + chunk.log_df(trace_label, 'utilities', None) + + # make choices + # positions is series with the chosen alternative represented as a column index in probs + # which is an integer between zero and num alternatives in the alternative sample + positions, rands = logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers) + + del probs + chunk.log_df(trace_label, 'probs', None) chunk.log_df(trace_label, 'positions', positions) chunk.log_df(trace_label, 'rands', rands) diff --git a/activitysim/core/pathbuilder.py b/activitysim/core/pathbuilder.py index 28d0707575..d4cee77504 100644 --- a/activitysim/core/pathbuilder.py +++ b/activitysim/core/pathbuilder.py @@ -744,10 +744,7 @@ def build_virtual_path(self, recipe, path_type, orig, dest, tod, demographic_seg self.trace_df(probs, trace_label, 'probs') else: - choices, rands = logit.make_choices( - probs, utilities=utilities_df, allow_bad_probs=True, trace_label=trace_label, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) - ) + choices, rands = logit.make_choices(probs, allow_bad_probs=True, trace_label=trace_label) chunk.log_df(trace_label, "rands", rands) del rands From 4b5cad2681a3ef6a968bfcf0957961d575d8b6f7 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:02:52 +1000 Subject: [PATCH 061/298] working through all make_choices calls to implement memory saving hack until sharrow arrives --- activitysim/abm/models/location_choice.py | 3 +-- activitysim/abm/models/non_mandatory_tour_frequency.py | 3 +-- activitysim/abm/models/parking_location_choice.py | 4 ++-- activitysim/abm/models/trip_destination.py | 3 +-- activitysim/abm/models/trip_scheduling_choice.py | 3 +-- activitysim/abm/models/util/tour_destination.py | 4 ++-- activitysim/abm/models/util/tour_od.py | 4 ++-- activitysim/abm/models/util/vectorize_tour_scheduling.py | 3 +-- activitysim/core/interaction_simulate.py | 6 ++---- 9 files changed, 13 insertions(+), 20 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 7be64b848d..3670c089ef 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -538,8 +538,7 @@ def run_location_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name=model_settings['DEST_CHOICE_COLUMN_NAME'], - estimator=estimator, - choose_individual_max_utility=choose_individual_max_utility + estimator=estimator ) if not want_logsums: diff --git a/activitysim/abm/models/non_mandatory_tour_frequency.py b/activitysim/abm/models/non_mandatory_tour_frequency.py index e0dd4b92c1..2ae277296b 100644 --- a/activitysim/abm/models/non_mandatory_tour_frequency.py +++ b/activitysim/abm/models/non_mandatory_tour_frequency.py @@ -232,8 +232,7 @@ def non_mandatory_tour_frequency(persons, persons_merged, chunk_size=chunk_size, trace_label='non_mandatory_tour_frequency.%s' % segment_name, trace_choice_name='non_mandatory_tour_frequency', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) if estimator: estimator.write_choices(choices) diff --git a/activitysim/abm/models/parking_location_choice.py b/activitysim/abm/models/parking_location_choice.py index c81eb89691..8802981616 100644 --- a/activitysim/abm/models/parking_location_choice.py +++ b/activitysim/abm/models/parking_location_choice.py @@ -124,8 +124,8 @@ def parking_destination_simulate( locals_d=locals_dict, chunk_size=chunk_size, trace_label=trace_label, - trace_choice_name='parking_loc', - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + trace_choice_name='parking_loc' + ) # drop any failed zero_prob destinations if (parking_locations == NO_DESTINATION).any(): diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 1042197b83..167c327466 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -710,8 +710,7 @@ def trip_destination_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name='trip_dest', - estimator=estimator, - choose_individual_max_utility=choose_individual_max_utility + estimator=estimator ) if not want_logsums: diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index b1e6f72626..c950b0726f 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -261,8 +261,7 @@ def run_trip_scheduling_choice(spec, tours, skims, locals_dict, locals_d=locals_dict, trace_label=chunk_trace_label, trace_choice_name='trip_schedule_stage_1', - estimator=None, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + estimator=None ) assert len(choices.index) == len(choosers.index) diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 7e3303c530..72212aff9d 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -645,8 +645,8 @@ def run_destination_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name='destination', - estimator=estimator, - choose_individual_max_utility=choose_individual_max_utility) + estimator=estimator + ) if not want_logsums: # for consistency, always return a dataframe with canonical column name diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 731aebe04a..ea632554a3 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -863,8 +863,8 @@ def run_od_simulate( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='origin_destination', - estimator=estimator, - choose_individual_max_utility=choose_individual_max_utility) + estimator=estimator + ) if not want_logsums: choices = choices.to_frame('choice') diff --git a/activitysim/abm/models/util/vectorize_tour_scheduling.py b/activitysim/abm/models/util/vectorize_tour_scheduling.py index ca77ab6c08..199059e2af 100644 --- a/activitysim/abm/models/util/vectorize_tour_scheduling.py +++ b/activitysim/abm/models/util/vectorize_tour_scheduling.py @@ -600,8 +600,7 @@ def _schedule_tours( locals_d=locals_d, chunk_size=0, trace_label=tour_trace_label, - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + estimator=estimator ) chunk.log_df(tour_trace_label, 'choices', choices) diff --git a/activitysim/core/interaction_simulate.py b/activitysim/core/interaction_simulate.py index 70067ebb95..827ed3aab3 100644 --- a/activitysim/core/interaction_simulate.py +++ b/activitysim/core/interaction_simulate.py @@ -432,8 +432,7 @@ def interaction_simulate( log_alt_losers=False, skims=None, locals_d=None, sample_size=None, chunk_size=0, trace_label=None, trace_choice_name=None, - estimator=None, - choose_individual_max_utility=False): + estimator=None): """ Run a simulation in the situation in which alternatives must @@ -499,8 +498,7 @@ def interaction_simulate( trace_label=chunk_trace_label, trace_choice_name=trace_choice_name, log_alt_losers=log_alt_losers, - estimator=estimator, - choose_individual_max_utility=choose_individual_max_utility) + estimator=estimator) result_list.append(choices) From b48dd71bbf9924351dd27659eec925bd3e4c138b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:09:57 +1000 Subject: [PATCH 062/298] working through all make_choices calls to implement memory saving hack until sharrow arrives --- activitysim/abm/models/atwork_subtour_frequency.py | 3 +-- activitysim/abm/models/auto_ownership.py | 3 +-- activitysim/abm/models/free_parking.py | 3 +-- activitysim/abm/models/joint_tour_composition.py | 3 +-- activitysim/abm/models/joint_tour_frequency.py | 3 +-- activitysim/abm/models/mandatory_tour_frequency.py | 3 +-- activitysim/abm/models/stop_frequency.py | 3 +-- activitysim/abm/models/trip_mode_choice.py | 3 +-- activitysim/abm/models/util/mode.py | 9 +++------ .../example_semcog/extensions/telecommute_frequency.py | 3 +-- .../example_semcog/extensions/transit_pass_ownership.py | 3 +-- .../example_semcog/extensions/transit_pass_subsidy.py | 3 +-- .../examples/example_semcog/extensions/work_from_home.py | 3 +-- 13 files changed, 15 insertions(+), 30 deletions(-) diff --git a/activitysim/abm/models/atwork_subtour_frequency.py b/activitysim/abm/models/atwork_subtour_frequency.py index ed62db14cd..cb9d3ca4c7 100644 --- a/activitysim/abm/models/atwork_subtour_frequency.py +++ b/activitysim/abm/models/atwork_subtour_frequency.py @@ -88,8 +88,7 @@ def atwork_subtour_frequency(tours, chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='atwork_subtour_frequency', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/auto_ownership.py b/activitysim/abm/models/auto_ownership.py index a6a1e2106a..12f8fbd2bb 100644 --- a/activitysim/abm/models/auto_ownership.py +++ b/activitysim/abm/models/auto_ownership.py @@ -57,8 +57,7 @@ def auto_ownership_simulate(households, trace_label=trace_label, trace_choice_name='auto_ownership', log_alt_losers=log_alt_losers, - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) if estimator: estimator.write_choices(choices) diff --git a/activitysim/abm/models/free_parking.py b/activitysim/abm/models/free_parking.py index 9f37872f9a..b144ed0543 100644 --- a/activitysim/abm/models/free_parking.py +++ b/activitysim/abm/models/free_parking.py @@ -68,8 +68,7 @@ def free_parking( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='free_parking_at_work', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) free_parking_alt = model_settings['FREE_PARKING_ALT'] choices = (choices == free_parking_alt) diff --git a/activitysim/abm/models/joint_tour_composition.py b/activitysim/abm/models/joint_tour_composition.py index 3e6cbc10f4..f21b8f8677 100644 --- a/activitysim/abm/models/joint_tour_composition.py +++ b/activitysim/abm/models/joint_tour_composition.py @@ -96,8 +96,7 @@ def joint_tour_composition( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='composition', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/joint_tour_frequency.py b/activitysim/abm/models/joint_tour_frequency.py index 8cf63e332d..d8930d9834 100644 --- a/activitysim/abm/models/joint_tour_frequency.py +++ b/activitysim/abm/models/joint_tour_frequency.py @@ -87,8 +87,7 @@ def joint_tour_frequency( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='joint_tour_frequency', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/mandatory_tour_frequency.py b/activitysim/abm/models/mandatory_tour_frequency.py index de3c6f2a5a..bbe1df599e 100644 --- a/activitysim/abm/models/mandatory_tour_frequency.py +++ b/activitysim/abm/models/mandatory_tour_frequency.py @@ -96,8 +96,7 @@ def mandatory_tour_frequency(persons_merged, chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='mandatory_tour_frequency', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) # convert indexes to alternative names choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/stop_frequency.py b/activitysim/abm/models/stop_frequency.py index db967f8b77..14ddeb0669 100644 --- a/activitysim/abm/models/stop_frequency.py +++ b/activitysim/abm/models/stop_frequency.py @@ -143,8 +143,7 @@ def stop_frequency( chunk_size=chunk_size, trace_label=tracing.extend_trace_label(trace_label, segment_name), trace_choice_name='stops', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) # convert indexes to alternative names choices = pd.Series(segment_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/abm/models/trip_mode_choice.py b/activitysim/abm/models/trip_mode_choice.py index 623c4b34df..59e9fc5e5e 100644 --- a/activitysim/abm/models/trip_mode_choice.py +++ b/activitysim/abm/models/trip_mode_choice.py @@ -194,8 +194,7 @@ def trip_mode_choice( logsum_column_name=logsum_column_name, trace_label=segment_trace_label, trace_choice_name='trip_mode_choice', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) if trace_hh_id: # trace the coefficients diff --git a/activitysim/abm/models/util/mode.py b/activitysim/abm/models/util/mode.py index ec3f39dd8f..41937ff0e3 100644 --- a/activitysim/abm/models/util/mode.py +++ b/activitysim/abm/models/util/mode.py @@ -25,8 +25,7 @@ def mode_choice_simulate( trace_label, trace_choice_name, trace_column_names=None, - estimator=None, - choose_individual_max_utility=False): + estimator=None): """ common method for both tour_mode_choice and trip_mode_choice @@ -61,8 +60,7 @@ def mode_choice_simulate( trace_label=trace_label, trace_choice_name=trace_choice_name, estimator=estimator, - trace_column_names=trace_column_names, - choose_individual_max_utility=choose_individual_max_utility) + trace_column_names=trace_column_names) # for consistency, always return dataframe, whether or not logsums were requested if isinstance(choices, pd.Series): @@ -146,8 +144,7 @@ def run_tour_mode_choice_simulate( trace_label=trace_label, trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) + estimator=estimator ) return choices diff --git a/activitysim/examples/example_semcog/extensions/telecommute_frequency.py b/activitysim/examples/example_semcog/extensions/telecommute_frequency.py index 6246820e5d..bbe72e11f1 100755 --- a/activitysim/examples/example_semcog/extensions/telecommute_frequency.py +++ b/activitysim/examples/example_semcog/extensions/telecommute_frequency.py @@ -75,8 +75,7 @@ def telecommute_frequency( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='telecommute_frequency', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) choices = pd.Series(model_spec.columns[choices.values], index=choices.index) diff --git a/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py b/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py index 1f111e3dec..1b9d3c1fae 100644 --- a/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py +++ b/activitysim/examples/example_semcog/extensions/transit_pass_ownership.py @@ -69,8 +69,7 @@ def transit_pass_ownership( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='transit_pass_ownership', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) if estimator: estimator.write_choices(choices) diff --git a/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py b/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py index ae02208c83..ddbcfbef32 100644 --- a/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py +++ b/activitysim/examples/example_semcog/extensions/transit_pass_subsidy.py @@ -69,8 +69,7 @@ def transit_pass_subsidy( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='transit_pass_subsidy', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) if estimator: estimator.write_choices(choices) diff --git a/activitysim/examples/example_semcog/extensions/work_from_home.py b/activitysim/examples/example_semcog/extensions/work_from_home.py index 38182fcd39..f6e2728b23 100755 --- a/activitysim/examples/example_semcog/extensions/work_from_home.py +++ b/activitysim/examples/example_semcog/extensions/work_from_home.py @@ -87,8 +87,7 @@ def work_from_home( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='work_from_home', - estimator=estimator, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + estimator=estimator) if iterations_target_percent is not None: choices_for_filter = choices[choosers[iterations_chooser_filter]] From 17792f5edb81eb0eb64b3f866b99015b3a1b1f2b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:13:49 +1000 Subject: [PATCH 063/298] working through all make_choices calls to implement memory saving hack until sharrow arrives --- .../abm/models/joint_tour_participation.py | 2 +- activitysim/abm/models/location_choice.py | 41 +++++++------------ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index af3a32e543..c69b5093c5 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -379,4 +379,4 @@ def joint_tour_participation( label="joint_tour_participation.participants") tracing.trace_df(joint_tours, - label="joint_tour_participation.joint_tours") \ No newline at end of file + label="joint_tour_participation.joint_tours") diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 3670c089ef..1c893bccbc 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -109,8 +109,7 @@ def _location_sample( model_settings, alt_dest_col_name, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility): + trace_label): """ select a sample of alternative locations. @@ -162,8 +161,7 @@ def _location_sample( locals_d=locals_d, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility + trace_label=trace_label ) return choices @@ -177,8 +175,7 @@ def location_sample( estimator, model_settings, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility): + trace_label): # FIXME - MEMORY HACK - only include columns actually used in spec chooser_columns = model_settings['SIMULATE_CHOOSER_COLUMNS'] @@ -202,8 +199,7 @@ def location_sample( model_settings, alt_dest_col_name, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility) + trace_label) return choices @@ -269,8 +265,7 @@ def location_presample( estimator, model_settings, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility): + trace_label): trace_label = tracing.extend_trace_label(trace_label, 'presample') @@ -308,8 +303,7 @@ def location_presample( model_settings, DEST_TAZ, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility) + trace_label) # print(f"taz_sample\n{taz_sample}") # dest_TAZ prob pick_count @@ -336,8 +330,7 @@ def run_location_sample( estimator, model_settings, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility): + trace_label): """ select a sample of alternative locations. @@ -380,8 +373,7 @@ def run_location_sample( model_settings, chunk_size, chunk_tag=f'{chunk_tag}.presample', - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) else: @@ -394,8 +386,7 @@ def run_location_sample( model_settings, chunk_size, chunk_tag=f'{chunk_tag}.sample', - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) return choices @@ -473,7 +464,7 @@ def run_location_simulate( estimator, model_settings, chunk_size, chunk_tag, - trace_label, choose_individual_max_utility): + trace_label): """ run location model on location_sample annotated with mode_choice logsum to select a dest zone from sample alternatives @@ -560,8 +551,7 @@ def run_location_choice( estimator, model_settings, chunk_size, chunk_tag, - trace_hh_id, trace_label, - choose_individual_max_utility, + trace_hh_id, trace_label ): """ Run the three-part location choice algorithm to generate a location choice for each chooser @@ -624,8 +614,7 @@ def run_location_choice( model_settings, chunk_size, chunk_tag, # run_location_sample will add appropriate suffix for sample or presample - trace_label=tracing.extend_trace_label(trace_label, 'sample.%s' % segment_name), - choose_individual_max_utility=choose_individual_max_utility) + trace_label=tracing.extend_trace_label(trace_label, 'sample.%s' % segment_name)) # - location_logsums location_sample_df = \ @@ -650,8 +639,7 @@ def run_location_choice( estimator, model_settings, chunk_size, chunk_tag=f'{chunk_tag}.simulate', - trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name), - choose_individual_max_utility=choose_individual_max_utility) + trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name)) if estimator: if trace_hh_id: @@ -795,8 +783,7 @@ def iterate_location_choice( model_settings=model_settings, chunk_size=chunk_size, chunk_tag=chunk_tag, trace_hh_id=trace_hh_id, - trace_label=tracing.extend_trace_label(trace_label, 'i%s' % iteration), - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + trace_label=tracing.extend_trace_label(trace_label, 'i%s' % iteration)) # choices_df is a pandas DataFrame with columns 'choice' and (optionally) 'logsum' if choices_df is None: From affe2b6dda4c12ec231b67fcca4b27d12d30a078 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:21:32 +1000 Subject: [PATCH 064/298] remove usage of choose_individual_max_utility --- activitysim/abm/models/location_choice.py | 6 +-- .../models/non_mandatory_tour_frequency.py | 3 +- .../abm/models/parking_location_choice.py | 3 +- activitysim/abm/models/tour_od_choice.py | 3 +- activitysim/abm/models/util/tour_od.py | 37 ++++++------------- 5 files changed, 16 insertions(+), 36 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 1c893bccbc..5b3b7d1e47 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -161,8 +161,7 @@ def _location_sample( locals_d=locals_d, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label - ) + trace_label=trace_label) return choices @@ -529,8 +528,7 @@ def run_location_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name=model_settings['DEST_CHOICE_COLUMN_NAME'], - estimator=estimator - ) + estimator=estimator) if not want_logsums: # for consistency, always return a dataframe with canonical column name diff --git a/activitysim/abm/models/non_mandatory_tour_frequency.py b/activitysim/abm/models/non_mandatory_tour_frequency.py index 2ae277296b..814bec97e6 100644 --- a/activitysim/abm/models/non_mandatory_tour_frequency.py +++ b/activitysim/abm/models/non_mandatory_tour_frequency.py @@ -111,8 +111,7 @@ def extend_tour_counts(persons, tour_counts, alternatives, trace_hh_id, trace_la choices, rands = logit.make_choices( choosers[PROBABILITY_COLUMNS], trace_label=tour_type_trace_label, - trace_choosers=choosers - ) + trace_choosers=choosers) # - extend tour_count (0-based prob alternative choice equals magnitude of extension) if choices.any(): diff --git a/activitysim/abm/models/parking_location_choice.py b/activitysim/abm/models/parking_location_choice.py index 8802981616..5c13969eed 100644 --- a/activitysim/abm/models/parking_location_choice.py +++ b/activitysim/abm/models/parking_location_choice.py @@ -124,8 +124,7 @@ def parking_destination_simulate( locals_d=locals_dict, chunk_size=chunk_size, trace_label=trace_label, - trace_choice_name='parking_loc' - ) + trace_choice_name='parking_loc') # drop any failed zero_prob destinations if (parking_locations == NO_DESTINATION).any(): diff --git a/activitysim/abm/models/tour_od_choice.py b/activitysim/abm/models/tour_od_choice.py index 41705358e0..6816eed385 100644 --- a/activitysim/abm/models/tour_od_choice.py +++ b/activitysim/abm/models/tour_od_choice.py @@ -93,8 +93,7 @@ def tour_od_choice( model_settings, network_los, estimator, - chunk_size, trace_hh_id, trace_label, - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + chunk_size, trace_hh_id, trace_label) if estimator: assert estimator.want_unsampled_alternatives diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index ea632554a3..bd017c4cae 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -115,8 +115,7 @@ def _od_sample( alt_od_col_name, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility): + trace_label): model_spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC', @@ -173,8 +172,7 @@ def _od_sample( locals_d=locals_d, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) return choices @@ -479,8 +477,7 @@ def od_presample( destination_size_terms, estimator, chunk_size, - trace_label, - choose_individual_max_utility): + trace_label): trace_label = tracing.extend_trace_label(trace_label, 'presample') chunk_tag = 'tour_od.presample' @@ -510,8 +507,7 @@ def od_presample( alt_od_col_name, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility) + trace_label) orig_MAZ_dest_TAZ_sample[ORIG_MAZ] = orig_MAZ_dest_TAZ_sample[alt_od_col_name].str.split('_').str[0].astype(int) orig_MAZ_dest_TAZ_sample[DEST_TAZ] = orig_MAZ_dest_TAZ_sample[alt_od_col_name].str.split('_').str[1].astype(int) @@ -586,8 +582,7 @@ def run_od_sample( destination_size_terms, estimator, chunk_size, - trace_label, - choose_individual_max_utility): + trace_label): model_spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC', segment_name=spec_segment_name, estimator=estimator) @@ -621,9 +616,7 @@ def run_od_sample( destination_size_terms, estimator, chunk_size, - trace_label, - choose_individual_max_utility - ) + trace_label) else: choices = od_sample( @@ -633,9 +626,7 @@ def run_od_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label, - choose_individual_max_utility - ) + chunk_size, trace_label) return choices @@ -791,8 +782,7 @@ def run_od_simulate( destination_size_terms, estimator, chunk_size, - trace_label, - choose_individual_max_utility): + trace_label): """ run simulate OD choices on tour_od_sample annotated with mode_choice logsum to select a tour OD from sample alternatives @@ -882,8 +872,7 @@ def run_tour_od( model_settings, network_los, estimator, - chunk_size, trace_hh_id, trace_label, - choose_individual_max_utility): + chunk_size, trace_hh_id, trace_label): size_term_calculator = SizeTermCalculator(model_settings['SIZE_TERM_SELECTOR']) preprocessor_settings = model_settings.get('preprocessor', None) @@ -933,10 +922,7 @@ def run_tour_od( segment_destination_size_terms, estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label( - trace_label, 'sample.%s' % segment_name), - choose_individual_max_utility=choose_individual_max_utility - ) + trace_label=tracing.extend_trace_label(trace_label, 'sample.%s' % segment_name)) if model_settings['ORIG_FILTER'] == 'original_MAZ > 0': pass @@ -975,8 +961,7 @@ def run_tour_od( destination_size_terms=segment_destination_size_terms, estimator=estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name), - choose_individual_max_utility=choose_individual_max_utility) + trace_label=tracing.extend_trace_label(trace_label, 'simulate.%s' % segment_name)) choices_list.append(choices) if estimator: From 6b38f128566027460966ca082c64592f9da46e0f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:26:48 +1000 Subject: [PATCH 065/298] remove usage of choose_individual_max_utility --- activitysim/abm/models/trip_destination.py | 44 +++++++--------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 167c327466..1e0462efe4 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -58,8 +58,7 @@ def _destination_sample( estimator, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility): + trace_label): """ Note: trips with no viable destination receive no sample rows @@ -110,9 +109,7 @@ def _destination_sample( skims=skims, locals_d=locals_dict, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility - ) + trace_label=trace_label) return choices @@ -126,8 +123,7 @@ def destination_sample( skim_hotel, estimator, chunk_size, - trace_label, - choose_individual_max_utility): + trace_label): chunk_tag = 'trip_destination.sample' @@ -145,9 +141,7 @@ def destination_sample( estimator, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility - ) + trace_label=trace_label) return choices @@ -367,8 +361,7 @@ def destination_presample( network_los, estimator, chunk_size, trace_hh_id, - trace_label, - choose_individual_max_utility): + trace_label): trace_label = tracing.extend_trace_label(trace_label, 'presample') chunk_tag = 'trip_destination.presample' # distinguish from trip_destination.sample @@ -406,8 +399,7 @@ def destination_presample( estimator, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total maz_sample = choose_MAZ_for_TAZ(taz_sample, size_term_matrix, trips, network_los, alt_dest_col_name, trace_label) @@ -426,8 +418,7 @@ def trip_destination_sample( skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label, - choose_individual_max_utility): + trace_label): """ Returns @@ -471,8 +462,7 @@ def trip_destination_sample( network_los, estimator, chunk_size, trace_hh_id, - trace_label, - choose_individual_max_utility) + trace_label) else: choices = destination_sample( @@ -484,8 +474,7 @@ def trip_destination_sample( skim_hotel, estimator, chunk_size, - trace_label, - choose_individual_max_utility) + trace_label) return choices @@ -664,8 +653,7 @@ def trip_destination_simulate( skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label, - choose_individual_max_utility): + trace_label): """ Chose destination from destination_sample (with od_logsum and dp_logsum columns added) @@ -744,8 +732,7 @@ def choose_trip_destination( size_term_matrix, skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label, - choose_individual_max_utility): + trace_label): logger.info("choose_trip_destination %s with %d trips", trace_label, trips.shape[0]) @@ -761,8 +748,7 @@ def choose_trip_destination( skim_hotel=skim_hotel, estimator=estimator, chunk_size=chunk_size, trace_hh_id=trace_hh_id, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) dropped_trips = ~trips.index.isin(destination_sample.index.unique()) if dropped_trips.any(): @@ -800,8 +786,7 @@ def choose_trip_destination( skim_hotel=skim_hotel, estimator=estimator, chunk_size=chunk_size, trace_hh_id=trace_hh_id, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) dropped_trips = ~trips.index.isin(destinations.index) if dropped_trips.any(): @@ -1054,8 +1039,7 @@ def run_trip_destination( size_term_matrix, skim_hotel, estimator, chunk_size, trace_hh_id, - trace_label=tracing.extend_trace_label(nth_trace_label, primary_purpose), - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False)) + trace_label=tracing.extend_trace_label(nth_trace_label, primary_purpose)) choices_list.append(choices) if want_sample_table: From 0387c356e604aa75c388a8156bb3b3f9704159cf Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:27:33 +1000 Subject: [PATCH 066/298] remove usage of choose_individual_max_utility --- activitysim/abm/models/trip_purpose.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activitysim/abm/models/trip_purpose.py b/activitysim/abm/models/trip_purpose.py index ab4dc28007..f0787ccf55 100644 --- a/activitysim/abm/models/trip_purpose.py +++ b/activitysim/abm/models/trip_purpose.py @@ -106,7 +106,9 @@ def choose_intermediate_trip_purpose( print(choosers[probs_cols]) estimator.write_table(choosers[probs_cols], 'probs', append=True) - choices, rands = logit.make_choices(choosers[purpose_cols], trace_label=trace_label, trace_choosers=choosers) + choices, rands = logit.make_choices( + choosers[purpose_cols], + trace_label=trace_label, trace_choosers=choosers) if have_trace_targets: tracing.trace_df(choices, '%s.choices' % trace_label, columns=[None, 'trip_purpose']) From 8ac91f19a056c478c7c380423ecffd342c17bc76 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:28:39 +1000 Subject: [PATCH 067/298] remove usage of choose_individual_max_utility --- activitysim/abm/models/trip_destination.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 1e0462efe4..8988bda74a 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -109,7 +109,8 @@ def _destination_sample( skims=skims, locals_d=locals_dict, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label) + trace_label=trace_label + ) return choices @@ -698,8 +699,7 @@ def trip_destination_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name='trip_dest', - estimator=estimator - ) + estimator=estimator) if not want_logsums: # for consistency, always return a dataframe with canonical column name From eea84ca079c1acef4a521372717816b42c7ccba7 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:31:14 +1000 Subject: [PATCH 068/298] remove usage of choose_individual_max_utility --- activitysim/abm/models/util/mode.py | 3 +- .../abm/models/util/tour_destination.py | 41 ++++++------------- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/activitysim/abm/models/util/mode.py b/activitysim/abm/models/util/mode.py index 41937ff0e3..37afe6d47d 100644 --- a/activitysim/abm/models/util/mode.py +++ b/activitysim/abm/models/util/mode.py @@ -144,7 +144,6 @@ def run_tour_mode_choice_simulate( trace_label=trace_label, trace_choice_name=trace_choice_name, trace_column_names=trace_column_names, - estimator=estimator - ) + estimator=estimator) return choices diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 72212aff9d..ab21de7352 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -77,8 +77,7 @@ def _destination_sample( alt_dest_col_name, chunk_size, chunk_tag, - trace_label, - choose_individual_max_utility): + trace_label): model_spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC', segment_name=spec_segment_name, estimator=estimator) @@ -111,8 +110,7 @@ def _destination_sample( locals_d=locals_d, chunk_size=chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) # remember person_id in chosen alts so we can merge with persons in subsequent steps # (broadcasts person_id onto all alternatives sharing the same tour_id index value) @@ -128,8 +126,7 @@ def destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label, - choose_individual_max_utility): + chunk_size, trace_label): chunk_tag = 'tour_destination.sample' @@ -156,8 +153,7 @@ def destination_sample( model_settings, alt_dest_col_name, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=trace_label) return choices @@ -397,8 +393,7 @@ def destination_presample( network_los, destination_size_terms, estimator, - chunk_size, trace_label, - choose_individual_max_utility): + chunk_size, trace_label): trace_label = tracing.extend_trace_label(trace_label, 'presample') chunk_tag = 'tour_destination.presample' @@ -430,9 +425,7 @@ def destination_presample( model_settings, DEST_TAZ, chunk_size, chunk_tag=chunk_tag, - trace_label=trace_label, - choose_individual_max_utility=choose_individual_max_utility - ) + trace_label=trace_label) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total maz_choices = choose_MAZ_for_TAZ(taz_sample, MAZ_size_terms, trace_label) @@ -451,8 +444,7 @@ def run_destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label, - choose_individual_max_utility): + chunk_size, trace_label): # FIXME - MEMORY HACK - only include columns actually used in spec (omit them pre-merge) chooser_columns = model_settings['SIMULATE_CHOOSER_COLUMNS'] @@ -483,9 +475,7 @@ def run_destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label, - choose_individual_max_utility - ) + chunk_size, trace_label) else: choices = destination_sample( @@ -495,9 +485,7 @@ def run_destination_sample( network_los, destination_size_terms, estimator, - chunk_size, trace_label, - choose_individual_max_utility - ) + chunk_size, trace_label) # remember person_id in chosen alts so we can merge with persons in subsequent steps # (broadcasts person_id onto all alternatives sharing the same tour_id index value) @@ -578,8 +566,7 @@ def run_destination_simulate( network_los, destination_size_terms, estimator, - chunk_size, trace_label, - choose_individual_max_utility): + chunk_size, trace_label): """ run destination_simulate on tour_destination_sample annotated with mode_choice logsum to select a destination from sample alternatives @@ -706,9 +693,7 @@ def run_tour_destination( segment_destination_size_terms, estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label(segment_trace_label, 'sample'), - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) - ) + trace_label=tracing.extend_trace_label(segment_trace_label, 'sample')) # - destination_logsums tour_purpose = segment_name # tour_purpose is segment_name @@ -736,9 +721,7 @@ def run_tour_destination( destination_size_terms=segment_destination_size_terms, estimator=estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label(segment_trace_label, 'simulate'), - choose_individual_max_utility=config.setting("freeze_unobserved_utilities", False) - ) + trace_label=tracing.extend_trace_label(segment_trace_label, 'simulate')) choices_list.append(choices) From cb807d13edf11ca199f864f8a6481eabaa336862 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 16:35:49 +1000 Subject: [PATCH 069/298] remove usage of choose_individual_max_utility --- activitysim/abm/models/trip_destination.py | 2 +- activitysim/abm/models/util/tour_destination.py | 3 +-- activitysim/abm/models/util/tour_od.py | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 8988bda74a..e4dbed4444 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -110,7 +110,7 @@ def _destination_sample( locals_d=locals_dict, chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label - ) + ) return choices diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index ab21de7352..7482aeb6db 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -632,8 +632,7 @@ def run_destination_simulate( chunk_size=chunk_size, chunk_tag=chunk_tag, trace_label=trace_label, trace_choice_name='destination', - estimator=estimator - ) + estimator=estimator) if not want_logsums: # for consistency, always return a dataframe with canonical column name diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index bd017c4cae..0db7e01d74 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -853,8 +853,7 @@ def run_od_simulate( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='origin_destination', - estimator=estimator - ) + estimator=estimator) if not want_logsums: choices = choices.to_frame('choice') @@ -922,7 +921,8 @@ def run_tour_od( segment_destination_size_terms, estimator, chunk_size=chunk_size, - trace_label=tracing.extend_trace_label(trace_label, 'sample.%s' % segment_name)) + trace_label=tracing.extend_trace_label( + trace_label, 'sample.%s' % segment_name)) if model_settings['ORIG_FILTER'] == 'original_MAZ > 0': pass From f0cc563bdb93bd5ca42cf5005c12474a9f952276 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 17:16:05 +1000 Subject: [PATCH 070/298] interaction_sample util based w/p probs --- activitysim/core/interaction_sample.py | 320 +++++++++++++------------ 1 file changed, 167 insertions(+), 153 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 4278b962c7..0b7fb99aea 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -11,6 +11,7 @@ from . import logit from . import tracing from . import chunk +from . import config from .simulate import set_skim_wrapper_targets from .logit import inverse_ev1_cdf @@ -23,14 +24,54 @@ DUMP = False +def make_sample_choices_utility_based( + choosers, utilities, + alternatives, + sample_size, alternative_count, alt_col_name, + allow_zero_probs, + trace_label): + + assert isinstance(utilities, pd.DataFrame) + assert utilities.shape == (len(choosers), alternative_count) + choice_dimension = (len(choosers), alternative_count, sample_size) + + rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count*sample_size) + rands = rands.reshape(choice_dimension) + rands = inverse_ev1_cdf(rands) + chunk.log_df(trace_label, 'rands', rands) + + utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented + utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) + utilities += rands + + del rands + chunk.log_df(trace_label, 'rands', None) + + # this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative + choices_array = np.argmax(utilities, axis=1) + + choosers_index_rep = np.tile(np.arange(0, choices_array.shape[0]), sample_size) + choices_flattened = choices_array.flatten(order='F') + + # choices_flattened are 0-based index into alternatives, need to map to alternative values given by + # alternatives.index.values (they are in this order by construction) + # explode to one row per chooser.index, alt_zone_id + choices_df = pd.DataFrame({ + alt_col_name: alternatives.index.values[choices_flattened], + 'rand': np.zeros_like(choosers_index_rep), # TODO [janzill June2022]: zero out for now + #'prob': probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F'), + # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction + choosers.index.name: np.tile(choosers.index.values, sample_size) + }) + return choices_df + + def make_sample_choices( choosers, probs, alternatives, sample_size, alternative_count, alt_col_name, allow_zero_probs, - trace_label, - utilities=None, - choose_individual_max_utility=False): + trace_label): """ Parameters @@ -50,7 +91,6 @@ def make_sample_choices( ------- """ - assert isinstance(probs, pd.DataFrame) assert probs.shape == (len(choosers), alternative_count) @@ -65,134 +105,96 @@ def make_sample_choices( # remove from sample probs = probs[~zero_probs] choosers = choosers[~zero_probs] - # TODO [janzill Jun2022]: do we want this for consistency? - # might need this in other places too? - if utilities is not None: - utilities = utilities[~zero_probs] - - if choose_individual_max_utility: - assert isinstance(utilities, pd.DataFrame) - assert utilities.shape == (len(choosers), alternative_count) - choice_dimension = (len(choosers), alternative_count, sample_size) - - rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count*sample_size) - rands = rands.reshape(choice_dimension) - rands = inverse_ev1_cdf(rands) - chunk.log_df(trace_label, 'rands', rands) - - utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented - utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) - utilities += rands - - del rands - chunk.log_df(trace_label, 'rands', None) - - # this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative - choices_array = np.argmax(utilities, axis=1) - - choosers_index_rep = np.tile(np.arange(0, choices_array.shape[0]), sample_size) - choices_flattened = choices_array.flatten(order='F') - - # choices_flattened are 0-based index into alternatives, need to map to alternative values given by - # alternatives.index.values (they are in this order by construction) - # explode to one row per chooser.index, alt_zone_id - choices_df = pd.DataFrame({ - alt_col_name: alternatives.index.values[choices_flattened], - 'rand': np.zeros_like(choosers_index_rep), # TODO [janzill June2022]: zero out for now - 'prob': probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F'), - # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction - choosers.index.name: np.tile(choosers.index.values, sample_size) - }) - else: - cum_probs_array = probs.values.cumsum(axis=1) - chunk.log_df(trace_label, 'cum_probs_array', cum_probs_array) - # alt probs in convenient layout to return prob of chose alternative - # (same layout as cum_probs_arr) - alt_probs_array = probs.values.flatten() - chunk.log_df(trace_label, 'alt_probs_array', alt_probs_array) + cum_probs_array = probs.values.cumsum(axis=1) + chunk.log_df(trace_label, 'cum_probs_array', cum_probs_array) - # get sample_size rands for each chooser - rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size) + # alt probs in convenient layout to return prob of chose alternative + # (same layout as cum_probs_arr) + alt_probs_array = probs.values.flatten() + chunk.log_df(trace_label, 'alt_probs_array', alt_probs_array) - # transform as we iterate over alternatives - # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr - # i.e rands[i] is a 2-D array of one alt choice rand for each chooser - rands = rands.T.reshape(sample_size, -1, 1) - chunk.log_df(trace_label, 'rands', rands) + # get sample_size rands for each chooser + rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size) - # the alternative value chosen - # WHY SHOULD CHOICES COL HAVE TO BE TYPE INT??? - # choices_array = np.empty([sample_size, len(choosers)]).astype(int) - choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype) - # chunk log these later after we populate them... + # transform as we iterate over alternatives + # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr + # i.e rands[i] is a 2-D array of one alt choice rand for each chooser + rands = rands.T.reshape(sample_size, -1, 1) + chunk.log_df(trace_label, 'rands', rands) - # the probability of the chosen alternative - choice_probs_array = np.empty([sample_size, len(choosers)]) - # chunk log these later after we populate them... + # the alternative value chosen + # WHY SHOULD CHOICES COL HAVE TO BE TYPE INT??? + # choices_array = np.empty([sample_size, len(choosers)]).astype(int) + choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype) + # chunk log these later after we populate them... - alts = np.tile(alternatives.index.values, len(choosers)) - chunk.log_df(trace_label, 'alts', alts) + # the probability of the chosen alternative + choice_probs_array = np.empty([sample_size, len(choosers)]) + # chunk log these later after we populate them... - # FIXME - do this all at once rather than iterate? - for i in range(sample_size): + alts = np.tile(alternatives.index.values, len(choosers)) + chunk.log_df(trace_label, 'alts', alts) - # FIXME - do this in numpy, not pandas? + # FIXME - do this all at once rather than iterate? + for i in range(sample_size): - # rands for this alt in broadcastable shape - r = rands[i] + # FIXME - do this in numpy, not pandas? - # position of first occurrence of positive value - positions = np.argmax(cum_probs_array > r, axis=1) + # rands for this alt in broadcastable shape + r = rands[i] - # FIXME - leave positions as numpy array, not pandas series? - # positions is series with the chosen alternative represented as a column index in probs - # which is an integer between zero and num alternatives in the alternative sample - positions = pd.Series(positions, index=probs.index) + # position of first occurrence of positive value + positions = np.argmax(cum_probs_array > r, axis=1) - # need to get from an integer offset into the alternative sample to the alternative index - # that is, we want the index value of the row that is offset by rows into the - # tranche of this choosers alternatives created by cross join of alternatives and choosers + # FIXME - leave positions as numpy array, not pandas series? + # positions is series with the chosen alternative represented as a column index in probs + # which is an integer between zero and num alternatives in the alternative sample + positions = pd.Series(positions, index=probs.index) - # offsets is the offset into model_design df of first row of chooser alternatives - offsets = np.arange(len(positions)) * alternative_count + # need to get from an integer offset into the alternative sample to the alternative index + # that is, we want the index value of the row that is offset by rows into the + # tranche of this choosers alternatives created by cross join of alternatives and choosers - # choices and choice_probs have one element per chooser and is in same order as choosers - choices_array[i] = np.take(alts, positions + offsets) - choice_probs_array[i] = np.take(alt_probs_array, positions + offsets) + # offsets is the offset into model_design df of first row of chooser alternatives + offsets = np.arange(len(positions)) * alternative_count - del positions - del offsets + # choices and choice_probs have one element per chooser and is in same order as choosers + choices_array[i] = np.take(alts, positions + offsets) + choice_probs_array[i] = np.take(alt_probs_array, positions + offsets) - chunk.log_df(trace_label, 'choices_array', choices_array) - chunk.log_df(trace_label, 'choice_probs_array', choice_probs_array) + del positions + del offsets - del alts - chunk.log_df(trace_label, 'alts', None) - del cum_probs_array - chunk.log_df(trace_label, 'cum_probs_array', None) - del alt_probs_array - chunk.log_df(trace_label, 'alt_probs_array', None) + chunk.log_df(trace_label, 'choices_array', choices_array) + chunk.log_df(trace_label, 'choice_probs_array', choice_probs_array) - # explode to one row per chooser.index, alt_zone_id - choices_df = pd.DataFrame( - {alt_col_name: choices_array.flatten(order='F'), - 'rand': rands.flatten(order='F'), - 'prob': choice_probs_array.flatten(order='F'), - choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size) - }) + del alts + chunk.log_df(trace_label, 'alts', None) + del cum_probs_array + chunk.log_df(trace_label, 'cum_probs_array', None) + del alt_probs_array + chunk.log_df(trace_label, 'alt_probs_array', None) - chunk.log_df(trace_label, 'choices_df', choices_df) + # explode to one row per chooser.index, alt_zone_id + choices_df = pd.DataFrame( + {alt_col_name: choices_array.flatten(order='F'), + 'rand': rands.flatten(order='F'), + 'prob': choice_probs_array.flatten(order='F'), + choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size) + }) + + chunk.log_df(trace_label, 'choices_df', choices_df) - del choices_array - chunk.log_df(trace_label, 'choices_array', None) - del rands - chunk.log_df(trace_label, 'rands', None) - del choice_probs_array - chunk.log_df(trace_label, 'choice_probs_array', None) + del choices_array + chunk.log_df(trace_label, 'choices_array', None) + del rands + chunk.log_df(trace_label, 'rands', None) + del choice_probs_array + chunk.log_df(trace_label, 'choice_probs_array', None) - # handing this off to caller - chunk.log_df(trace_label, 'choices_df', None) + # handing this off to caller + chunk.log_df(trace_label, 'choices_df', None) return choices_df @@ -204,8 +206,7 @@ def _interaction_sample( log_alt_losers=False, skims=None, locals_d=None, - trace_label=None, - choose_individual_max_utility=False): + trace_label=None): """ Run a MNL simulation in the situation in which alternatives must be merged with choosers because there are interaction terms or @@ -350,51 +351,65 @@ def _interaction_sample( tracing.dump_df(DUMP, utilities, trace_label, 'utilities') - # convert to probabilities (utilities exponentiated and normalized to probs) - # probs is same shape as utilities, one row per chooser and one column for alternative - probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs, - trace_label=trace_label, trace_choosers=choosers) - chunk.log_df(trace_label, 'probs', probs) - - #del utilities - #chunk.log_df(trace_label, 'utilities', None) - if have_trace_targets: - tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), - column_labels=['alternative', 'probability']) + # sample size 0 is for estimation mode - see below + if config.setting("freeze_unobserved_utilities", False) and (sample_size != 0): - if sample_size == 0: - # FIXME return full alternative set rather than sample - logger.info("Estimation mode for %s using unsampled alternatives" % (trace_label, )) + choices_df = make_sample_choices_utility_based( + choosers, utilities, alternatives, + sample_size, alternative_count, alt_col_name, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label) - index_name = probs.index.name - choices_df = \ - pd.melt(probs.reset_index(), id_vars=[index_name])\ - .sort_values(by=index_name, kind='mergesort')\ - .set_index(index_name)\ - .rename(columns={'value': 'prob'})\ - .drop(columns='variable') + chunk.log_df(trace_label, 'choices_df', choices_df) - choices_df['pick_count'] = 1 - choices_df.insert(0, alt_col_name, np.tile(alternatives.index.values, len(choosers.index))) + del utilities + chunk.log_df(trace_label, 'utilities', None) - return choices_df else: - choices_df = make_sample_choices( - choosers, probs, alternatives, - sample_size, alternative_count, alt_col_name, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - utilities=utilities, - choose_individual_max_utility=choose_individual_max_utility) + # convert to probabilities (utilities exponentiated and normalized to probs) + # probs is same shape as utilities, one row per chooser and one column for alternative + probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs, + trace_label=trace_label, trace_choosers=choosers) + chunk.log_df(trace_label, 'probs', probs) + + del utilities + chunk.log_df(trace_label, 'utilities', None) + + if have_trace_targets: + tracing.trace_df(probs, tracing.extend_trace_label(trace_label, 'probs'), + column_labels=['alternative', 'probability']) + + if sample_size == 0: + # FIXME return full alternative set rather than sample + logger.info("Estimation mode for %s using unsampled alternatives" % (trace_label, )) + + index_name = probs.index.name + choices_df = \ + pd.melt(probs.reset_index(), id_vars=[index_name])\ + .sort_values(by=index_name, kind='mergesort')\ + .set_index(index_name)\ + .rename(columns={'value': 'prob'})\ + .drop(columns='variable') + + choices_df['pick_count'] = 1 + choices_df.insert(0, alt_col_name, np.tile(alternatives.index.values, len(choosers.index))) + + return choices_df + else: + choices_df = make_sample_choices( + choosers, probs, alternatives, + sample_size, alternative_count, alt_col_name, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label) - chunk.log_df(trace_label, 'choices_df', choices_df) + chunk.log_df(trace_label, 'choices_df', choices_df) - del utilities - chunk.log_df(trace_label, 'utilities', None) + # - NARROW + choices_df['prob'] = choices_df['prob'].astype(np.float32) - del probs - chunk.log_df(trace_label, 'probs', None) + del probs + chunk.log_df(trace_label, 'probs', None) # pick_count and pick_dup # pick_count is number of duplicate picks @@ -429,7 +444,7 @@ def _interaction_sample( chunk.log_df(trace_label, 'choices_df', choices_df) # - NARROW - choices_df['prob'] = choices_df['prob'].astype(np.float32) + # choices_df['prob'] = choices_df['prob'].astype(np.float32) assert (choices_df['pick_count'].max() < 4294967295) or (choices_df.empty) choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32) @@ -442,7 +457,7 @@ def interaction_sample( allow_zero_probs=False, log_alt_losers=False, skims=None, locals_d=None, chunk_size=0, chunk_tag=None, - trace_label=None, choose_individual_max_utility=False): + trace_label=None): """ Run a simulation in the situation in which alternatives must @@ -523,8 +538,7 @@ def interaction_sample( log_alt_losers=log_alt_losers, skims=skims, locals_d=locals_d, - trace_label=chunk_trace_label, - choose_individual_max_utility=choose_individual_max_utility) + trace_label=chunk_trace_label) if choices.shape[0] > 0: # might not be any if allow_zero_probs From 417e90feb6b95eda77338334455299ead1ebc8e3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 17:17:44 +1000 Subject: [PATCH 071/298] wrong method in cdap --- activitysim/abm/models/util/cdap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index d0bf565931..3cc2bc862e 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -637,7 +637,7 @@ def household_activity_choices(indiv_utils, interaction_coefficients, hhsize, return pd.Series(dtype='float64') if config.setting("freeze_unobserved_utilities", False): - idx_choices, rands = logit.make_choices(utils, trace_label=trace_label) + idx_choices, rands = logit.make_choices_utility_based(utils, trace_label=trace_label) else: probs = logit.utils_to_probs(utils, trace_label=trace_label) From 4f825ae9c4cbb6980e7c8632030fe9a786554780 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 17:22:29 +1000 Subject: [PATCH 072/298] style fixes --- activitysim/core/logit.py | 2 +- activitysim/core/simulate.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 1fe6065fd4..1f884fce8f 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -436,7 +436,7 @@ def __init__(self, name=None, level=0): self.coefficient = 0 def print(self): - print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s " % + print("Nest name: %s level: %s coefficient: %s product_of_coefficients: %s ancestors: %s" % (self.name, self.level, self.coefficient, self.product_of_coefficients, self.ancestors)) @property diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 5ce21c359d..10f27dcf7d 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -922,8 +922,6 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, This is the column label to be used in trace file csv dump of choices trace_column_names: str or list of str chooser columns to include when tracing expression_values - choose_individual_max_utility: bool - apply frozen randomness at the individual utility level Returns ------- @@ -994,8 +992,7 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, log_alt_losers=False, want_logsums=False, trace_label=None, - trace_choice_name=None, trace_column_names=None, - choose_individual_max_utility=False): + trace_choice_name=None, trace_column_names=None): """ Run a nested-logit simulation for when the model spec does not involve alternative specific data, e.g. there are no interactions with alternative @@ -1052,7 +1049,6 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label, column_labels=['alternative', 'utility']) - if config.setting("freeze_unobserved_utilities", False): if custom_chooser: # TODO JAN: ADD HERE - need to figure out what to do with custom chooser though. leave out for now, From 7a6cc29ee70a06aa752e490418401ab6e895a285 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 17:38:34 +1000 Subject: [PATCH 073/298] need probs in interaction_sample - try it the quick way --- activitysim/core/interaction_sample.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 0b7fb99aea..a85d8113ca 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -25,7 +25,7 @@ def make_sample_choices_utility_based( - choosers, utilities, + choosers, utilities, probs, alternatives, sample_size, alternative_count, alt_col_name, allow_zero_probs, @@ -59,7 +59,7 @@ def make_sample_choices_utility_based( choices_df = pd.DataFrame({ alt_col_name: alternatives.index.values[choices_flattened], 'rand': np.zeros_like(choosers_index_rep), # TODO [janzill June2022]: zero out for now - #'prob': probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F'), + 'prob': probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F'), # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction choosers.index.name: np.tile(choosers.index.values, sample_size) }) @@ -355,14 +355,23 @@ def _interaction_sample( # sample size 0 is for estimation mode - see below if config.setting("freeze_unobserved_utilities", False) and (sample_size != 0): + # TODO: calc probs afterwards and merge to keep memory usage down? + probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs, + trace_label=trace_label, trace_choosers=choosers) + probs = probs.astype(np.float32) + chunk.log_df(trace_label, 'probs', probs) + choices_df = make_sample_choices_utility_based( - choosers, utilities, alternatives, + choosers, utilities, probs, + alternatives, sample_size, alternative_count, alt_col_name, allow_zero_probs=allow_zero_probs, trace_label=trace_label) chunk.log_df(trace_label, 'choices_df', choices_df) + # Now we need to attach probs - memory preserving to do this after making choices + del utilities chunk.log_df(trace_label, 'utilities', None) @@ -405,9 +414,6 @@ def _interaction_sample( chunk.log_df(trace_label, 'choices_df', choices_df) - # - NARROW - choices_df['prob'] = choices_df['prob'].astype(np.float32) - del probs chunk.log_df(trace_label, 'probs', None) @@ -444,7 +450,7 @@ def _interaction_sample( chunk.log_df(trace_label, 'choices_df', choices_df) # - NARROW - # choices_df['prob'] = choices_df['prob'].astype(np.float32) + choices_df['prob'] = choices_df['prob'].astype(np.float32) assert (choices_df['pick_count'].max() < 4294967295) or (choices_df.empty) choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32) From f0143e99f3bebbe6f29e6e5a07eb37f4df1eb497 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 17:54:36 +1000 Subject: [PATCH 074/298] remove false comparison --- activitysim/core/interaction_sample_simulate.py | 12 ++++-------- activitysim/core/logit.py | 1 - 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index ad81696213..0ef9506eaf 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -199,6 +199,10 @@ def _interaction_sample_simulate( index=choosers.index) chunk.log_df(trace_label, 'utilities_df', utilities_df) + if want_logsums: + logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) + chunk.log_df(trace_label, 'logsums', logsums) + del padded_utilities chunk.log_df(trace_label, 'padded_utilities', None) @@ -213,10 +217,6 @@ def _interaction_sample_simulate( utilities_df, trace_label=trace_label, trace_choosers=choosers ) - if want_logsums: - logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) - chunk.log_df(trace_label, 'logsums', logsums) - del utilities_df chunk.log_df(trace_label, 'utilities_df', None) @@ -227,10 +227,6 @@ def _interaction_sample_simulate( trace_label=trace_label, trace_choosers=choosers) chunk.log_df(trace_label, 'probs', probs) - if want_logsums: - logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) - chunk.log_df(trace_label, 'logsums', logsums) - del utilities_df chunk.log_df(trace_label, 'utilities_df', None) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 1f884fce8f..37c70da6a3 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -265,7 +265,6 @@ def make_choices_ru_frozen_mnl(utilities): def make_choices_ru_frozen(utilities, alt_order_array, nest_spec=None, trace_label=None): trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen_mnl') if nest_spec is None: - assert (alt_order_array == utilities.columns.values).all() choices = make_choices_ru_frozen_mnl(utilities) else: choices = make_choices_ru_frozen_nl(utilities, alt_order_array, nest_spec) From f6d81d60d1d3035a9f993c76bd8e5af9dd81e2b5 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 17:57:00 +1000 Subject: [PATCH 075/298] bug fix --- activitysim/core/interaction_sample_simulate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 0ef9506eaf..bd63a20411 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -244,14 +244,14 @@ def _interaction_sample_simulate( # positions is series with the chosen alternative represented as a column index in probs # which is an integer between zero and num alternatives in the alternative sample positions, rands = \ - logit.make_choices(probs, utilities_df, trace_label=trace_label, trace_choosers=choosers) + logit.make_choices(probs, trace_label=trace_label, trace_choosers=choosers) + + del probs + chunk.log_df(trace_label, 'probs', None) chunk.log_df(trace_label, 'positions', positions) chunk.log_df(trace_label, 'rands', rands) - del probs - chunk.log_df(trace_label, 'probs', None) - # shouldn't have chosen any of the dummy pad utilities assert positions.max() < max_sample_count From b4495932ff7122faab31b7f1c11458a2ade7c053 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 18:02:29 +1000 Subject: [PATCH 076/298] bug fix --- activitysim/core/interaction_sample_simulate.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index bd63a20411..720b2b3113 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -267,9 +267,11 @@ def _interaction_sample_simulate( chunk.log_df(trace_label, 'choices', choices) - if allow_zero_probs and zero_probs.any(): - # FIXME this is kind of gnarly, patch choice for zero_probs - choices.loc[zero_probs] = zero_prob_choice_val + # TODO [janzill Jun2022]: Also for utility based choices? + if not config.setting("freeze_unobserved_utilities", False): + if allow_zero_probs and zero_probs.any(): + # FIXME this is kind of gnarly, patch choice for zero_probs + choices.loc[zero_probs] = zero_prob_choice_val if have_trace_targets: tracing.trace_df(choices, tracing.extend_trace_label(trace_label, 'choices'), From dbc809a4ffda797f10bf8b1b377f70ff2a27a4a7 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 18:05:10 +1000 Subject: [PATCH 077/298] add utility based choice option for transit virtual pathbuilder --- activitysim/core/pathbuilder.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/activitysim/core/pathbuilder.py b/activitysim/core/pathbuilder.py index d4cee77504..902ba24164 100644 --- a/activitysim/core/pathbuilder.py +++ b/activitysim/core/pathbuilder.py @@ -744,7 +744,12 @@ def build_virtual_path(self, recipe, path_type, orig, dest, tod, demographic_seg self.trace_df(probs, trace_label, 'probs') else: - choices, rands = logit.make_choices(probs, allow_bad_probs=True, trace_label=trace_label) + if config.setting("freeze_unobserved_utilities", False): + choices, rands = logit.make_choices_utility_based( + utilities_df, allow_bad_probs=True, trace_label=trace_label + ) + else: + choices, rands = logit.make_choices(probs, allow_bad_probs=True, trace_label=trace_label) chunk.log_df(trace_label, "rands", rands) del rands From 4c6a524e1eacccbc6327c1cc10f3566d6a31ac0d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 19:47:07 +1000 Subject: [PATCH 078/298] custom chooser --- .../abm/models/joint_tour_participation.py | 112 +++++++++++++++++- activitysim/core/simulate.py | 34 +++--- 2 files changed, 129 insertions(+), 17 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index c69b5093c5..1da9500ecf 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -212,6 +212,109 @@ def participants_chooser(probs, choosers, spec, trace_label): return choices, rands +def participants_chooser_utility_based(utilities, choosers, spec, trace_label): + """ + custom alternative to logit.make_choices for simulate.simple_simulate + + Choosing participants for mixed tours is trickier than adult or child tours becuase we + need at least one adult and one child participant in a mixed tour. We call logit.make_choices + and then check to see if the tour statisfies this requirement, and rechoose for any that + fail until all are satisfied. + + In principal, this shold always occur eventually, but we fail after MAX_ITERATIONS, + just in case there is some failure in program logic (haven't seen this occur.) + + Parameters + ---------- + utilities : pandas.DataFrame + Rows for choosers and columns for the alternatives from which they + are choosing. + choosers : pandas.dataframe + simple_simulate choosers df + spec : pandas.DataFrame + simple_simulate spec df + We only need spec so we can know the column index of the 'participate' alternative + indicating that the participant has been chosen to participate in the tour + trace_label : str + + Returns - same as logit.make_choices + ------- + choices, rands + choices, rands as returned by logit.make_choices (in same order as probs) + + """ + + assert utilities.index.equals(choosers.index) + + # choice is boolean (participate or not) + model_settings = config.read_model_settings('joint_tour_participation.yaml') + + choice_col = model_settings.get('participation_choice', 'participate') + assert choice_col in spec.columns, \ + "couldn't find participation choice column '%s' in spec" + PARTICIPATE_CHOICE = spec.columns.get_loc(choice_col) + MAX_ITERATIONS = model_settings.get('max_participation_choice_iterations', 5000) + + trace_label = tracing.extend_trace_label(trace_label, 'participants_chooser') + + candidates = choosers.copy() + choices_list = [] + rands_list = [] + + num_tours_remaining = len(candidates.tour_id.unique()) + logger.info('%s %s joint tours to satisfy.', trace_label, num_tours_remaining,) + + iter = 0 + while candidates.shape[0] > 0: + + iter += 1 + + if iter > MAX_ITERATIONS: + logger.warning('%s max iterations exceeded (%s).', trace_label, MAX_ITERATIONS) + diagnostic_cols = ['tour_id', 'household_id', 'composition', 'adult'] + unsatisfied_candidates = candidates[diagnostic_cols].join(utilities) + tracing.write_csv(unsatisfied_candidates, + file_name='%s.UNSATISFIED' % trace_label, transpose=False) + print(unsatisfied_candidates.head(20)) + assert False + + choices, rands = logit.make_choices_utility_based(utilities, trace_label=trace_label, trace_choosers=choosers) + participate = (choices == PARTICIPATE_CHOICE) + + # satisfaction indexed by tour_id + tour_satisfaction = get_tour_satisfaction(candidates, participate) + num_tours_satisfied_this_iter = tour_satisfaction.sum() + + if num_tours_satisfied_this_iter > 0: + + num_tours_remaining -= num_tours_satisfied_this_iter + + satisfied = reindex(tour_satisfaction, candidates.tour_id) + + choices_list.append(choices[satisfied]) + rands_list.append(rands[satisfied]) + + # remove candidates of satisfied tours + utilities = utilities[~satisfied] + candidates = candidates[~satisfied] + + logger.debug(f"{trace_label} iteration {iter} : " + f"{num_tours_satisfied_this_iter} joint tours satisfied {num_tours_remaining} remaining") + + choices = pd.concat(choices_list) + rands = pd.concat(rands_list).reindex(choosers.index) + + # reindex choices and rands to match probs and v index + choices = choices.reindex(choosers.index) + rands = rands.reindex(choosers.index) + assert choices.index.equals(choosers.index) + assert rands.index.equals(choosers.index) + + logger.info('%s %s iterations to satisfy all joint tours.', trace_label, iter,) + + return choices, rands + + def annotate_jtp(model_settings, trace_label): # - annotate persons @@ -305,6 +408,13 @@ def joint_tour_participation( household_chunk_ids = pd.Series(range(len(unique_household_ids)), index=unique_household_ids) candidates['chunk_id'] = reindex(household_chunk_ids, candidates.household_id) + + # TODO: loads of code duplication, could make this implicit and get rid of it + if config.setting("freeze_unobserved_utilities", False): + custom_chooser = participants_chooser_utility_based + else: + custom_chooser = participants_chooser + choices = simulate.simple_simulate_by_chunk_id( choosers=candidates, spec=model_spec, @@ -313,7 +423,7 @@ def joint_tour_participation( chunk_size=chunk_size, trace_label=trace_label, trace_choice_name='participation', - custom_chooser=participants_chooser, + custom_chooser=custom_chooser, estimator=estimator) # choice is boolean (participate or not) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 10f27dcf7d..8c1c3dca4f 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -951,11 +951,9 @@ def eval_mnl(choosers, spec, locals_d, custom_chooser, estimator, if config.setting("freeze_unobserved_utilities", False): if custom_chooser: - # TODO JAN: ADD HERE - need to figure out what to do with custom chooser though. leave out for now, - # we only need to fix up joint_tour_participation for now - raise "Not implemented yet" - - choices, rands = logit.make_choices_utility_based(utilities, trace_label=trace_label) + choices, rands = custom_chooser(utilities=utilities, choosers=choosers, spec=spec, trace_label=trace_label) + else: + choices, rands = logit.make_choices_utility_based(utilities, trace_label=trace_label) del utilities chunk.log_df(trace_label, 'utilities', None) @@ -1050,11 +1048,6 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, column_labels=['alternative', 'utility']) if config.setting("freeze_unobserved_utilities", False): - if custom_chooser: - # TODO JAN: ADD HERE - need to figure out what to do with custom chooser though. leave out for now, - # we only need to fix up joit_tour_participation for now - raise "Not implemented yet" - # TODO [janzill Jun2022]: combine with nested_exp_utilities? # utilities of leaves and nests nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) @@ -1076,12 +1069,21 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, del raw_utilities chunk.log_df(trace_label, 'raw_utilities', None) - choices, rands = logit.make_choices_utility_based( - nested_utilities, - name_mapping=name_mapping, - nest_spec=nest_spec, - trace_label=trace_label - ) + if custom_chooser: + choices, rands = custom_chooser( + utilities=nested_utilities, + name_mapping=name_mapping, + choosers=choosers, + spec=spec, + trace_label=trace_label + ) + else: + choices, rands = logit.make_choices_utility_based( + nested_utilities, + name_mapping=name_mapping, + nest_spec=nest_spec, + trace_label=trace_label + ) del(nested_utilities) chunk.log_df(trace_label, "nested_utilities", None) From 2ec41b18f8e8f9d7de378ed1dc5a59f948cd046c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 20:00:53 +1000 Subject: [PATCH 079/298] mem tracing in interaction_sample --- activitysim/core/interaction_sample.py | 41 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index a85d8113ca..7610d677ab 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -40,18 +40,27 @@ def make_sample_choices_utility_based( rands = inverse_ev1_cdf(rands) chunk.log_df(trace_label, 'rands', rands) - utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented - utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) - utilities += rands + # # use rands + #utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented + #utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) + #utilities += rands + rands += np.repeat(utilities.to_numpy()[:, :, None], sample_size, axis=2) + + # this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative + choices_array = np.argmax(rands, axis=1) + chunk.log_df(trace_label, 'choices_array', choices_array) del rands chunk.log_df(trace_label, 'rands', None) - # this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative - choices_array = np.argmax(utilities, axis=1) - choosers_index_rep = np.tile(np.arange(0, choices_array.shape[0]), sample_size) + chunk.log_df(trace_label, 'choosers_index_rep', choosers_index_rep) + choices_flattened = choices_array.flatten(order='F') + chunk.log_df(trace_label, 'choices_flattened', choices_flattened) + + probs_selection = probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F') + chunk.log_df(trace_label, 'probs_selection', probs_selection) # choices_flattened are 0-based index into alternatives, need to map to alternative values given by # alternatives.index.values (they are in this order by construction) @@ -59,10 +68,24 @@ def make_sample_choices_utility_based( choices_df = pd.DataFrame({ alt_col_name: alternatives.index.values[choices_flattened], 'rand': np.zeros_like(choosers_index_rep), # TODO [janzill June2022]: zero out for now - 'prob': probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F'), + 'prob': probs_selection, # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction choosers.index.name: np.tile(choosers.index.values, sample_size) }) + chunk.log_df(trace_label, 'choices_df', choices_df) + + del probs_selection + chunk.log_df(trace_label, 'probs_selection', None) + del choices_array + chunk.log_df(trace_label, 'choices_array', None) + del choosers_index_rep + chunk.log_df(trace_label, 'choosers_index_rep', None) + del choices_flattened + chunk.log_df(trace_label, 'choices_flattened', None) + + # handing this off to caller + chunk.log_df(trace_label, 'choices_df', None) + return choices_df @@ -370,11 +393,11 @@ def _interaction_sample( chunk.log_df(trace_label, 'choices_df', choices_df) - # Now we need to attach probs - memory preserving to do this after making choices - del utilities chunk.log_df(trace_label, 'utilities', None) + del probs + chunk.log_df(trace_label, 'probs', None) else: # convert to probabilities (utilities exponentiated and normalized to probs) # probs is same shape as utilities, one row per chooser and one column for alternative From 47601e3bc97e043deb8a172be3c5fd6d520e855d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 20:18:40 +1000 Subject: [PATCH 080/298] bug fix --- activitysim/core/simulate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 8c1c3dca4f..5929f59e36 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1075,6 +1075,7 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, name_mapping=name_mapping, choosers=choosers, spec=spec, + nest_spec=nest_spec, trace_label=trace_label ) else: @@ -1145,7 +1146,7 @@ def eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec, trace_label=trace_label) else: - choices, rands = logit.make_choices(base_probabilities,nest_spec=nest_spec, trace_label=trace_label) + choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label) del base_probabilities chunk.log_df(trace_label, 'base_probabilities', None) From e0555d439fe2c75265d16f3d78db440130162ec9 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 22:43:13 +1000 Subject: [PATCH 081/298] interaction_sample memory saving implementation --- activitysim/core/interaction_sample.py | 185 ++++++------- .../frozen_impl_interaction_sample_dev.ipynb | 242 ++++++++++++++---- 2 files changed, 283 insertions(+), 144 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 7610d677ab..79eb15dee6 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -25,68 +25,57 @@ def make_sample_choices_utility_based( - choosers, utilities, probs, - alternatives, - sample_size, alternative_count, alt_col_name, - allow_zero_probs, - trace_label): + choosers, + utilities, + sample_size, + alternative_count, + allow_zero_probs +): assert isinstance(utilities, pd.DataFrame) assert utilities.shape == (len(choosers), alternative_count) - choice_dimension = (len(choosers), alternative_count, sample_size) - - rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count*sample_size) - rands = rands.reshape(choice_dimension) - rands = inverse_ev1_cdf(rands) - chunk.log_df(trace_label, 'rands', rands) - - # # use rands - #utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented - #utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) - #utilities += rands - rands += np.repeat(utilities.to_numpy()[:, :, None], sample_size, axis=2) - - # this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative - choices_array = np.argmax(rands, axis=1) - chunk.log_df(trace_label, 'choices_array', choices_array) - - del rands - chunk.log_df(trace_label, 'rands', None) - - choosers_index_rep = np.tile(np.arange(0, choices_array.shape[0]), sample_size) - chunk.log_df(trace_label, 'choosers_index_rep', choosers_index_rep) - - choices_flattened = choices_array.flatten(order='F') - chunk.log_df(trace_label, 'choices_flattened', choices_flattened) + #choice_dimension = (len(choosers), alternative_count, sample_size) - probs_selection = probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F') - chunk.log_df(trace_label, 'probs_selection', probs_selection) - - # choices_flattened are 0-based index into alternatives, need to map to alternative values given by - # alternatives.index.values (they are in this order by construction) - # explode to one row per chooser.index, alt_zone_id - choices_df = pd.DataFrame({ - alt_col_name: alternatives.index.values[choices_flattened], - 'rand': np.zeros_like(choosers_index_rep), # TODO [janzill June2022]: zero out for now - 'prob': probs_selection, - # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction - choosers.index.name: np.tile(choosers.index.values, sample_size) - }) - chunk.log_df(trace_label, 'choices_df', choices_df) - - del probs_selection - chunk.log_df(trace_label, 'probs_selection', None) - del choices_array - chunk.log_df(trace_label, 'choices_array', None) - del choosers_index_rep - chunk.log_df(trace_label, 'choosers_index_rep', None) - del choices_flattened - chunk.log_df(trace_label, 'choices_flattened', None) - - # handing this off to caller - chunk.log_df(trace_label, 'choices_df', None) + # TODO [janzill Jun2022]: THIS NEEDS for loop for memory like previous method, an array of dimension + # (len(choosers), alternative_count, sample_size) can get very large + choices = np.zeros_like(utilities, dtype=np.uint32) + zero_dim_index = np.arange(utilities.shape[0]) - return choices_df + for i in range(sample_size): + rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) # * sample_size + #rands = rands.reshape(choice_dimension) + rands = inverse_ev1_cdf(rands) + #chunk.log_df(trace_label, 'rands', rands) + + ## # use rands + ##utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented + ##utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) + ##utilities += rands + #rands += np.repeat(utilities.to_numpy()[:, :, None], sample_size, axis=2) + rands += utilities + + # NOT ANYMORE: this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative + # NOW: len(choosers) dimensional array + #choices_array = np.argmax(rands, axis=1) + #chunk.log_df(trace_label, 'choices_array', choices_array) + + choices[zero_dim_index, np.argmax(rands, axis=1)] += 1 + + #choosers_index_rep = np.tile(np.arange(0, choices_array.shape[0]), sample_size) + #choices_flattened = choices_array.flatten(order='F') + #probs_selection = probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F') + + # # choices_flattened are 0-based index into alternatives, need to map to alternative values given by + # # alternatives.index.values (they are in this order by construction) + # # explode to one row per chooser.index, alt_zone_id + # choices_df = pd.DataFrame({ + # alt_col_name: alternatives.index.values[choices_flattened], + # 'rand': np.zeros_like(choosers_index_rep), # zero out for now + # 'prob': probs_selection, + # # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction + # choosers.index.name: np.tile(choosers.index.values, sample_size) + # }) + return choices def make_sample_choices( @@ -374,28 +363,41 @@ def _interaction_sample( tracing.dump_df(DUMP, utilities, trace_label, 'utilities') - # sample size 0 is for estimation mode - see below if config.setting("freeze_unobserved_utilities", False) and (sample_size != 0): - # TODO: calc probs afterwards and merge to keep memory usage down? + choices_array = make_sample_choices_utility_based( + choosers, utilities, sample_size, alternative_count, allow_zero_probs + ) + chunk.log_df(trace_label, 'choices_array', choices_array) + + del utilities + chunk.log_df(trace_label, 'utilities', None) + + # choices array has same dim as utilities, with values indicating number of counts per chooser and alternative + # let's turn the nonzero values into a dataframe + i, j = np.non_zero(choices_array) + chunk.log_df(trace_label, 'i', i) + chunk.log_df(trace_label, 'j', j) + probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs, trace_label=trace_label, trace_choosers=choosers) - probs = probs.astype(np.float32) chunk.log_df(trace_label, 'probs', probs) - choices_df = make_sample_choices_utility_based( - choosers, utilities, probs, - alternatives, - sample_size, alternative_count, alt_col_name, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label) - + choices_df = pd.DataFrame({ + alt_col_name: alternatives.index.values[j], + "pick_count": choices_array[i, j], + "prob": probs[i, j], + choosers.index.name: choosers.index.values[i] + }) chunk.log_df(trace_label, 'choices_df', choices_df) - del utilities - chunk.log_df(trace_label, 'utilities', None) - + del choices_array + chunk.log_df(trace_label, 'choices_array', None) + del i + chunk.log_df(trace_label, 'i', None) + del j + chunk.log_df(trace_label, 'j', None) del probs chunk.log_df(trace_label, 'probs', None) else: @@ -440,25 +442,27 @@ def _interaction_sample( del probs chunk.log_df(trace_label, 'probs', None) - # pick_count and pick_dup - # pick_count is number of duplicate picks - # pick_dup flag is True for all but first of duplicates - pick_group = choices_df.groupby([choosers.index.name, alt_col_name]) - - # number each item in each group from 0 to the length of that group - 1. - choices_df['pick_count'] = pick_group.cumcount(ascending=True) - # flag duplicate rows after first - choices_df['pick_dup'] = choices_df['pick_count'] > 0 - # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge) - choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1 - - # drop the duplicates - choices_df = choices_df[~choices_df['pick_dup']] - del choices_df['pick_dup'] - chunk.log_df(trace_label, 'choices_df', choices_df) + # pick_count and pick_dup + # pick_count is number of duplicate picks + # pick_dup flag is True for all but first of duplicates + pick_group = choices_df.groupby([choosers.index.name, alt_col_name]) + + # number each item in each group from 0 to the length of that group - 1. + choices_df['pick_count'] = pick_group.cumcount(ascending=True) + # flag duplicate rows after first + choices_df['pick_dup'] = choices_df['pick_count'] > 0 + # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge) + choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1 + + # drop the duplicates + choices_df = choices_df[~choices_df['pick_dup']] + del choices_df['pick_dup'] - # set index after groupby so we can trace on it - choices_df.set_index(choosers.index.name, inplace=True) + # set index after groupby so we can trace on it + choices_df.set_index(choosers.index.name, inplace=True) + + + chunk.log_df(trace_label, 'choices_df', choices_df) tracing.dump_df(DUMP, choices_df, trace_label, 'choices_df') @@ -469,8 +473,9 @@ def _interaction_sample( column_labels=['sample_alt', 'alternative']) # don't need this after tracing - del choices_df['rand'] - chunk.log_df(trace_label, 'choices_df', choices_df) + if not config.setting("freeze_unobserved_utilities", False): + del choices_df['rand'] + chunk.log_df(trace_label, 'choices_df', choices_df) # - NARROW choices_df['prob'] = choices_df['prob'].astype(np.float32) diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index 17c559e364..56274754bd 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -1,17 +1,5 @@ { "cells": [ - { - "cell_type": "markdown", - "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", - "metadata": {}, - "source": [ - "# validate results\n", - "\n", - "## TODO\n", - "what happened to tracing when I fixed probability calcs - it seems like all trip ids are attached when I add one by\n", - "hand below - why?" - ] - }, { "cell_type": "code", "execution_count": 1, @@ -98,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 112, + "execution_count": 5, "outputs": [ { "name": "stdout", @@ -114,7 +102,7 @@ "data": { "text/plain": "array([2, 2, 1, 2, 1, 0])" }, - "execution_count": 112, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -271,7 +259,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "execution": { @@ -286,12 +274,12 @@ "outputs": [], "source": [ "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_example_mtc\")" + "example_dir = os.path.join(root_dir, \"test_example_mtc_frozen_rand\")" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "execution": { @@ -310,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "execution": { @@ -326,7 +314,7 @@ "source": [ "parser = argparse.ArgumentParser()\n", "run.add_run_args(parser)\n", - "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", + "args = parser.parse_args(['-c', 'configs', '-o', 'output_without_before', '-d', 'data'])\n", "#run.run(args) # 2mins full example run\n", "if not inject.is_injectable('preload_injectables'):\n", " from activitysim import abm # register abm steps and other abm-specific injectables\n", @@ -335,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 9, "outputs": [], "source": [ "from activitysim.core import inject\n", @@ -358,7 +346,7 @@ }, { "cell_type": "code", - "execution_count": 234, + "execution_count": 10, "outputs": [], "source": [ "from activitysim.core.logit import inverse_ev1_cdf\n", @@ -503,7 +491,7 @@ }, { "cell_type": "code", - "execution_count": 237, + "execution_count": 11, "outputs": [ { "name": "stderr", @@ -541,8 +529,8 @@ "running gradeschool, 1\n", "dropping 0 of 25 rows where size_term is zero\n", "Running school_location with 17 persons\n", - "(17, 10)\n", - "(170,)\n", + "(17, 25)\n", + "(425,)\n", "(17, 25)\n", " 0 1 2 3 4 5 \\\n", "person_id \n", @@ -573,8 +561,8 @@ "386008 0.025936 \n", "418442 0.028217 \n", "595686 0.040062 \n", - "CPU times: user 281 ms, sys: 31.2 ms, total: 312 ms\n", - "Wall time: 334 ms\n" + "CPU times: user 1.39 s, sys: 328 ms, total: 1.72 s\n", + "Wall time: 2.15 s\n" ] } ], @@ -799,20 +787,50 @@ }, { "cell_type": "code", - "execution_count": 238, + "execution_count": 12, "outputs": [ { "data": { - "text/plain": " alt_dest rand prob person_id\n0 9 0 0.336708 386008\n51 7 0 0.056174 386008\n85 9 0 0.336708 386008\n102 9 0 0.336708 386008\n34 8 0 0.157982 386008\n.. ... ... ... ...\n50 16 0 0.182639 4171620\n33 6 0 0.032124 4171620\n16 9 0 0.111409 4171620\n152 10 0 0.097048 4171620\n169 7 0 0.062739 4171620\n\n[170 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
0900.336708386008
51700.056174386008
85900.336708386008
102900.336708386008
34800.157982386008
...............
501600.1826394171620
33600.0321244171620
16900.1114094171620
1521000.0970484171620
169700.0627394171620
\n

170 rows × 4 columns

\n
" + "text/plain": " alt_dest rand prob person_id\n0 10 0 0.205884 386008\n1 10 0 0.288773 418442\n2 21 0 0.147571 595686\n3 3 0 0.005248 644292\n4 8 0 0.117232 644478\n.. ... ... ... ...\n420 25 0 0.265744 2566702\n421 10 0 0.288773 2877287\n422 9 0 0.336708 3596365\n423 6 0 0.032124 3891104\n424 10 0 0.097048 4171620\n\n[425 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n

425 rows × 4 columns

\n
" }, - "execution_count": 238, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "temp_choices.sort_values(by=[\"person_id\"])" + "temp_choices #.sort_values(by=[\"person_id\"])" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 28, + "outputs": [], + "source": [ + "test_df = temp_choices.copy()\n", + "# pick_count and pick_dup\n", + "# pick_count is number of duplicate picks\n", + "# pick_dup flag is True for all but first of duplicates\n", + "pick_group = test_df.groupby([choosers.index.name, alt_col_name])\n", + "# number each item in each group from 0 to the length of that group - 1.\n", + "test_df['pick_count'] = pick_group.cumcount(ascending=True)\n", + "# flag duplicate rows after first\n", + "test_df['pick_dup'] = test_df['pick_count'] > 0\n", + "# add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", + "test_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", + "# drop the duplicates\n", + "test_df = test_df[~test_df['pick_dup']]\n", + "del test_df['pick_dup']\n", + "# set index after groupby so we can trace on it\n", + "test_df.set_index(choosers.index.name, inplace=True)\n", + "del test_df['rand']" ], "metadata": { "collapsed": false, @@ -823,20 +841,20 @@ }, { "cell_type": "code", - "execution_count": 216, + "execution_count": 29, "outputs": [ { "data": { - "text/plain": " alt_dest rand prob person_id\n0 8 0.222922 0.157982 386008\n1 9 0.404176 0.336708 386008\n2 8 0.223154 0.157982 386008\n3 9 0.457402 0.336708 386008\n4 11 0.812506 0.069858 386008\n.. ... ... ... ...\n165 7 0.107363 0.062739 4171620\n166 11 0.517573 0.070848 4171620\n167 10 0.375828 0.097048 4171620\n168 9 0.279810 0.111409 4171620\n169 20 0.848968 0.026642 4171620\n\n[170 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
080.2229220.157982386008
190.4041760.336708386008
280.2231540.157982386008
390.4574020.336708386008
4110.8125060.069858386008
...............
16570.1073630.0627394171620
166110.5175730.0708484171620
167100.3758280.0970484171620
16890.2798100.1114094171620
169200.8489680.0266424171620
\n

170 rows × 4 columns

\n
" + "text/plain": " alt_dest prob pick_count\nperson_id \n386008 10 0.205884 4\n418442 10 0.288773 9\n595686 21 0.147571 4\n644292 3 0.005248 1\n644478 8 0.117232 4\n... ... ... ...\n644478 6 0.032124 1\n2458502 25 0.032621 1\n2458503 11 0.078812 1\n2566700 16 0.036706 1\n2566701 18 0.014321 1\n\n[165 rows x 3 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destprobpick_count
person_id
386008100.2058844
418442100.2887739
595686210.1475714
64429230.0052481
64447880.1172324
............
64447860.0321241
2458502250.0326211
2458503110.0788121
2566700160.0367061
2566701180.0143211
\n

165 rows × 3 columns

\n
" }, - "execution_count": 216, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "#temp_choices" + "test_df" ], "metadata": { "collapsed": false, @@ -847,10 +865,22 @@ }, { "cell_type": "code", - "execution_count": null, - "outputs": [], + "execution_count": 39, + "outputs": [ + { + "data": { + "text/plain": " alt_dest prob pick_count\nperson_id \n3596365 9 0.336708 13\n386008 9 0.336708 12\n2877287 10 0.288773 10\n1958678 9 0.336708 10\n2458502 8 0.301414 9\n... ... ... ...\n2458503 11 0.078812 1\n644292 3 0.005248 1\n2458502 10 0.103895 1\n3891104 10 0.097048 1\n2566702 11 0.061290 1\n\n[165 rows x 3 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destprobpick_count
person_id
359636590.33670813
38600890.33670812
2877287100.28877310
195867890.33670810
245850280.3014149
............
2458503110.0788121
64429230.0052481
2458502100.1038951
3891104100.0970481
2566702110.0612901
\n

165 rows × 3 columns

\n
" + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "choices_df" + "test_df = temp_choices.copy()\n", + "del test_df['rand']\n", + "test_df.value_counts().to_frame(\"pick_count\").reset_index([\"prob\", \"alt_dest\"])" ], "metadata": { "collapsed": false, @@ -861,20 +891,20 @@ }, { "cell_type": "code", - "execution_count": 217, + "execution_count": 31, "outputs": [ { "data": { - "text/plain": " alt_dest prob pick_count\nperson_id \n386008 8 0.157982 2\n386008 9 0.336708 5\n386008 10 0.205884 1\n386008 11 0.069858 1\n386008 17 0.009346 1\n... ... ... ...\n4171620 9 0.111409 1\n4171620 10 0.097048 1\n4171620 11 0.070848 1\n4171620 20 0.026642 1\n4171620 25 0.062053 1\n\n[105 rows x 3 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destprobpick_count
person_id
38600880.1579822
38600890.3367085
386008100.2058841
386008110.0698581
386008170.0093461
............
417162090.1114091
4171620100.0970481
4171620110.0708481
4171620200.0266421
4171620250.0620531
\n

105 rows × 3 columns

\n
" + "text/plain": " alt_dest rand prob person_id\n0 10 0 0.205884 386008\n1 10 0 0.288773 418442\n2 21 0 0.147571 595686\n3 3 0 0.005248 644292\n4 8 0 0.117232 644478\n.. ... ... ... ...\n420 25 0 0.265744 2566702\n421 10 0 0.288773 2877287\n422 9 0 0.336708 3596365\n423 6 0 0.032124 3891104\n424 10 0 0.097048 4171620\n\n[425 rows x 4 columns]", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n

425 rows × 4 columns

\n
" }, - "execution_count": 217, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "#choices_df" + "test_df" ], "metadata": { "collapsed": false, @@ -885,19 +915,20 @@ }, { "cell_type": "code", - "execution_count": 231, + "execution_count": 40, "outputs": [ { "data": { - "text/plain": "array([1, 2, 3, 4, 5])" + "text/plain": " 0 1 2 3 4 5 \\\nperson_id \n386008 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n418442 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n595686 0.363442 1.313932 2.021662 1.599651 3.201645 3.726050 \n644292 0.426142 1.419508 2.267947 1.796678 3.710634 4.948799 \n644478 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n1958678 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n2159059 0.044242 0.994732 1.702462 1.151822 2.664408 3.617750 \n2219998 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n2458502 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n2458503 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n2566700 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n2566701 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n2566702 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n2877287 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n3596365 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n3891104 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n4171620 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n\n 6 7 8 9 10 11 \\\nperson_id \n386008 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n418442 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n595686 4.560310 5.610758 5.254657 5.202408 5.385828 1.836706 \n644292 5.808154 5.939138 5.530123 5.169570 5.057449 1.508326 \n644478 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n1958678 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n2159059 4.338439 5.249540 5.661475 5.941263 5.073867 1.056589 \n2219998 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n2458502 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n2458503 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n2566700 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n2566701 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n2566702 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n2877287 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n3596365 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n3891104 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n4171620 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n\n 12 13 14 15 16 17 \\\nperson_id \n386008 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n418442 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n595686 -0.149232 0.604582 0.207110 4.469041 3.740891 3.991371 \n644292 -0.379098 0.498325 0.133010 3.810490 3.312458 3.137154 \n644478 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n1958678 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n2159059 -0.719988 0.258925 -0.106390 3.844690 3.346658 3.301773 \n2219998 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n2458502 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n2458503 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n2566700 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n2566701 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n2566702 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n2877287 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n3596365 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n3891104 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n4171620 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n\n 18 19 20 21 22 23 \\\nperson_id \n386008 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n418442 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n595686 3.145862 4.005500 5.488831 1.780972 0.846532 2.046901 \n644292 2.751629 3.584558 4.421596 1.843672 0.909232 2.115301 \n644478 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n1958678 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n2159059 3.572756 4.974221 4.914166 1.461772 0.527332 1.733401 \n2219998 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n2458502 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n2458503 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n2566700 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n2566701 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n2566702 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n2877287 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n3596365 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n3891104 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n4171620 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n\n 24 \nperson_id \n386008 4.099442 \n418442 4.042442 \n595686 4.184942 \n644292 4.430042 \n644478 4.276142 \n1958678 4.099442 \n2159059 3.962642 \n2219998 4.276142 \n2458502 4.355942 \n2458503 4.355942 \n2566700 5.864195 \n2566701 5.864195 \n2566702 5.864195 \n2877287 4.042442 \n3596365 4.099442 \n3891104 4.276142 \n4171620 4.276142 ", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
0123456789101112131415161718192021222324
person_id
3860080.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
4184420.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
5956860.3634421.3139322.0216621.5996513.2016453.7260504.5603105.6107585.2546575.2024085.3858281.836706-0.1492320.6045820.2071104.4690413.7408913.9913713.1458624.0055005.4888311.7809720.8465322.0469014.184942
6442920.4261421.4195082.2679471.7966783.7106344.9487995.8081545.9391385.5301235.1695705.0574491.508326-0.3790980.4983250.1330103.8104903.3124583.1371542.7516293.5845584.4215961.8436720.9092322.1153014.430042
6444781.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
19586780.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
21590590.0442420.9947321.7024621.1518222.6644083.6177504.3384395.2495405.6614755.9412635.0738671.056589-0.7199880.258925-0.1063903.8446903.3466583.3017733.5727564.9742214.9141661.4617720.5273321.7334013.962642
22199981.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
24585020.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
24585030.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
25667000.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667010.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667020.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
28772870.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
35963650.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
38911041.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
41716201.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
\n
" }, - "execution_count": 231, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "dest_size_terms.index.values[[0,1,2,3,4]]" + "utilities" ], "metadata": { "collapsed": false, @@ -910,7 +941,10 @@ "cell_type": "code", "execution_count": null, "outputs": [], - "source": [], + "source": [ + "display(sample_size)\n", + "sample_list[2].groupby('person_id').pick_count.sum()" + ], "metadata": { "collapsed": false, "pycharm": { @@ -946,9 +980,34 @@ "cell_type": "code", "execution_count": null, "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 46, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(2, 6)\n", + "[[1 2 3 4 5 6]\n", + " [4 6 5 9 9 6]]\n", + "[5 3]\n" + ] + } + ], "source": [ - "display(sample_size)\n", - "sample_list[2].groupby('person_id').pick_count.sum()" + "utils = np.array([[1,2,3,4,5,6],[4,6,5,9,9,6]])\n", + "print(utils.shape)\n", + "print(utils)\n", + "print(np.argmax(utils, axis=1))" ], "metadata": { "collapsed": false, @@ -959,9 +1018,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 49, + "outputs": [ + { + "ename": "TypeError", + "evalue": "_argmax_dispatcher() got an unexpected keyword argument 'keepdim'", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mTypeError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_10839/398049723.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mutils\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mnp\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0margmax\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mutils\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0maxis\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;36m1\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mkeepdim\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;32m<__array_function__ internals>\u001B[0m in \u001B[0;36margmax\u001B[0;34m(*args, **kwargs)\u001B[0m\n", + "\u001B[0;31mTypeError\u001B[0m: _argmax_dispatcher() got an unexpected keyword argument 'keepdim'" + ] + } + ], + "source": [ + "utils[np.argmax(utils, axis=1, keepdim=True)]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 71, "outputs": [], - "source": [], + "source": [ + "utils[np.arange(2), np.argmax(utils, axis=1)] += 1" + ], "metadata": { "collapsed": false, "pycharm": { @@ -971,7 +1059,53 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 72, + "outputs": [ + { + "data": { + "text/plain": "array([[ 1, 2, 3, 4, 5, 7],\n [ 4, 6, 5, 10, 9, 6]])" + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "utils" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 74, + "outputs": [ + { + "data": { + "text/plain": "array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n 18, 19, 20, 21, 22, 23, 24, 25])" + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dest_size_terms.index.values" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, "outputs": [], "source": [], "metadata": { From 2568b72148f9f2c4be4d5bfe61ea45ce4ccdcff8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 23:00:59 +1000 Subject: [PATCH 082/298] memory work --- activitysim/core/interaction_sample.py | 8 ++++++-- notebooks/frozen_impl_interaction_sample_dev.ipynb | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 79eb15dee6..eeb89b7bca 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -40,19 +40,23 @@ def make_sample_choices_utility_based( # (len(choosers), alternative_count, sample_size) can get very large choices = np.zeros_like(utilities, dtype=np.uint32) zero_dim_index = np.arange(utilities.shape[0]) + #print(f"shape of choices = {choices.shape}, index shape = {zero_dim_index.shape}", flush=True) for i in range(sample_size): rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) # * sample_size #rands = rands.reshape(choice_dimension) rands = inverse_ev1_cdf(rands) - #chunk.log_df(trace_label, 'rands', rands) + #print(f"shape of rands = {rands.shape}", flush=True) ## # use rands ##utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented ##utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) ##utilities += rands #rands += np.repeat(utilities.to_numpy()[:, :, None], sample_size, axis=2) - rands += utilities + rands += utilities.to_numpy() # TODO [janzill Jun2022]: once or for each? + + #print(f"shape of rands which are now utils = {rands.shape}; values are {rands}", flush=True) + #print(f"argmaxs are {np.argmax(rands, axis=1)}", flush=True) # NOT ANYMORE: this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative # NOW: len(choosers) dimensional array diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index 56274754bd..732ffcfe34 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -1059,19 +1059,19 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 77, "outputs": [ { "data": { - "text/plain": "array([[ 1, 2, 3, 4, 5, 7],\n [ 4, 6, 5, 10, 9, 6]])" + "text/plain": "True" }, - "execution_count": 72, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "utils" + "isinstance(utils, np.ndarray)" ], "metadata": { "collapsed": false, From 78630be674d9b382646f89df2d9045204585e0a3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 23:01:39 +1000 Subject: [PATCH 083/298] spelling --- activitysim/core/interaction_sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index eeb89b7bca..8d689b0a38 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -380,7 +380,7 @@ def _interaction_sample( # choices array has same dim as utilities, with values indicating number of counts per chooser and alternative # let's turn the nonzero values into a dataframe - i, j = np.non_zero(choices_array) + i, j = np.nonzero(choices_array) chunk.log_df(trace_label, 'i', i) chunk.log_df(trace_label, 'j', j) From c6d75d126735ab4e7fb0291def7c3172c100ff2b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 23:03:21 +1000 Subject: [PATCH 084/298] delete after last ref, not before --- activitysim/core/interaction_sample.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 8d689b0a38..893180284a 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -375,9 +375,6 @@ def _interaction_sample( ) chunk.log_df(trace_label, 'choices_array', choices_array) - del utilities - chunk.log_df(trace_label, 'utilities', None) - # choices array has same dim as utilities, with values indicating number of counts per chooser and alternative # let's turn the nonzero values into a dataframe i, j = np.nonzero(choices_array) @@ -388,6 +385,9 @@ def _interaction_sample( trace_label=trace_label, trace_choosers=choosers) chunk.log_df(trace_label, 'probs', probs) + del utilities + chunk.log_df(trace_label, 'utilities', None) + choices_df = pd.DataFrame({ alt_col_name: alternatives.index.values[j], "pick_count": choices_array[i, j], From 7a66a67ded39e284de7fcecc7d6b4f436fff2bb0 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 23:10:00 +1000 Subject: [PATCH 085/298] set index --- activitysim/core/interaction_sample.py | 7 +- .../frozen_impl_interaction_sample_dev.ipynb | 73 +++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 893180284a..629abce35c 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -391,7 +391,7 @@ def _interaction_sample( choices_df = pd.DataFrame({ alt_col_name: alternatives.index.values[j], "pick_count": choices_array[i, j], - "prob": probs[i, j], + "prob": probs.to_numpy()[i, j], choosers.index.name: choosers.index.values[i] }) chunk.log_df(trace_label, 'choices_df', choices_df) @@ -462,9 +462,8 @@ def _interaction_sample( choices_df = choices_df[~choices_df['pick_dup']] del choices_df['pick_dup'] - # set index after groupby so we can trace on it - choices_df.set_index(choosers.index.name, inplace=True) - + # set index after groupby so we can trace on it + choices_df.set_index(choosers.index.name, inplace=True) chunk.log_df(trace_label, 'choices_df', choices_df) diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index 732ffcfe34..155de2df9a 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -1103,6 +1103,79 @@ } } }, + { + "cell_type": "code", + "execution_count": 81, + "outputs": [ + { + "data": { + "text/plain": " 0 1 2 3 4 5\n0 1 2 3 4 5 6\n1 4 6 5 9 9 6\n2 1 1 1 1 1 1", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
012345
0123456
1465996
2111111
\n
" + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.DataFrame(np.array([[1,2,3,4,5,6],[4,6,5,9,9,6], [1,1,1,1,1,1]]))\n", + "df" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 82, + "outputs": [ + { + "data": { + "text/plain": " 0 0 1 2\n0 1 1 2 3\n0 1 1 2 3\n1 4 4 6 5\n2 1 1 1 1", + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
0012
01123
01123
14465
21111
\n
" + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.iloc[np.array([0,0,1,2]), np.array([0,0,1,2])]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 84, + "outputs": [ + { + "data": { + "text/plain": "array([1, 1, 6, 1])" + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x_ = np.array([[1,2,3,4,5,6],[4,6,5,9,9,6], [1,1,1,1,1,1]])\n", + "x_[np.array([0,0,1,2]), np.array([0,0,1,2])]" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, { "cell_type": "code", "execution_count": null, From 7df4c8ddb4bee084c32a1f0dfc17ad9b949cc1ba Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 20 Jun 2022 23:36:17 +1000 Subject: [PATCH 086/298] to numpy once --- activitysim/core/interaction_sample.py | 37 +++----------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 629abce35c..f690d4add3 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -36,49 +36,18 @@ def make_sample_choices_utility_based( assert utilities.shape == (len(choosers), alternative_count) #choice_dimension = (len(choosers), alternative_count, sample_size) - # TODO [janzill Jun2022]: THIS NEEDS for loop for memory like previous method, an array of dimension + # Note [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension # (len(choosers), alternative_count, sample_size) can get very large choices = np.zeros_like(utilities, dtype=np.uint32) zero_dim_index = np.arange(utilities.shape[0]) - #print(f"shape of choices = {choices.shape}, index shape = {zero_dim_index.shape}", flush=True) + utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each? for i in range(sample_size): rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) # * sample_size - #rands = rands.reshape(choice_dimension) rands = inverse_ev1_cdf(rands) - #print(f"shape of rands = {rands.shape}", flush=True) - - ## # use rands - ##utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented - ##utilities = np.repeat(utilities[:, :, None], sample_size, axis=2) - ##utilities += rands - #rands += np.repeat(utilities.to_numpy()[:, :, None], sample_size, axis=2) - rands += utilities.to_numpy() # TODO [janzill Jun2022]: once or for each? - - #print(f"shape of rands which are now utils = {rands.shape}; values are {rands}", flush=True) - #print(f"argmaxs are {np.argmax(rands, axis=1)}", flush=True) - - # NOT ANYMORE: this gives us (len(choosers), sample_size) dimensional array, with values the chosen alternative - # NOW: len(choosers) dimensional array - #choices_array = np.argmax(rands, axis=1) - #chunk.log_df(trace_label, 'choices_array', choices_array) - + rands += utils_array choices[zero_dim_index, np.argmax(rands, axis=1)] += 1 - #choosers_index_rep = np.tile(np.arange(0, choices_array.shape[0]), sample_size) - #choices_flattened = choices_array.flatten(order='F') - #probs_selection = probs.to_numpy()[choosers_index_rep, choices_flattened].flatten(order='F') - - # # choices_flattened are 0-based index into alternatives, need to map to alternative values given by - # # alternatives.index.values (they are in this order by construction) - # # explode to one row per chooser.index, alt_zone_id - # choices_df = pd.DataFrame({ - # alt_col_name: alternatives.index.values[choices_flattened], - # 'rand': np.zeros_like(choosers_index_rep), # zero out for now - # 'prob': probs_selection, - # # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction - # choosers.index.name: np.tile(choosers.index.values, sample_size) - # }) return choices From d6f4f97531475c472642fb627b80345efec271a4 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 21 Jun 2022 07:29:51 +1000 Subject: [PATCH 087/298] remove comment --- activitysim/core/interaction_sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index f690d4add3..268b965c25 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -43,7 +43,7 @@ def make_sample_choices_utility_based( utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each? for i in range(sample_size): - rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) # * sample_size + rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) rands = inverse_ev1_cdf(rands) rands += utils_array choices[zero_dim_index, np.argmax(rands, axis=1)] += 1 From 26a7090218577a95c3b8afd6d220517069fb6372 Mon Sep 17 00:00:00 2001 From: janzill Date: Tue, 21 Jun 2022 13:59:47 +1000 Subject: [PATCH 088/298] interaction sample dev --- .../frozen_impl_interaction_sample_dev.ipynb | 1966 +++++++++++++---- 1 file changed, 1513 insertions(+), 453 deletions(-) diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index 155de2df9a..e05e520bee 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -2,35 +2,30 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.385153Z", - "iopub.status.busy": "2022-05-01T10:17:57.384881Z", - "iopub.status.idle": "2022-05-01T10:17:57.534433Z", - "shell.execute_reply": "2022-05-01T10:17:57.533096Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" - }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 8, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.536623Z", - "iopub.status.busy": "2022-05-01T10:17:57.536012Z", - "iopub.status.idle": "2022-05-01T10:17:57.542755Z", - "shell.execute_reply": "2022-05-01T10:17:57.541685Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" - }, "tags": [] }, "outputs": [], @@ -40,16 +35,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:00.350944Z", - "iopub.status.busy": "2022-05-01T10:18:00.350730Z", - "iopub.status.idle": "2022-05-01T10:18:12.760977Z", - "shell.execute_reply": "2022-05-01T10:18:12.760013Z", - "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" - }, "tags": [] }, "outputs": [], @@ -67,228 +55,34 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 10, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:16.944537Z", - "iopub.status.busy": "2022-05-01T10:18:16.944291Z", - "iopub.status.idle": "2022-05-01T10:18:17.124764Z", - "shell.execute_reply": "2022-05-01T10:18:17.123725Z", - "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" - }, "tags": [] }, "outputs": [], "source": [ - "pd.set_option(\"max_columns\", 500)" + "pd.set_option(\"display.max_columns\", 500)" ] }, { "cell_type": "code", - "execution_count": 5, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(3, 3, 2)\n", - "[[2 2]\n", - " [2 1]\n", - " [1 0]]\n" - ] - }, - { - "data": { - "text/plain": "array([2, 2, 1, 2, 1, 0])" - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "utils = np.array([[1,2,3,4,5,6],[4,6,5,9,9,6],[7,8,9,1,2,3]]).reshape((3,3,2))\n", - "print(utils.shape)\n", - "print(np.argmax(utils, axis=1))\n", - "np.argmax(utils, axis=1).flatten(order=\"F\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 124, - "outputs": [ - { - "data": { - "text/plain": "array([[2, 2],\n [2, 1],\n [1, 0]])" - }, - "execution_count": 124, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ch_array = np.argmax(utils, axis=1)\n", - "ch_array" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 191, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(3, 4)\n" - ] - }, - { - "data": { - "text/plain": "array([[0.1 , 0.7 , 0.1 , 0.1 ],\n [0. , 0.5 , 0.25, 0.25],\n [0.3 , 0.3 , 0.2 , 0.2 ]])" - }, - "execution_count": 191, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "probs = np.array([[0.1,0.7,0.1,0.1], [0.0,0.5,0.25,0.25], [0.3,0.3,0.2,0.2]])\n", - "print(probs.shape)\n", - "probs" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 174, - "outputs": [ - { - "data": { - "text/plain": "(3, 4, 3)" - }, - "execution_count": 174, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.repeat(probs[:,:,None], 3, axis=2).shape" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 198, - "outputs": [ - { - "data": { - "text/plain": "array([0, 0, 1, 1, 2, 2])" - }, - "execution_count": 198, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.repeat(np.arange(0,probs.shape[0]), 2)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 202, - "outputs": [ - { - "data": { - "text/plain": "array([0, 1, 2, 0, 1, 2, 0, 1, 2])" - }, - "execution_count": 202, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.tile(np.arange(0,probs.shape[0]), 3)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 6, + "execution_count": 15, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:17.839947Z", - "iopub.status.busy": "2022-05-01T10:18:17.839070Z", - "iopub.status.idle": "2022-05-01T10:18:18.019676Z", - "shell.execute_reply": "2022-05-01T10:18:18.018689Z", - "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" - }, "tags": [] }, "outputs": [], "source": [ - "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_example_mtc_frozen_rand\")" + "root_dir = \"/home/jan/code/activitysim\"\n", + "example_dir = os.path.join(root_dir, \"test_runs\", \"seq_asim\")" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 16, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:18.894533Z", - "iopub.status.busy": "2022-05-01T10:18:18.894303Z", - "iopub.status.idle": "2022-05-01T10:18:19.078807Z", - "shell.execute_reply": "2022-05-01T10:18:19.077951Z", - "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" - }, "tags": [] }, "outputs": [], @@ -298,23 +92,16 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 18, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:22.111723Z", - "iopub.status.busy": "2022-05-01T10:18:22.111490Z", - "iopub.status.idle": "2022-05-01T10:18:22.297437Z", - "shell.execute_reply": "2022-05-01T10:18:22.296501Z", - "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" - }, "tags": [] }, "outputs": [], "source": [ "parser = argparse.ArgumentParser()\n", "run.add_run_args(parser)\n", - "args = parser.parse_args(['-c', 'configs', '-o', 'output_without_before', '-d', 'data'])\n", + "args = parser.parse_args(['-c', 'configs_lessmodes', '-o', 'output_fru', '-d', 'data'])\n", "#run.run(args) # 2mins full example run\n", "if not inject.is_injectable('preload_injectables'):\n", " from activitysim import abm # register abm steps and other abm-specific injectables\n", @@ -323,7 +110,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 19, + "id": "9955cc9c", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [], "source": [ "from activitysim.core import inject\n", @@ -336,17 +133,21 @@ "from activitysim.core import logit\n", "from activitysim.core.simulate import set_skim_wrapper_targets\n", "from activitysim.core import chunk" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "3a923505", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 10, + }, "outputs": [], "source": [ "from activitysim.core.logit import inverse_ev1_cdf\n", @@ -481,17 +282,175 @@ " })\n", "\n", " return choices_df" - ], + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7599d0d1-9063-48aa-95de-cef45e926a42", + "metadata": {}, + "outputs": [], + "source": [ + "from activitysim.core.logit import inverse_ev1_cdf\n", + "\n", + "def make_sample_choices_dev(\n", + " choosers, probs,\n", + " alternatives,\n", + " sample_size, alternative_count, alt_col_name,\n", + " allow_zero_probs,\n", + " trace_label,\n", + " utilities=None,\n", + " choose_individual_max_utility=False\n", + "):\n", + " assert isinstance(probs, pd.DataFrame)\n", + " assert probs.shape == (len(choosers), alternative_count)\n", + " assert isinstance(alternatives, pd.DataFrame)\n", + " assert len(alternatives) == alternative_count\n", + "\n", + " if allow_zero_probs:\n", + " zero_probs = (probs.sum(axis=1) == 0)\n", + " if zero_probs.all():\n", + " return pd.DataFrame(columns=[alt_col_name, 'rand', 'prob', choosers.index.name])\n", + " if zero_probs.any():\n", + " # remove from sample\n", + " probs = probs[~zero_probs]\n", + " choosers = choosers[~zero_probs]\n", + " # TODO [janzill Jun2022]: do we want this for consistency?\n", + " # might need this in other places too?\n", + " if utilities is not None:\n", + " utilities = utilities[~zero_probs]\n", + "\n", + "\n", + " if choose_individual_max_utility:\n", + " # Note [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension\n", + " # (len(choosers), alternative_count, sample_size) can get very large\n", + " choices_array = np.zeros_like(utilities, dtype=np.uint32)\n", + " zero_dim_index = np.arange(utilities.shape[0])\n", + "\n", + " utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each?\n", + " for i in range(sample_size):\n", + " rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) # TODO: once?\n", + " rands = inverse_ev1_cdf(rands)\n", + " rands += utils_array\n", + " choices_array[zero_dim_index, np.argmax(rands, axis=1)] += 1\n", + "\n", + " i, j = np.nonzero(choices_array)\n", + " chunk.log_df(trace_label, 'i', i)\n", + " chunk.log_df(trace_label, 'j', j)\n", + "\n", + " probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", + " trace_label=trace_label, trace_choosers=choosers)\n", + " del utilities\n", + " choices_df = pd.DataFrame({\n", + " alt_col_name: alternatives.index.values[j],\n", + " \"pick_count\": choices_array[i, j],\n", + " \"prob\": probs.to_numpy()[i, j],\n", + " choosers.index.name: choosers.index.values[i]\n", + " })\n", + " del choices_array\n", + " del i\n", + " del j\n", + " del probs\n", + " \n", + " else:\n", + " cum_probs_array = probs.values.cumsum(axis=1)\n", + " # alt probs in convenient layout to return prob of chose alternative\n", + " # (same layout as cum_probs_arr)\n", + " alt_probs_array = probs.values.flatten()\n", + " # get sample_size rands for each chooser\n", + " rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size)\n", + " # transform as we iterate over alternatives\n", + " # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr\n", + " # i.e rands[i] is a 2-D array of one alt choice rand for each chooser\n", + " rands = rands.T.reshape(sample_size, -1, 1)\n", + " # the alternative value chosen\n", + " choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype)\n", + " # chunk log these later after we populate them...\n", + " # the probability of the chosen alternative\n", + " choice_probs_array = np.empty([sample_size, len(choosers)])\n", + " # chunk log these later after we populate them...\n", + " alts = np.tile(alternatives.index.values, len(choosers))\n", + " # FIXME - do this all at once rather than iterate?\n", + " for i in range(sample_size):\n", + " # FIXME - do this in numpy, not pandas?\n", + " # rands for this alt in broadcastable shape\n", + " r = rands[i]\n", + "\n", + " # position of first occurrence of positive value\n", + " positions = np.argmax(cum_probs_array > r, axis=1)\n", + "\n", + " # FIXME - leave positions as numpy array, not pandas series?\n", + " # positions is series with the chosen alternative represented as a column index in probs\n", + " # which is an integer between zero and num alternatives in the alternative sample\n", + " positions = pd.Series(positions, index=probs.index)\n", + "\n", + " # need to get from an integer offset into the alternative sample to the alternative index\n", + " # that is, we want the index value of the row that is offset by rows into the\n", + " # tranche of this choosers alternatives created by cross join of alternatives and choosers\n", + "\n", + " # offsets is the offset into model_design df of first row of chooser alternatives\n", + " offsets = np.arange(len(positions)) * alternative_count\n", + "\n", + " # choices and choice_probs have one element per chooser and is in same order as choosers\n", + " choices_array[i] = np.take(alts, positions + offsets)\n", + " choice_probs_array[i] = np.take(alt_probs_array, positions + offsets)\n", + " del positions\n", + " del offsets\n", + "\n", + " del alts\n", + " del cum_probs_array\n", + " del alt_probs_array\n", + "\n", + " # explode to one row per chooser.index, alt_zone_id\n", + " choices_df = pd.DataFrame(\n", + " {alt_col_name: choices_array.flatten(order='F'),\n", + " 'rand': rands.flatten(order='F'),\n", + " 'prob': choice_probs_array.flatten(order='F'),\n", + " choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", + " })\n", + "\n", + " # pick_count and pick_dup\n", + " # pick_count is number of duplicate picks\n", + " # pick_dup flag is True for all but first of duplicates\n", + " pick_group = choices_df.groupby([choosers.index.name, alt_col_name])\n", + " # number each item in each group from 0 to the length of that group - 1.\n", + " choices_df['pick_count'] = pick_group.cumcount(ascending=True)\n", + " # flag duplicate rows after first\n", + " choices_df['pick_dup'] = choices_df['pick_count'] > 0\n", + " # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", + " choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", + " # drop the duplicates\n", + " choices_df = choices_df[~choices_df['pick_dup']]\n", + " del choices_df['pick_dup']\n", + " # set index after groupby so we can trace on it\n", + " choices_df.set_index(choosers.index.name, inplace=True)\n", + " # don't need this after tracing\n", + " del choices_df['rand']\n", + " \n", + " return choices_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dec58ae1-e15d-498c-82e4-ba7929aea7c7", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1611fe26", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 11, + }, "outputs": [ { "name": "stderr", @@ -571,9 +530,9 @@ "\n", "choose_individual_max_utility = True\n", "\n", - "resume_after = \"compute_accessibility\"\n", + "resume_after = \"work_from_home\"\n", "model_name = \"school_location\"\n", - "chunk_size = 0 # test_mtc means no chunking\n", + "chunk_size = 0 # no chunking\n", "\n", "pipeline.open_pipeline(resume_after)\n", "# preload any bulky injectables (e.g. skims) not in pipeline\n", @@ -721,7 +680,7 @@ " trace_label=chunk_trace_label, trace_choosers=chooser_chunk)\n", " #del utilities\n", "\n", - " temp_choices = hack_make_sample_choices(\n", + " choices_df = make_sample_choices_dev(\n", " chooser_chunk, probs, dest_size_terms,\n", " sample_size, alternative_count, alt_col_name,\n", " allow_zero_probs=allow_zero_probs,\n", @@ -729,32 +688,11 @@ " utilities=utilities,\n", " choose_individual_max_utility=choose_individual_max_utility\n", " )\n", - "\n", - " choices_df = temp_choices.copy()\n", - "\n", - " # pick_count and pick_dup\n", - " # pick_count is number of duplicate picks\n", - " # pick_dup flag is True for all but first of duplicates\n", - " pick_group = choices_df.groupby([choosers.index.name, alt_col_name])\n", - " # number each item in each group from 0 to the length of that group - 1.\n", - " choices_df['pick_count'] = pick_group.cumcount(ascending=True)\n", - " # flag duplicate rows after first\n", - " choices_df['pick_dup'] = choices_df['pick_count'] > 0\n", - " # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", - " choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", - " # drop the duplicates\n", - " choices_df = choices_df[~choices_df['pick_dup']]\n", - " del choices_df['pick_dup']\n", - " # set index after groupby so we can trace on it\n", - " choices_df.set_index(choosers.index.name, inplace=True)\n", - " # don't need this after tracing\n", - " del choices_df['rand']\n", " # - NARROW\n", " choices_df['prob'] = choices_df['prob'].astype(np.float32)\n", " assert (choices_df['pick_count'].max() < 4294967295) or (choices_df.empty)\n", " choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32)\n", "\n", - "\n", " if choices_df.shape[0] > 0:\n", " result_list.append(choices_df)\n", "\n", @@ -777,22 +715,148 @@ " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", "\n", " pipeline.close_pipeline()" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "783732f3", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 12, + }, "outputs": [ { "data": { - "text/plain": " alt_dest rand prob person_id\n0 10 0 0.205884 386008\n1 10 0 0.288773 418442\n2 21 0 0.147571 595686\n3 3 0 0.005248 644292\n4 8 0 0.117232 644478\n.. ... ... ... ...\n420 25 0 0.265744 2566702\n421 10 0 0.288773 2877287\n422 9 0 0.336708 3596365\n423 6 0 0.032124 3891104\n424 10 0 0.097048 4171620\n\n[425 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n

425 rows × 4 columns

\n
" + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n", + "

425 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " alt_dest rand prob person_id\n", + "0 10 0 0.205884 386008\n", + "1 10 0 0.288773 418442\n", + "2 21 0 0.147571 595686\n", + "3 3 0 0.005248 644292\n", + "4 8 0 0.117232 644478\n", + ".. ... ... ... ...\n", + "420 25 0 0.265744 2566702\n", + "421 10 0 0.288773 2877287\n", + "422 9 0 0.336708 3596365\n", + "423 6 0 0.032124 3891104\n", + "424 10 0 0.097048 4171620\n", + "\n", + "[425 rows x 4 columns]" + ] }, "execution_count": 12, "metadata": {}, @@ -801,17 +865,21 @@ ], "source": [ "temp_choices #.sort_values(by=[\"person_id\"])" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "e7be6961", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 28, + }, "outputs": [], "source": [ "test_df = temp_choices.copy()\n", @@ -831,22 +899,143 @@ "# set index after groupby so we can trace on it\n", "test_df.set_index(choosers.index.name, inplace=True)\n", "del test_df['rand']" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "64053012", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 29, + }, "outputs": [ { "data": { - "text/plain": " alt_dest prob pick_count\nperson_id \n386008 10 0.205884 4\n418442 10 0.288773 9\n595686 21 0.147571 4\n644292 3 0.005248 1\n644478 8 0.117232 4\n... ... ... ...\n644478 6 0.032124 1\n2458502 25 0.032621 1\n2458503 11 0.078812 1\n2566700 16 0.036706 1\n2566701 18 0.014321 1\n\n[165 rows x 3 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destprobpick_count
person_id
386008100.2058844
418442100.2887739
595686210.1475714
64429230.0052481
64447880.1172324
............
64447860.0321241
2458502250.0326211
2458503110.0788121
2566700160.0367061
2566701180.0143211
\n

165 rows × 3 columns

\n
" + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alt_destprobpick_count
person_id
386008100.2058844
418442100.2887739
595686210.1475714
64429230.0052481
64447880.1172324
............
64447860.0321241
2458502250.0326211
2458503110.0788121
2566700160.0367061
2566701180.0143211
\n", + "

165 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " alt_dest prob pick_count\n", + "person_id \n", + "386008 10 0.205884 4\n", + "418442 10 0.288773 9\n", + "595686 21 0.147571 4\n", + "644292 3 0.005248 1\n", + "644478 8 0.117232 4\n", + "... ... ... ...\n", + "644478 6 0.032124 1\n", + "2458502 25 0.032621 1\n", + "2458503 11 0.078812 1\n", + "2566700 16 0.036706 1\n", + "2566701 18 0.014321 1\n", + "\n", + "[165 rows x 3 columns]" + ] }, "execution_count": 29, "metadata": {}, @@ -855,22 +1044,143 @@ ], "source": [ "test_df" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "2b75344c", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 39, + }, "outputs": [ { "data": { - "text/plain": " alt_dest prob pick_count\nperson_id \n3596365 9 0.336708 13\n386008 9 0.336708 12\n2877287 10 0.288773 10\n1958678 9 0.336708 10\n2458502 8 0.301414 9\n... ... ... ...\n2458503 11 0.078812 1\n644292 3 0.005248 1\n2458502 10 0.103895 1\n3891104 10 0.097048 1\n2566702 11 0.061290 1\n\n[165 rows x 3 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destprobpick_count
person_id
359636590.33670813
38600890.33670812
2877287100.28877310
195867890.33670810
245850280.3014149
............
2458503110.0788121
64429230.0052481
2458502100.1038951
3891104100.0970481
2566702110.0612901
\n

165 rows × 3 columns

\n
" + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alt_destprobpick_count
person_id
359636590.33670813
38600890.33670812
2877287100.28877310
195867890.33670810
245850280.3014149
............
2458503110.0788121
64429230.0052481
2458502100.1038951
3891104100.0970481
2566702110.0612901
\n", + "

165 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " alt_dest prob pick_count\n", + "person_id \n", + "3596365 9 0.336708 13\n", + "386008 9 0.336708 12\n", + "2877287 10 0.288773 10\n", + "1958678 9 0.336708 10\n", + "2458502 8 0.301414 9\n", + "... ... ... ...\n", + "2458503 11 0.078812 1\n", + "644292 3 0.005248 1\n", + "2458502 10 0.103895 1\n", + "3891104 10 0.097048 1\n", + "2566702 11 0.061290 1\n", + "\n", + "[165 rows x 3 columns]" + ] }, "execution_count": 39, "metadata": {}, @@ -881,22 +1191,148 @@ "test_df = temp_choices.copy()\n", "del test_df['rand']\n", "test_df.value_counts().to_frame(\"pick_count\").reset_index([\"prob\", \"alt_dest\"])" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "504b03dc", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 31, + }, "outputs": [ { "data": { - "text/plain": " alt_dest rand prob person_id\n0 10 0 0.205884 386008\n1 10 0 0.288773 418442\n2 21 0 0.147571 595686\n3 3 0 0.005248 644292\n4 8 0 0.117232 644478\n.. ... ... ... ...\n420 25 0 0.265744 2566702\n421 10 0 0.288773 2877287\n422 9 0 0.336708 3596365\n423 6 0 0.032124 3891104\n424 10 0 0.097048 4171620\n\n[425 rows x 4 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n

425 rows × 4 columns

\n
" + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n", + "

425 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " alt_dest rand prob person_id\n", + "0 10 0 0.205884 386008\n", + "1 10 0 0.288773 418442\n", + "2 21 0 0.147571 595686\n", + "3 3 0 0.005248 644292\n", + "4 8 0 0.117232 644478\n", + ".. ... ... ... ...\n", + "420 25 0 0.265744 2566702\n", + "421 10 0 0.288773 2877287\n", + "422 9 0 0.336708 3596365\n", + "423 6 0 0.032124 3891104\n", + "424 10 0 0.097048 4171620\n", + "\n", + "[425 rows x 4 columns]" + ] }, "execution_count": 31, "metadata": {}, @@ -905,22 +1341,680 @@ ], "source": [ "test_df" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "f332d85e", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 40, + }, "outputs": [ { "data": { - "text/plain": " 0 1 2 3 4 5 \\\nperson_id \n386008 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n418442 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n595686 0.363442 1.313932 2.021662 1.599651 3.201645 3.726050 \n644292 0.426142 1.419508 2.267947 1.796678 3.710634 4.948799 \n644478 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n1958678 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n2159059 0.044242 0.994732 1.702462 1.151822 2.664408 3.617750 \n2219998 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n2458502 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n2458503 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n2566700 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n2566701 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n2566702 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n2877287 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n3596365 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n3891104 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n4171620 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n\n 6 7 8 9 10 11 \\\nperson_id \n386008 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n418442 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n595686 4.560310 5.610758 5.254657 5.202408 5.385828 1.836706 \n644292 5.808154 5.939138 5.530123 5.169570 5.057449 1.508326 \n644478 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n1958678 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n2159059 4.338439 5.249540 5.661475 5.941263 5.073867 1.056589 \n2219998 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n2458502 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n2458503 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n2566700 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n2566701 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n2566702 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n2877287 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n3596365 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n3891104 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n4171620 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n\n 12 13 14 15 16 17 \\\nperson_id \n386008 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n418442 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n595686 -0.149232 0.604582 0.207110 4.469041 3.740891 3.991371 \n644292 -0.379098 0.498325 0.133010 3.810490 3.312458 3.137154 \n644478 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n1958678 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n2159059 -0.719988 0.258925 -0.106390 3.844690 3.346658 3.301773 \n2219998 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n2458502 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n2458503 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n2566700 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n2566701 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n2566702 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n2877287 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n3596365 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n3891104 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n4171620 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n\n 18 19 20 21 22 23 \\\nperson_id \n386008 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n418442 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n595686 3.145862 4.005500 5.488831 1.780972 0.846532 2.046901 \n644292 2.751629 3.584558 4.421596 1.843672 0.909232 2.115301 \n644478 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n1958678 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n2159059 3.572756 4.974221 4.914166 1.461772 0.527332 1.733401 \n2219998 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n2458502 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n2458503 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n2566700 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n2566701 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n2566702 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n2877287 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n3596365 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n3891104 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n4171620 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n\n 24 \nperson_id \n386008 4.099442 \n418442 4.042442 \n595686 4.184942 \n644292 4.430042 \n644478 4.276142 \n1958678 4.099442 \n2159059 3.962642 \n2219998 4.276142 \n2458502 4.355942 \n2458503 4.355942 \n2566700 5.864195 \n2566701 5.864195 \n2566702 5.864195 \n2877287 4.042442 \n3596365 4.099442 \n3891104 4.276142 \n4171620 4.276142 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
0123456789101112131415161718192021222324
person_id
3860080.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
4184420.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
5956860.3634421.3139322.0216621.5996513.2016453.7260504.5603105.6107585.2546575.2024085.3858281.836706-0.1492320.6045820.2071104.4690413.7408913.9913713.1458624.0055005.4888311.7809720.8465322.0469014.184942
6442920.4261421.4195082.2679471.7966783.7106344.9487995.8081545.9391385.5301235.1695705.0574491.508326-0.3790980.4983250.1330103.8104903.3124583.1371542.7516293.5845584.4215961.8436720.9092322.1153014.430042
6444781.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
19586780.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
21590590.0442420.9947321.7024621.1518222.6644083.6177504.3384395.2495405.6614755.9412635.0738671.056589-0.7199880.258925-0.1063903.8446903.3466583.3017733.5727564.9742214.9141661.4617720.5273321.7334013.962642
22199981.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
24585020.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
24585030.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
25667000.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667010.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667020.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
28772870.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
35963650.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
38911041.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
41716201.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
\n
" + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
0123456789101112131415161718192021222324
person_id
3860080.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
4184420.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
5956860.3634421.3139322.0216621.5996513.2016453.7260504.5603105.6107585.2546575.2024085.3858281.836706-0.1492320.6045820.2071104.4690413.7408913.9913713.1458624.0055005.4888311.7809720.8465322.0469014.184942
6442920.4261421.4195082.2679471.7966783.7106344.9487995.8081545.9391385.5301235.1695705.0574491.508326-0.3790980.4983250.1330103.8104903.3124583.1371542.7516293.5845584.4215961.8436720.9092322.1153014.430042
6444781.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
19586780.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
21590590.0442420.9947321.7024621.1518222.6644083.6177504.3384395.2495405.6614755.9412635.0738671.056589-0.7199880.258925-0.1063903.8446903.3466583.3017733.5727564.9742214.9141661.4617720.5273321.7334013.962642
22199981.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
24585020.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
24585030.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
25667000.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667010.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667020.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
28772870.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
35963650.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
38911041.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
41716201.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
\n", + "
" + ], + "text/plain": [ + " 0 1 2 3 4 5 \\\n", + "person_id \n", + "386008 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n", + "418442 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n", + "595686 0.363442 1.313932 2.021662 1.599651 3.201645 3.726050 \n", + "644292 0.426142 1.419508 2.267947 1.796678 3.710634 4.948799 \n", + "644478 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", + "1958678 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n", + "2159059 0.044242 0.994732 1.702462 1.151822 2.664408 3.617750 \n", + "2219998 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", + "2458502 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n", + "2458503 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n", + "2566700 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n", + "2566701 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n", + "2566702 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n", + "2877287 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n", + "3596365 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n", + "3891104 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", + "4171620 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", + "\n", + " 6 7 8 9 10 11 \\\n", + "person_id \n", + "386008 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n", + "418442 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n", + "595686 4.560310 5.610758 5.254657 5.202408 5.385828 1.836706 \n", + "644292 5.808154 5.939138 5.530123 5.169570 5.057449 1.508326 \n", + "644478 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", + "1958678 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n", + "2159059 4.338439 5.249540 5.661475 5.941263 5.073867 1.056589 \n", + "2219998 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", + "2458502 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n", + "2458503 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n", + "2566700 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n", + "2566701 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n", + "2566702 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n", + "2877287 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n", + "3596365 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n", + "3891104 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", + "4171620 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", + "\n", + " 12 13 14 15 16 17 \\\n", + "person_id \n", + "386008 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n", + "418442 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n", + "595686 -0.149232 0.604582 0.207110 4.469041 3.740891 3.991371 \n", + "644292 -0.379098 0.498325 0.133010 3.810490 3.312458 3.137154 \n", + "644478 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", + "1958678 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n", + "2159059 -0.719988 0.258925 -0.106390 3.844690 3.346658 3.301773 \n", + "2219998 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", + "2458502 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n", + "2458503 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n", + "2566700 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n", + "2566701 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n", + "2566702 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n", + "2877287 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n", + "3596365 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n", + "3891104 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", + "4171620 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", + "\n", + " 18 19 20 21 22 23 \\\n", + "person_id \n", + "386008 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n", + "418442 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n", + "595686 3.145862 4.005500 5.488831 1.780972 0.846532 2.046901 \n", + "644292 2.751629 3.584558 4.421596 1.843672 0.909232 2.115301 \n", + "644478 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", + "1958678 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n", + "2159059 3.572756 4.974221 4.914166 1.461772 0.527332 1.733401 \n", + "2219998 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", + "2458502 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n", + "2458503 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n", + "2566700 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n", + "2566701 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n", + "2566702 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n", + "2877287 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n", + "3596365 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n", + "3891104 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", + "4171620 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", + "\n", + " 24 \n", + "person_id \n", + "386008 4.099442 \n", + "418442 4.042442 \n", + "595686 4.184942 \n", + "644292 4.430042 \n", + "644478 4.276142 \n", + "1958678 4.099442 \n", + "2159059 3.962642 \n", + "2219998 4.276142 \n", + "2458502 4.355942 \n", + "2458503 4.355942 \n", + "2566700 5.864195 \n", + "2566701 5.864195 \n", + "2566702 5.864195 \n", + "2877287 4.042442 \n", + "3596365 4.099442 \n", + "3891104 4.276142 \n", + "4171620 4.276142 " + ] }, "execution_count": 40, "metadata": {}, @@ -929,68 +2023,88 @@ ], "source": [ "utilities" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [ - "display(sample_size)\n", - "sample_list[2].groupby('person_id').pick_count.sum()" - ], + "id": "c582bf26", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [ + "display(sample_size)\n", + "sample_list[2].groupby('person_id').pick_count.sum()" + ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [], + "id": "de698ddc", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [], + "id": "0c8cb9b5", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [], + "id": "0301d604", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [] }, { "cell_type": "code", "execution_count": 46, + "id": "60fb8dac", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "pycharm": { + "name": "#%%\n" + } + }, "outputs": [ { "name": "stdout", @@ -1008,190 +2122,136 @@ "print(utils.shape)\n", "print(utils)\n", "print(np.argmax(utils, axis=1))" - ], + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "d43aeee6", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 49, + }, "outputs": [ { "ename": "TypeError", "evalue": "_argmax_dispatcher() got an unexpected keyword argument 'keepdim'", "output_type": "error", "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mTypeError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m/tmp/ipykernel_10839/398049723.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mutils\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mnp\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0margmax\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mutils\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0maxis\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;36m1\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mkeepdim\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", - "\u001B[0;32m<__array_function__ internals>\u001B[0m in \u001B[0;36margmax\u001B[0;34m(*args, **kwargs)\u001B[0m\n", - "\u001B[0;31mTypeError\u001B[0m: _argmax_dispatcher() got an unexpected keyword argument 'keepdim'" + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_10839/398049723.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mutils\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0margmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mutils\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkeepdim\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m<__array_function__ internals>\u001b[0m in \u001b[0;36margmax\u001b[0;34m(*args, **kwargs)\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: _argmax_dispatcher() got an unexpected keyword argument 'keepdim'" ] } ], "source": [ - "utils[np.argmax(utils, axis=1, keepdim=True)]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + "utils[np.argmax(utils, axis=1)]" + ] }, { "cell_type": "code", "execution_count": 71, - "outputs": [], - "source": [ - "utils[np.arange(2), np.argmax(utils, axis=1)] += 1" - ], + "id": "85892d45", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 77, - "outputs": [ - { - "data": { - "text/plain": "True" - }, - "execution_count": 77, - "metadata": {}, - "output_type": "execute_result" - } - ], + }, + "outputs": [], "source": [ - "isinstance(utils, np.ndarray)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + "utils[np.arange(2), np.argmax(utils, axis=1)] += 1" + ] }, { "cell_type": "code", - "execution_count": 74, - "outputs": [ - { - "data": { - "text/plain": "array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n 18, 19, 20, 21, 22, 23, 24, 25])" - }, - "execution_count": 74, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dest_size_terms.index.values" - ], + "execution_count": 77, + "id": "477c7f3d", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 81, + }, "outputs": [ { "data": { - "text/plain": " 0 1 2 3 4 5\n0 1 2 3 4 5 6\n1 4 6 5 9 9 6\n2 1 1 1 1 1 1", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
012345
0123456
1465996
2111111
\n
" + "text/plain": [ + "True" + ] }, - "execution_count": 81, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df = pd.DataFrame(np.array([[1,2,3,4,5,6],[4,6,5,9,9,6], [1,1,1,1,1,1]]))\n", - "df" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + "isinstance(utils, np.ndarray)" + ] }, { "cell_type": "code", - "execution_count": 82, - "outputs": [ - { - "data": { - "text/plain": " 0 0 1 2\n0 1 1 2 3\n0 1 1 2 3\n1 4 4 6 5\n2 1 1 1 1", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
0012
01123
01123
14465
21111
\n
" - }, - "execution_count": 82, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.iloc[np.array([0,0,1,2]), np.array([0,0,1,2])]" - ], + "execution_count": 74, + "id": "a576a8a0", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": 84, + }, "outputs": [ { "data": { - "text/plain": "array([1, 1, 6, 1])" + "text/plain": [ + "array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n", + " 18, 19, 20, 21, 22, 23, 24, 25])" + ] }, - "execution_count": 84, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "x_ = np.array([[1,2,3,4,5,6],[4,6,5,9,9,6], [1,1,1,1,1,1]])\n", - "x_[np.array([0,0,1,2]), np.array([0,0,1,2])]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + "dest_size_terms.index.values" + ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [], + "id": "72c709ab", "metadata": { "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, "pycharm": { "name": "#%%\n" } - } + }, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1205,9 +2265,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} From 313bcd0926bd955a48aa59d046561762812fb60b Mon Sep 17 00:00:00 2001 From: janzill Date: Tue, 21 Jun 2022 15:16:51 +1000 Subject: [PATCH 089/298] remove scale and location params from ev1 quartile fct --- activitysim/core/interaction_sample.py | 4 +- activitysim/core/logit.py | 7 +- .../frozen_impl_interaction_sample_dev.ipynb | 2486 +++++++---------- 3 files changed, 942 insertions(+), 1555 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 268b965c25..d3838dde31 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -44,9 +44,7 @@ def make_sample_choices_utility_based( utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each? for i in range(sample_size): rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) - rands = inverse_ev1_cdf(rands) - rands += utils_array - choices[zero_dim_index, np.argmax(rands, axis=1)] += 1 + choices[zero_dim_index, np.argmax(inverse_ev1_cdf(rands) + utils_array, axis=1)] += 1 return choices diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 37c70da6a3..a79be72041 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -183,13 +183,14 @@ def utils_to_probs(utils, trace_label=None, exponentiated=False, allow_zero_prob return probs - -def inverse_ev1_cdf(x, location=0.0, scale=1.0): +# OPTIMISE for runtime in interaction_sample - do not need scale nor location +def inverse_ev1_cdf(x): #, location=0.0, scale=1.0): # quantile function of EV1 # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution (and biogeme and larch convention) where the scale # is proportional to variance (not variance^{-1}). this means nested scales are between 0 and 1. # x can be number or np array or pd df for vecops - return location - scale * np.log(-np.log(x)) + #return location - scale * np.log(-np.log(x)) + return -np.log(-np.log(x)) # def add_ev1_random(df, nest_spec): diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index e05e520bee..cf210fcccd 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -2,28 +2,19 @@ "cells": [ { "cell_type": "code", - "execution_count": 7, + "execution_count": 1, "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", "metadata": { "tags": [] }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], + "outputs": [], "source": [ "%load_ext autoreload" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 2, "id": "c3dba451-1e10-403e-8614-35d57e6577f4", "metadata": { "tags": [] @@ -35,7 +26,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 3, "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", "metadata": { "tags": [] @@ -55,7 +46,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 4, "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", "metadata": { "tags": [] @@ -67,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 5, "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", "metadata": { "tags": [] @@ -80,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 6, "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", "metadata": { "tags": [] @@ -92,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 7, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "tags": [] @@ -110,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 8, "id": "9955cc9c", "metadata": { "collapsed": false, @@ -137,7 +128,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 19, "id": "3a923505", "metadata": { "collapsed": false, @@ -286,13 +277,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "id": "7599d0d1-9063-48aa-95de-cef45e926a42", "metadata": {}, "outputs": [], "source": [ - "from activitysim.core.logit import inverse_ev1_cdf\n", - "\n", "def make_sample_choices_dev(\n", " choosers, probs,\n", " alternatives,\n", @@ -329,17 +318,15 @@ "\n", " utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each?\n", " for i in range(sample_size):\n", - " rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) # TODO: once?\n", - " rands = inverse_ev1_cdf(rands)\n", - " rands += utils_array\n", - " choices_array[zero_dim_index, np.argmax(rands, axis=1)] += 1\n", + " rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count)\n", + " choices_array[zero_dim_index, np.argmax(utils_array - np.log(-np.log(rands)), axis=1)] += 1\n", "\n", " i, j = np.nonzero(choices_array)\n", " chunk.log_df(trace_label, 'i', i)\n", " chunk.log_df(trace_label, 'j', j)\n", "\n", - " probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", - " trace_label=trace_label, trace_choosers=choosers)\n", + " #probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", + " # trace_label=trace_label, trace_choosers=choosers)\n", " del utilities\n", " choices_df = pd.DataFrame({\n", " alt_col_name: alternatives.index.values[j],\n", @@ -350,7 +337,7 @@ " del choices_array\n", " del i\n", " del j\n", - " del probs\n", + " # del probs\n", " \n", " else:\n", " cum_probs_array = probs.values.cumsum(axis=1)\n", @@ -430,14 +417,6 @@ " return choices_df" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "dec58ae1-e15d-498c-82e4-ba7929aea7c7", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 11, @@ -464,64 +443,16 @@ "output_type": "stream", "text": [ "running university, 3\n", - "dropping 19 of 25 rows where size_term is zero\n", - "Running school_location with 17 persons\n", - "(17, 6)\n", - "(102,)\n", - "(17, 6)\n", - " 0 1 2 3 4 5\n", - "person_id \n", - "325623 0.001874 0.005184 0.002695 0.299427 0.508256 0.182563\n", - "386007 0.001584 0.724879 0.145533 0.080880 0.041571 0.005553\n", - "1774265 0.019349 0.146800 0.038209 0.486232 0.273819 0.035591\n", + "dropping 4288 of 4380 rows where size_term is zero\n", + "Running school_location with 5377 persons\n", "running highschool, 2\n", - "dropping 23 of 25 rows where size_term is zero\n", - "Running school_location with 5 persons\n", - "(5, 2)\n", - "(10,)\n", - "(5, 2)\n", - " 0 1\n", - "person_id \n", - "386062 0.176063 0.823937\n", - "595685 0.057237 0.942763\n", - "2877285 0.126995 0.873005\n", + "dropping 4000 of 4380 rows where size_term is zero\n", + "Running school_location with 2888 persons\n", "running gradeschool, 1\n", - "dropping 0 of 25 rows where size_term is zero\n", - "Running school_location with 17 persons\n", - "(17, 25)\n", - "(425,)\n", - "(17, 25)\n", - " 0 1 2 3 4 5 \\\n", - "person_id \n", - "386008 0.000504 0.001303 0.002645 0.001534 0.007081 0.023018 \n", - "418442 0.000554 0.001434 0.002911 0.001678 0.007704 0.020100 \n", - "595686 0.000877 0.002269 0.004605 0.003020 0.014986 0.025318 \n", - "\n", - " 6 7 8 9 10 11 \\\n", - "person_id \n", - "386008 0.056174 0.157982 0.336708 0.205884 0.069858 0.001223 \n", - "418442 0.042219 0.120700 0.229318 0.288773 0.101255 0.001526 \n", - "595686 0.058311 0.166706 0.116761 0.110817 0.133127 0.003827 \n", - "\n", - " 12 13 14 15 16 17 \\\n", - "person_id \n", - "386008 0.000206 0.000548 0.000380 0.015291 0.009346 0.008790 \n", - "418442 0.000257 0.000683 0.000474 0.019075 0.011592 0.010965 \n", - "595686 0.000525 0.001116 0.000750 0.053224 0.025697 0.033011 \n", - "\n", - " 18 19 20 21 22 23 \\\n", - "person_id \n", - "386008 0.008610 0.032788 0.028567 0.002079 0.000817 0.002728 \n", - "418442 0.012703 0.052444 0.039245 0.002288 0.000899 0.002985 \n", - "595686 0.014173 0.033481 0.147571 0.003620 0.001422 0.004723 \n", - "\n", - " 24 \n", - "person_id \n", - "386008 0.025936 \n", - "418442 0.028217 \n", - "595686 0.040062 \n", - "CPU times: user 1.39 s, sys: 328 ms, total: 1.72 s\n", - "Wall time: 2.15 s\n" + "dropping 200 of 4380 rows where size_term is zero\n", + "Running school_location with 13471 persons\n", + "CPU times: user 3min 15s, sys: 1min 17s, total: 4min 32s\n", + "Wall time: 4min 19s\n" ] } ], @@ -698,8 +629,11 @@ "\n", " if len(result_list) > 1:\n", " choices_df = pd.concat(result_list)\n", - " assert allow_zero_probs or (len(choosers_location_sample.index) == len(np.unique(choices_df.index.values))), \\\n", - " \"what is this\"\n", + " \n", + " # TODO: why does this fail\n", + " #assert allow_zero_probs or (len(choosers_location_sample.index) == len(np.unique(choices_df.index.values))), \\\n", + " # \"what is this\"\n", + " \n", " # keep alts in canonical order so choices based on their probs are stable across runs\n", " choices_df = choices_df.sort_values(by=alt_col_name).sort_index(kind='mergesort')\n", "\n", @@ -719,1532 +653,986 @@ }, { "cell_type": "code", - "execution_count": 12, - "id": "783732f3", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n", - "

425 rows × 4 columns

\n", - "
" - ], - "text/plain": [ - " alt_dest rand prob person_id\n", - "0 10 0 0.205884 386008\n", - "1 10 0 0.288773 418442\n", - "2 21 0 0.147571 595686\n", - "3 3 0 0.005248 644292\n", - "4 8 0 0.117232 644478\n", - ".. ... ... ... ...\n", - "420 25 0 0.265744 2566702\n", - "421 10 0 0.288773 2877287\n", - "422 9 0 0.336708 3596365\n", - "423 6 0 0.032124 3891104\n", - "424 10 0 0.097048 4171620\n", - "\n", - "[425 rows x 4 columns]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "temp_choices #.sort_values(by=[\"person_id\"])" - ] + "execution_count": null, + "id": "0db07776-70ab-41d1-8dff-84cd7c30065a", + "metadata": {}, + "outputs": [], + "source": [] }, { "cell_type": "code", - "execution_count": 28, - "id": "e7be6961", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, + "execution_count": 37, + "id": "87f5534d-ae21-4f7b-9c85-cd910cc375ae", + "metadata": {}, "outputs": [], "source": [ - "test_df = temp_choices.copy()\n", - "# pick_count and pick_dup\n", - "# pick_count is number of duplicate picks\n", - "# pick_dup flag is True for all but first of duplicates\n", - "pick_group = test_df.groupby([choosers.index.name, alt_col_name])\n", - "# number each item in each group from 0 to the length of that group - 1.\n", - "test_df['pick_count'] = pick_group.cumcount(ascending=True)\n", - "# flag duplicate rows after first\n", - "test_df['pick_dup'] = test_df['pick_count'] > 0\n", - "# add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", - "test_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", - "# drop the duplicates\n", - "test_df = test_df[~test_df['pick_dup']]\n", - "del test_df['pick_dup']\n", - "# set index after groupby so we can trace on it\n", - "test_df.set_index(choosers.index.name, inplace=True)\n", - "del test_df['rand']" + "def run_fru():\n", + " test_df = make_sample_choices_dev(\n", + " chooser_chunk, probs, dest_size_terms,\n", + " sample_size, alternative_count, alt_col_name,\n", + " allow_zero_probs=allow_zero_probs,\n", + " trace_label=chunk_trace_label,\n", + " utilities=utilities,\n", + " choose_individual_max_utility=True\n", + " )\n", + " \n", + "\n", + "def run_previous():\n", + " test_df = make_sample_choices_dev(\n", + " chooser_chunk, probs, dest_size_terms,\n", + " sample_size, alternative_count, alt_col_name,\n", + " allow_zero_probs=allow_zero_probs,\n", + " trace_label=chunk_trace_label,\n", + " utilities=None,\n", + " choose_individual_max_utility=False\n", + " )" ] }, { "cell_type": "code", - "execution_count": 29, - "id": "64053012", + "execution_count": 17, + "id": "22315306-6ec6-4daf-9176-ba397493c945", "metadata": { - "collapsed": false, + "collapsed": true, "jupyter": { - "outputs_hidden": false + "outputs_hidden": true }, - "pycharm": { - "name": "#%%\n" - } + "tags": [] }, "outputs": [ { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
alt_destprobpick_count
person_id
386008100.2058844
418442100.2887739
595686210.1475714
64429230.0052481
64447880.1172324
............
64447860.0321241
2458502250.0326211
2458503110.0788121
2566700160.0367061
2566701180.0143211
\n", - "

165 rows × 3 columns

\n", - "
" - ], - "text/plain": [ - " alt_dest prob pick_count\n", - "person_id \n", - "386008 10 0.205884 4\n", - "418442 10 0.288773 9\n", - "595686 21 0.147571 4\n", - "644292 3 0.005248 1\n", - "644478 8 0.117232 4\n", - "... ... ... ...\n", - "644478 6 0.032124 1\n", - "2458502 25 0.032621 1\n", - "2458503 11 0.078812 1\n", - "2566700 16 0.036706 1\n", - "2566701 18 0.014321 1\n", - "\n", - "[165 rows x 3 columns]" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "test_df" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "2b75344c", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false + "name": "stdout", + "output_type": "stream", + "text": [ + " " + ] }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
alt_destprobpick_count
person_id
359636590.33670813
38600890.33670812
2877287100.28877310
195867890.33670810
245850280.3014149
............
2458503110.0788121
64429230.0052481
2458502100.1038951
3891104100.0970481
2566702110.0612901
\n", - "

165 rows × 3 columns

\n", - "
" - ], "text/plain": [ - " alt_dest prob pick_count\n", - "person_id \n", - "3596365 9 0.336708 13\n", - "386008 9 0.336708 12\n", - "2877287 10 0.288773 10\n", - "1958678 9 0.336708 10\n", - "2458502 8 0.301414 9\n", - "... ... ... ...\n", - "2458503 11 0.078812 1\n", - "644292 3 0.005248 1\n", - "2458502 10 0.103895 1\n", - "3891104 10 0.097048 1\n", - "2566702 11 0.061290 1\n", + " 37642 function calls (37233 primitive calls) in 3.187 seconds\n", + "\n", + " Ordered by: cumulative time\n", "\n", - "[165 rows x 3 columns]" + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.000 0.000 3.187 3.187 {built-in method builtins.exec}\n", + " 1 0.003 0.003 3.187 3.187 :1()\n", + " 1 0.000 0.000 3.185 3.185 2292568830.py:12(run_previous)\n", + " 1 2.165 2.165 3.185 3.185 3493973527.py:3(make_sample_choices_dev)\n", + " 170/148 0.006 0.000 0.344 0.002 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 3 0.332 0.111 0.332 0.111 {method 'cumsum' of 'numpy.ndarray' objects}\n", + " 4 0.241 0.060 0.241 0.060 {method 'repeat' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.236 0.236 <__array_function__ internals>:177(tile)\n", + " 1 0.000 0.000 0.236 0.236 shape_base.py:1171(tile)\n", + " 4 0.221 0.055 0.221 0.055 {method 'flatten' of 'numpy.ndarray' objects}\n", + " 98 0.000 0.000 0.101 0.001 fromnumeric.py:51(_wrapfunc)\n", + " 30 0.000 0.000 0.077 0.003 <__array_function__ internals>:177(argmax)\n", + " 30 0.000 0.000 0.077 0.003 fromnumeric.py:1127(argmax)\n", + " 30 0.076 0.003 0.076 0.003 {method 'argmax' of 'numpy.ndarray' objects}\n", + " 2 0.000 0.000 0.065 0.033 groupby.py:3040(cumcount)\n", + " 2 0.004 0.002 0.058 0.029 groupby.py:1699(_cumcount_array)\n", + " 1 0.000 0.000 0.042 0.042 ops.py:832(group_info)\n", + " 1 0.000 0.000 0.042 0.042 ops.py:854(_get_compressed_codes)\n", + " 1 0.000 0.000 0.030 0.030 sorting.py:661(compress_group_index)\n", + " 73 0.023 0.000 0.023 0.000 {method 'take' of 'numpy.ndarray' objects}\n", + " 1 0.001 0.001 0.022 0.022 random.py:562(random_for_df)\n", + " 60 0.000 0.000 0.019 0.000 <__array_function__ internals>:177(take)\n", + " 60 0.000 0.000 0.019 0.000 fromnumeric.py:93(take)\n", + " 1 0.000 0.000 0.018 0.018 sorting.py:683(_reorder_by_uniques)\n", + " 1 0.005 0.005 0.018 0.018 random.py:598()\n", + " 63 0.000 0.000 0.016 0.000 common.py:55(new_method)\n", + " 4 0.000 0.000 0.015 0.004 frame.py:587(__init__)\n", + " 1 0.000 0.000 0.015 0.015 construction.py:425(dict_to_mgr)\n", + " 1 0.000 0.000 0.015 0.015 construction.py:102(arrays_to_mgr)\n", + " 62 0.000 0.000 0.015 0.000 arraylike.py:98(__add__)\n", + " 1 0.000 0.000 0.015 0.015 managers.py:1937(create_block_manager_from_column_arrays)\n", + " 62 0.000 0.000 0.015 0.000 series.py:5637(_arith_method)\n", + " 62 0.001 0.000 0.014 0.000 base.py:1286(_arith_method)\n", + " 9 0.000 0.000 0.013 0.001 frame.py:3463(__getitem__)\n", + " 13471 0.013 0.000 0.013 0.000 {method 'rand' of 'numpy.random.mtrand.RandomState' objects}\n", + " 101 0.001 0.000 0.012 0.000 series.py:323(__init__)\n", + " 1 0.011 0.011 0.011 0.011 {method 'get_labels_groupby' of 'pandas._libs.hashtable.Int64HashTable' objects}\n", + " 3 0.000 0.000 0.011 0.004 generic.py:3708(_take_with_is_copy)\n", + " 3 0.000 0.000 0.011 0.004 generic.py:3609(take)\n", + " 1 0.000 0.000 0.011 0.011 ops.py:790(codes)\n", + " 1 0.000 0.000 0.011 0.011 ops.py:793()\n", + " 2 0.000 0.000 0.011 0.005 grouper.py:616(codes)\n", + " 2 0.000 0.000 0.011 0.005 grouper.py:659(_codes_and_uniques)\n", + " 2 0.000 0.000 0.011 0.005 algorithms.py:568(factorize)\n", + " 3 0.000 0.000 0.011 0.004 managers.py:1683(_consolidate_inplace)\n", + " 3 0.000 0.000 0.010 0.003 managers.py:2074(_consolidate)\n", + " 8 0.005 0.001 0.010 0.001 managers.py:2091(_merge_blocks)\n", + " 2 0.000 0.000 0.009 0.005 algorithms.py:524(factorize_array)\n", + " 2 0.009 0.004 0.009 0.004 {method 'factorize' of 'pandas._libs.hashtable.Int64HashTable' objects}\n", + " 63 0.000 0.000 0.009 0.000 series.py:2988(_construct_result)\n", + " 8 0.009 0.001 0.009 0.001 {method 'argsort' of 'numpy.ndarray' objects}\n", + " 3 0.000 0.000 0.008 0.003 managers.py:875(take)\n", + " 1 0.000 0.000 0.007 0.007 managers.py:2008(_form_blocks)\n", + " 3 0.007 0.002 0.007 0.002 managers.py:2060(_stack_arrays)\n", + " 1 0.000 0.000 0.007 0.007 frame.py:3530(_getitem_bool_array)\n", + " 19 0.000 0.000 0.006 0.000 take.py:57(take_nd)\n", + " 19 0.000 0.000 0.006 0.000 take.py:120(_take_nd_ndarray)\n", + " 59/44 0.000 0.000 0.006 0.000 groupby.py:908(__getattribute__)\n", + " 3 0.000 0.000 0.006 0.002 managers.py:634(reindex_indexer)\n", + " 2 0.000 0.000 0.006 0.003 groupby.py:675(_selected_obj)\n", + " 7 0.000 0.000 0.006 0.001 blocks.py:1114(take_nd)\n", + " 17 0.000 0.000 0.005 0.000 <__array_function__ internals>:177(concatenate)\n", + " 3 0.000 0.000 0.005 0.002 <__array_function__ internals>:177(vstack)\n", + " 3 0.000 0.000 0.005 0.002 shape_base.py:222(vstack)\n", + " 3 0.000 0.000 0.005 0.002 <__array_function__ internals>:177(repeat)\n", + " 3 0.000 0.000 0.005 0.002 fromnumeric.py:436(repeat)\n", + " 1 0.004 0.004 0.004 0.004 {method 'put' of 'numpy.ndarray' objects}\n", + " 38 0.004 0.000 0.004 0.000 {built-in method numpy.arange}\n", + " 4861 0.003 0.000 0.004 0.000 {built-in method builtins.isinstance}\n", + " 1 0.000 0.000 0.003 0.003 managers.py:692()\n", + " 5 0.000 0.000 0.003 0.001 generic.py:5646(_consolidate_inplace)\n", + " 5 0.000 0.000 0.003 0.001 generic.py:5632(_protect_consolidate)\n", + " 5 0.000 0.000 0.003 0.001 generic.py:5650(f)\n", + " 5 0.000 0.000 0.003 0.001 managers.py:618(consolidate)\n", + " 102 0.001 0.000 0.003 0.000 construction.py:470(sanitize_array)\n", + " 5 0.000 0.000 0.003 0.001 managers.py:713(_slice_take_blocks_ax0)\n", + " 21 0.003 0.000 0.003 0.000 {built-in method numpy.asanyarray}\n", + " 62 0.000 0.000 0.002 0.000 array_ops.py:183(arithmetic_op)\n", + " 95 0.000 0.000 0.002 0.000 managers.py:1731(from_array)\n", + " 2 0.000 0.000 0.002 0.001 sorting.py:618(get_group_index_sorter)\n", + " 2 0.002 0.001 0.002 0.001 {pandas._libs.algos.groupsort_indexer}\n", + " 63 0.000 0.000 0.002 0.000 array_ops.py:134(_na_arithmetic_op)\n", + " 181 0.000 0.000 0.002 0.000 generic.py:5577(__setattr__)\n", + " 3 0.000 0.000 0.002 0.001 frame.py:3630(__setitem__)\n", + " 3 0.000 0.000 0.002 0.001 frame.py:3822(_set_item)\n", + " 96 0.000 0.000 0.002 0.000 blocks.py:2041(new_block)\n", + " 63 0.000 0.000 0.002 0.000 expressions.py:223(evaluate)\n", + " 63 0.000 0.000 0.002 0.000 expressions.py:92(_evaluate_numexpr)\n", + " 96 0.000 0.000 0.002 0.000 config.py:255(__call__)\n", + " 2 0.000 0.000 0.002 0.001 take.py:350(wrapper)\n", + " 1354 0.001 0.000 0.002 0.000 generic.py:43(_check)\n", + " 223 0.000 0.000 0.001 0.000 series.py:640(name)\n", + " 15/12 0.000 0.000 0.001 0.000 base.py:397(__new__)\n", + " 96 0.000 0.000 0.001 0.000 config.py:127(_get_option)\n", + " 63 0.000 0.000 0.001 0.000 expressions.py:63(_evaluate_standard)\n", + " 3 0.000 0.000 0.001 0.000 frame.py:3797(_set_item_mgr)\n", + " 2 0.000 0.000 0.001 0.001 base.py:5768(_get_indexer_strict)\n", + " 72 0.001 0.000 0.001 0.000 generic.py:5517(__finalize__)\n", + "1525/1157 0.001 0.000 0.001 0.000 {built-in method builtins.len}\n", + " 2 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis0_float64_float64}\n", + " 10 0.001 0.000 0.001 0.000 {method 'nonzero' of 'numpy.ndarray' objects}\n", + " 4 0.000 0.000 0.001 0.000 {built-in method builtins.next}\n", + " 4 0.000 0.000 0.001 0.000 groupby.py:998(_group_selection_context)\n", + " 171 0.000 0.000 0.001 0.000 construction.py:379(extract_array)\n", + " 62 0.001 0.000 0.001 0.000 {built-in method _operator.add}\n", + " 2 0.000 0.000 0.001 0.001 algorithms.py:1663(safe_sort)\n", + " 2 0.000 0.000 0.001 0.001 contextlib.py:108(__enter__)\n", + " 2 0.000 0.000 0.001 0.001 groupby.py:958(_set_group_selection)\n", + " 6 0.000 0.000 0.001 0.000 base.py:3706(get_indexer)\n", + " 5 0.000 0.000 0.001 0.000 base.py:1098(take)\n", + " 223 0.000 0.000 0.001 0.000 common.py:1721(validate_all_hashable)\n", + " 8 0.000 0.000 0.001 0.000 base.py:672(_with_infer)\n", + " 1 0.001 0.001 0.001 0.001 sorting.py:100(get_group_index)\n", + " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis1_int64_int64}\n", + " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis1_float64_float64}\n", + " 128 0.000 0.000 0.001 0.000 _ufunc_config.py:32(seterr)\n", + " 96 0.000 0.000 0.001 0.000 config.py:109(_get_single_key)\n", + " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis0_bool_bool}\n", + " 4 0.000 0.000 0.001 0.000 base.py:5744(get_indexer_for)\n", + " 228 0.000 0.000 0.001 0.000 {built-in method builtins.all}\n", + " 5 0.000 0.000 0.001 0.000 algorithms.py:1352(take)\n", + " 2 0.000 0.000 0.001 0.000 <__array_function__ internals>:177(nonzero)\n", + " 2 0.000 0.000 0.001 0.000 fromnumeric.py:1866(nonzero)\n", + " 1 0.000 0.000 0.001 0.001 generic.py:11363(__iadd__)\n", + " 1 0.000 0.000 0.001 0.001 generic.py:11337(_inplace_method)\n", + " 106 0.000 0.000 0.001 0.000 base.py:6987(ensure_index)\n", + " 1709 0.001 0.000 0.001 0.000 {built-in method builtins.getattr}\n", + " 2 0.000 0.000 0.001 0.000 managers.py:1219(insert)\n", + " 64 0.000 0.000 0.001 0.000 _ufunc_config.py:429(__enter__)\n", + " 2 0.000 0.000 0.001 0.000 base.py:3400(difference)\n", + " 110 0.000 0.000 0.001 0.000 base.py:7082(maybe_extract_name)\n", + " 3 0.000 0.000 0.001 0.000 generic.py:4020(__delitem__)\n", + " 2 0.000 0.000 0.001 0.000 base.py:4109(reindex)\n", + " 1 0.000 0.000 0.001 0.001 frame.py:3790(_iset_item_mgr)\n", + " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis1_bool_bool}\n", + " 1 0.000 0.000 0.001 0.001 managers.py:1061(iset)\n", + " 12 0.001 0.000 0.001 0.000 {pandas._libs.algos.take_1d_int64_int64}\n", + " 102 0.000 0.000 0.001 0.000 construction.py:695(_try_cast)\n", + " 6 0.000 0.000 0.001 0.000 utils.py:249(maybe_convert_indices)\n", + " 5 0.000 0.000 0.001 0.000 index_tricks.py:322(__getitem__)\n", + " 3 0.000 0.000 0.001 0.000 managers.py:1299(idelete)\n", + " 446 0.000 0.000 0.001 0.000 common.py:1740()\n", + " 102 0.000 0.000 0.001 0.000 blocks.py:1960(maybe_coerce_values)\n", + " 105 0.000 0.000 0.001 0.000 generic.py:239(__init__)\n", + " 134 0.000 0.000 0.001 0.000 series.py:687(_values)\n", + " 1 0.000 0.000 0.001 0.001 blocks.py:378(delete)\n", + " 2 0.000 0.000 0.000 0.000 base.py:6561(insert)\n", + " 1 0.000 0.000 0.000 0.000 _decorators.py:302(wrapper)\n", + " 1 0.000 0.000 0.000 0.000 frame.py:5365(set_index)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(delete)\n", + " 3 0.000 0.000 0.000 0.000 frame.py:4515(_sanitize_column)\n", + " 2 0.000 0.000 0.000 0.000 function_base.py:4958(delete)\n", + " 64 0.000 0.000 0.000 0.000 _ufunc_config.py:434(__exit__)\n", + " 99 0.000 0.000 0.000 0.000 common.py:552(require_length_match)\n", + " 3 0.000 0.000 0.000 0.000 frame.py:10986(_reindex_for_setitem)\n", + " 96 0.000 0.000 0.000 0.000 blocks.py:2055(check_ndim)\n", + " 2 0.000 0.000 0.000 0.000 base.py:3451(_difference)\n", + " 344 0.000 0.000 0.000 0.000 inference.py:321(is_hashable)\n", + " 6 0.000 0.000 0.000 0.000 {method 'copy' of 'numpy.ndarray' objects}\n", + " 105 0.000 0.000 0.000 0.000 blocks.py:1989(get_block_type)\n", + " 293 0.000 0.000 0.000 0.000 series.py:590(name)\n", + " 1 0.000 0.000 0.000 0.000 arraylike.py:54(__gt__)\n", + " 1 0.000 0.000 0.000 0.000 series.py:5613(_cmp_method)\n", + " 1 0.000 0.000 0.000 0.000 frame.py:7595(groupby)\n", + " 67 0.000 0.000 0.000 0.000 common.py:75(get_op_result_name)\n", + " 6 0.000 0.000 0.000 0.000 frame.py:3923(_get_item_cache)\n", + " 1 0.000 0.000 0.000 0.000 groupby.py:839(__init__)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(diff)\n", + " 2 0.000 0.000 0.000 0.000 function_base.py:1295(diff)\n", + " 1 0.000 0.000 0.000 0.000 grouper.py:700(get_grouper)\n", + " 15 0.000 0.000 0.000 0.000 {pandas._libs.lib.maybe_convert_objects}\n", + " 61 0.000 0.000 0.000 0.000 series.py:825(__array__)\n", + " 13 0.000 0.000 0.000 0.000 base.py:5178(equals)\n", + " 134 0.000 0.000 0.000 0.000 managers.py:1848(internal_values)\n", + " 16 0.000 0.000 0.000 0.000 {method 'astype' of 'numpy.ndarray' objects}\n", + " 261 0.000 0.000 0.000 0.000 base.py:884(__len__)\n", + " 19 0.000 0.000 0.000 0.000 take.py:554(_take_preprocess_indexer_and_fill_value)\n", + " 63 0.000 0.000 0.000 0.000 dispatch.py:11(should_extension_dispatch)\n", + " 102 0.000 0.000 0.000 0.000 construction.py:627(_sanitize_ndim)\n", + " 9 0.000 0.000 0.000 0.000 base.py:7106(_maybe_cast_data_without_dtype)\n", + " 6 0.000 0.000 0.000 0.000 base.py:6293(_maybe_cast_listlike_indexer)\n", + " 1 0.000 0.000 0.000 0.000 {pandas._libs.algos.take_2d_axis0_int64_int64}\n", + " 128 0.000 0.000 0.000 0.000 _ufunc_config.py:131(geterr)\n", + " 95 0.000 0.000 0.000 0.000 construction.py:802(is_empty_data)\n", + " 96 0.000 0.000 0.000 0.000 config.py:589(_get_root)\n", + " 90 0.000 0.000 0.000 0.000 common.py:1587(_is_dtype_type)\n", + " 96 0.000 0.000 0.000 0.000 config.py:642(_warn_if_deprecated)\n", + " 178 0.000 0.000 0.000 0.000 construction.py:438(ensure_wrapped_if_datetimelike)\n", + " 5 0.000 0.000 0.000 0.000 frame.py:3411(_ixs)\n", + " 38 0.000 0.000 0.000 0.000 base.py:286(is_dtype)\n", + " 218 0.000 0.000 0.000 0.000 generic.py:5561(__getattr__)\n", + " 108 0.000 0.000 0.000 0.000 base.py:4820(_values)\n", + " 1 0.000 0.000 0.000 0.000 array_ops.py:227(comparison_op)\n", + " 28 0.000 0.000 0.000 0.000 common.py:497(is_categorical_dtype)\n", + " 12 0.000 0.000 0.000 0.000 base.py:554(_dtype_to_subclass)\n", + " 51 0.000 0.000 0.000 0.000 {built-in method numpy.empty}\n", + " 192 0.000 0.000 0.000 0.000 config.py:603(_get_deprecated_option)\n", + " 1 0.000 0.000 0.000 0.000 ops.py:684(shape)\n", + " 31 0.000 0.000 0.000 0.000 series.py:743(__len__)\n", + " 3 0.000 0.000 0.000 0.000 ops.py:686()\n", + " 8 0.000 0.000 0.000 0.000 base.py:6004(_should_compare)\n", + " 23 0.000 0.000 0.000 0.000 _dtype.py:328(_name_get)\n", + " 11 0.000 0.000 0.000 0.000 base.py:3577(get_loc)\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:603(ngroups)\n", + " 62 0.000 0.000 0.000 0.000 array_ops.py:441(maybe_prepare_scalar_for_op)\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:650(group_index)\n", + " 40 0.000 0.000 0.000 0.000 common.py:161(is_object_dtype)\n", + " 13 0.000 0.000 0.000 0.000 {pandas._libs.lib.infer_dtype}\n", + " 1 0.000 0.000 0.000 0.000 range.py:189(_data)\n", + " 4 0.000 0.000 0.000 0.000 algorithms.py:115(_ensure_data)\n", + " 62 0.000 0.000 0.000 0.000 __init__.py:152(align_method_SERIES)\n", + " 11 0.000 0.000 0.000 0.000 base.py:2596(inferred_type)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method _operator.gt}\n", + " 322 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_list_like}\n", + " 30 0.000 0.000 0.000 0.000 {method 'reduce' of 'numpy.ufunc' objects}\n", + " 95 0.000 0.000 0.000 0.000 series.py:542(_set_axis)\n", + " 396 0.000 0.000 0.000 0.000 {built-in method builtins.issubclass}\n", + " 17 0.000 0.000 0.000 0.000 base.py:53(shape)\n", + " 102 0.000 0.000 0.000 0.000 construction.py:664(_sanitize_str_dtypes)\n", + " 5 0.000 0.000 0.000 0.000 {built-in method builtins.sorted}\n", + " 128 0.000 0.000 0.000 0.000 {built-in method numpy.seterrobj}\n", + " 359 0.000 0.000 0.000 0.000 {built-in method builtins.hash}\n", + " 8 0.000 0.000 0.000 0.000 warnings.py:130(filterwarnings)\n", + " 2 0.000 0.000 0.000 0.000 algorithms.py:299(_get_data_algo)\n", + " 22 0.000 0.000 0.000 0.000 managers.py:2079()\n", + " 11 0.000 0.000 0.000 0.000 base.py:6284(_maybe_cast_indexer)\n", + " 14 0.000 0.000 0.000 0.000 common.py:229(asarray_tuplesafe)\n", + " 14 0.000 0.000 0.000 0.000 {method 'any' of 'numpy.ndarray' objects}\n", + " 104 0.000 0.000 0.000 0.000 common.py:1416(is_1d_only_ea_dtype)\n", + " 31 0.000 0.000 0.000 0.000 base.py:45(__len__)\n", + " 105 0.000 0.000 0.000 0.000 flags.py:47(__init__)\n", + " 256 0.000 0.000 0.000 0.000 {built-in method numpy.geterrobj}\n", + " 6 0.000 0.000 0.000 0.000 base.py:3823(_check_indexing_method)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(putmask)\n", + " 15 0.000 0.000 0.000 0.000 numeric.py:289(full)\n", + " 1 0.000 0.000 0.000 0.000 generic.py:1516(__invert__)\n", + " 102 0.000 0.000 0.000 0.000 construction.py:684(_maybe_repeat)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:596(_homogenize)\n", + " 8 0.000 0.000 0.000 0.000 blocks.py:166(_consolidate_key)\n", + " 96 0.000 0.000 0.000 0.000 config.py:630(_translate_key)\n", + " 23 0.000 0.000 0.000 0.000 common.py:581(is_dtype_equal)\n", + " 101 0.000 0.000 0.000 0.000 managers.py:1700(__init__)\n", + " 5 0.000 0.000 0.000 0.000 frame.py:3906(_box_col_values)\n", + " 13 0.000 0.000 0.000 0.000 base.py:5023(__getitem__)\n", + " 14 0.000 0.000 0.000 0.000 _methods.py:54(_any)\n", + " 167/166 0.000 0.000 0.000 0.000 {built-in method numpy.asarray}\n", + " 172 0.000 0.000 0.000 0.000 {built-in method builtins.hasattr}\n", + " 24 0.000 0.000 0.000 0.000 base.py:654(_simple_new)\n", + " 101 0.000 0.000 0.000 0.000 cast.py:1711(sanitize_to_nanoseconds)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(insert)\n", + " 51 0.000 0.000 0.000 0.000 base.py:55()\n", + " 19 0.000 0.000 0.000 0.000 common.py:1240(is_float_dtype)\n", + " 16 0.000 0.000 0.000 0.000 base.py:2272(is_boolean)\n", + " 2 0.000 0.000 0.000 0.000 algorithms.py:289(_get_values_for_rank)\n", + " 2 0.000 0.000 0.000 0.000 function_base.py:5143(insert)\n", + " 11 0.000 0.000 0.000 0.000 base.py:2344(is_floating)\n", + " 72 0.000 0.000 0.000 0.000 flags.py:83(allows_duplicate_labels)\n", + " 96 0.000 0.000 0.000 0.000 config.py:571(_select_options)\n", + " 5 0.000 0.000 0.000 0.000 {method '_rebuild_blknos_and_blklocs' of 'pandas._libs.internals.BlockManager' objects}\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(setdiff1d)\n", + " 23 0.000 0.000 0.000 0.000 _dtype.py:314(_name_includes_bit_suffix)\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:72(inner)\n", + " 12 0.000 0.000 0.000 0.000 common.py:1274(is_bool_dtype)\n", + " 6 0.000 0.000 0.000 0.000 base.py:3786(_get_indexer)\n", + " 63 0.000 0.000 0.000 0.000 missing.py:138(dispatch_fill_zeros)\n", + " 6 0.000 0.000 0.000 0.000 common.py:105(is_bool_indexer)\n", + " 2 0.000 0.000 0.000 0.000 arraysetops.py:784(setdiff1d)\n", + " 5 0.000 0.000 0.000 0.000 managers.py:2137(_preprocess_slice_or_indexer)\n", + " 2 0.000 0.000 0.000 0.000 managers.py:1277(_insert_update_blklocs_and_blknos)\n", + " 4 0.000 0.000 0.000 0.000 blocks.py:238(fill_value)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:253(apply)\n", + " 2 0.000 0.000 0.000 0.000 frame.py:10808(values)\n", + " 30 0.000 0.000 0.000 0.000 common.py:1483(is_ea_or_datetimelike_dtype)\n", + " 19 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(copyto)\n", + " 15 0.000 0.000 0.000 0.000 base.py:4973(__contains__)\n", + " 209 0.000 0.000 0.000 0.000 typing.py:1149(cast)\n", + " 8 0.000 0.000 0.000 0.000 base.py:7168(unpack_nested_dtype)\n", + " 6 0.000 0.000 0.000 0.000 blocks.py:2030(new_block_2d)\n", + " 1 0.000 0.000 0.000 0.000 base.py:6947(ensure_index_from_sequences)\n", + " 96 0.000 0.000 0.000 0.000 {method 'split' of 'str' objects}\n", + " 4 0.000 0.000 0.000 0.000 missing.py:571(na_value_for_dtype)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(in1d)\n", + " 4 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(append)\n", + " 1 0.000 0.000 0.000 0.000 indexing.py:2352(check_bool_indexer)\n", + " 6 0.000 0.000 0.000 0.000 common.py:459(is_interval_dtype)\n", + " 58 0.000 0.000 0.000 0.000 common.py:1552(get_dtype)\n", + " 13 0.000 0.000 0.000 0.000 {method 'max' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.000 0.000 blocks.py:396(apply)\n", + " 11 0.000 0.000 0.000 0.000 numerictypes.py:356(issubdtype)\n", + " 5 0.000 0.000 0.000 0.000 managers.py:1016(iget)\n", + " 59 0.000 0.000 0.000 0.000 common.py:147()\n", + " 2 0.000 0.000 0.000 0.000 arraysetops.py:519(in1d)\n", + " 31 0.000 0.000 0.000 0.000 common.py:155()\n", + " 7 0.000 0.000 0.000 0.000 common.py:680(is_integer_dtype)\n", + " 101 0.000 0.000 0.000 0.000 managers.py:1792(_block)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(moveaxis)\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:468(__init__)\n", + " 4 0.000 0.000 0.000 0.000 function_base.py:5337(append)\n", + " 8 0.000 0.000 0.000 0.000 managers.py:1665(is_consolidated)\n", + " 5 0.000 0.000 0.000 0.000 numerictypes.py:597(find_common_type)\n", + " 6 0.000 0.000 0.000 0.000 dtypes.py:1206(is_dtype)\n", + " 1 0.000 0.000 0.000 0.000 {function SeedSequence.generate_state at 0x7fb5e410ac10}\n", + " 12 0.000 0.000 0.000 0.000 common.py:786(is_unsigned_integer_dtype)\n", + " 6 0.000 0.000 0.000 0.000 base.py:845(_engine)\n", + " 63 0.000 0.000 0.000 0.000 expressions.py:72(_can_use_numexpr)\n", + " 145 0.000 0.000 0.000 0.000 generic.py:349(flags)\n", + " 12 0.000 0.000 0.000 0.000 common.py:732(is_signed_integer_dtype)\n", + " 6 0.000 0.000 0.000 0.000 base.py:5917(_maybe_promote)\n", + " 13 0.000 0.000 0.000 0.000 _methods.py:38(_amax)\n", + " 2 0.000 0.000 0.000 0.000 numeric.py:1404(moveaxis)\n", + " 2 0.000 0.000 0.000 0.000 contextlib.py:117(__exit__)\n", + " 5 0.000 0.000 0.000 0.000 managers.py:1673(_consolidate_check)\n", + " 63 0.000 0.000 0.000 0.000 _ufunc_config.py:425(__init__)\n", + " 17 0.000 0.000 0.000 0.000 common.py:1429(is_extension_array_dtype)\n", + " 8 0.000 0.000 0.000 0.000 warnings.py:181(_add_filter)\n", + " 8 0.000 0.000 0.000 0.000 re.py:250(compile)\n", + " 34 0.000 0.000 0.000 0.000 range.py:909(__len__)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:635(_extract_index)\n", + " 2 0.000 0.000 0.000 0.000 base.py:3465(_wrap_difference_result)\n", + " 2 0.000 0.000 0.000 0.000 base.py:5799(_raise_if_missing)\n", + " 1 0.000 0.000 0.000 0.000 utils.py:457(check_array_indexer)\n", + " 6 0.000 0.000 0.000 0.000 {method 'get_indexer' of 'pandas._libs.index.IndexEngine' objects}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method _operator.invert}\n", + " 2 0.000 0.000 0.000 0.000 base.py:3258(_wrap_setop_result)\n", + " 10 0.000 0.000 0.000 0.000 frame.py:1413(__len__)\n", + " 95 0.000 0.000 0.000 0.000 numeric.py:331(_is_all_dates)\n", + " 72 0.000 0.000 0.000 0.000 generic.py:328(attrs)\n", + " 62 0.000 0.000 0.000 0.000 array_ops.py:510(_bool_arith_check)\n", + " 3 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(argsort)\n", + " 75 0.000 0.000 0.000 0.000 {pandas._libs.lib.item_from_zerodim}\n", + " 2 0.000 0.000 0.000 0.000 algorithms.py:308(_check_object_for_strings)\n", + " 2 0.000 0.000 0.000 0.000 algorithms.py:193(_reconstruct_data)\n", + " 8 0.000 0.000 0.000 0.000 re.py:289(_compile)\n", + " 22 0.000 0.000 0.000 0.000 numerictypes.py:282(issubclass_)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:487()\n", + " 15 0.000 0.000 0.000 0.000 :1017(_handle_fromlist)\n", + " 3 0.000 0.000 0.000 0.000 numeric.py:139(_ensure_array)\n", + " 4 0.000 0.000 0.000 0.000 numeric.py:149(ones)\n", + " 15 0.000 0.000 0.000 0.000 managers.py:156(blknos)\n", + " 5 0.000 0.000 0.000 0.000 common.py:97(_maybe_match_name)\n", + " 3 0.000 0.000 0.000 0.000 _asarray.py:22(require)\n", + " 4 0.000 0.000 0.000 0.000 base.py:118(_reset_cache)\n", + " 6 0.000 0.000 0.000 0.000 indexing.py:2318(convert_to_index_sliceable)\n", + " 2 0.000 0.000 0.000 0.000 generic.py:1991(__contains__)\n", + " 2 0.000 0.000 0.000 0.000 managers.py:1541(as_array)\n", + " 4 0.000 0.000 0.000 0.000 common.py:348(is_datetime64tz_dtype)\n", + " 12 0.000 0.000 0.000 0.000 base.py:4846(_get_engine_target)\n", + " 2 0.000 0.000 0.000 0.000 managers.py:1267(_insert_update_mgr_locs)\n", + " 13 0.000 0.000 0.000 0.000 missing.py:390(array_equivalent)\n", + " 2 0.000 0.000 0.000 0.000 groupby.py:985(_reset_group_selection)\n", + " 73 0.000 0.000 0.000 0.000 flags.py:51(allows_duplicate_labels)\n", + " 11 0.000 0.000 0.000 0.000 inference.py:184(is_array_like)\n", + " 18 0.000 0.000 0.000 0.000 {built-in method numpy.array}\n", + " 1 0.000 0.000 0.000 0.000 construction.py:483()\n", + " 4 0.000 0.000 0.000 0.000 base.py:2232(is_unique)\n", + " 21 0.000 0.000 0.000 0.000 common.py:160(cast_scalar_indexer)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(prod)\n", + " 9 0.000 0.000 0.000 0.000 indexing.py:2481(check_deprecated_indexers)\n", + " 9 0.000 0.000 0.000 0.000 blocks.py:2119(extend_blocks)\n", + " 64 0.000 0.000 0.000 0.000 series.py:523(_constructor)\n", + " 60 0.000 0.000 0.000 0.000 fromnumeric.py:89(_take_dispatcher)\n", + " 11 0.000 0.000 0.000 0.000 {method 'get_loc' of 'pandas._libs.index.IndexEngine' objects}\n", + " 4 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(ravel)\n", + " 2 0.000 0.000 0.000 0.000 base.py:3560(_convert_can_do_setop)\n", + " 3 0.000 0.000 0.000 0.000 fromnumeric.py:1012(argsort)\n", + " 4 0.000 0.000 0.000 0.000 common.py:287(maybe_iterable_to_list)\n", + " 59 0.000 0.000 0.000 0.000 common.py:145(classes)\n", + " 4 0.000 0.000 0.000 0.000 numeric.py:1341(normalize_axis_tuple)\n", + " 9 0.000 0.000 0.000 0.000 base.py:2624(_is_multi)\n", + " 30 0.000 0.000 0.000 0.000 fromnumeric.py:1123(_argmax_dispatcher)\n", + " 3 0.000 0.000 0.000 0.000 cast.py:1960(construct_1d_object_array_from_listlike)\n", + " 74 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}\n", + " 1 0.000 0.000 0.000 0.000 {method 'delete' of 'pandas._libs.internals.BlockPlacement' objects}\n", + " 3 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(atleast_2d)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2970(prod)\n", + " 8 0.000 0.000 0.000 0.000 warnings.py:458(__enter__)\n", + " 5 0.000 0.000 0.000 0.000 managers.py:1679()\n", + " 2 0.000 0.000 0.000 0.000 {method 'sum' of 'numpy.ndarray' objects}\n", + " 5 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(ndim)\n", + " 9 0.000 0.000 0.000 0.000 series.py:575(dtype)\n", + " 5 0.000 0.000 0.000 0.000 series.py:1238(_set_as_cached)\n", + " 11 0.000 0.000 0.000 0.000 generic.py:560(_get_axis)\n", + " 37 0.000 0.000 0.000 0.000 managers.py:222(items)\n", + " 11 0.000 0.000 0.000 0.000 abc.py:96(__instancecheck__)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:69(_wrapreduction)\n", + " 9 0.000 0.000 0.000 0.000 blocks.py:332(getitem_block_columns)\n", + " 1 0.000 0.000 0.000 0.000 blocks.py:427(_split_op_result)\n", + " 2 0.000 0.000 0.000 0.000 generic.py:1752(_check_label_or_level_ambiguity)\n", + " 2 0.000 0.000 0.000 0.000 _methods.py:46(_sum)\n", + " 13 0.000 0.000 0.000 0.000 base.py:803(is_)\n", + " 46 0.000 0.000 0.000 0.000 {pandas._libs.algos.ensure_platform_int}\n", + " 19 0.000 0.000 0.000 0.000 take.py:326(_get_take_nd_function)\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:925(_convert_grouper)\n", + " 1 0.000 0.000 0.000 0.000 blocks.py:252(make_block)\n", + " 1 0.000 0.000 0.000 0.000 generic.py:767(_set_axis)\n", + " 3 0.000 0.000 0.000 0.000 blocks.py:2135(ensure_block_shape)\n", + " 3 0.000 0.000 0.000 0.000 {built-in method builtins.any}\n", + " 25 0.000 0.000 0.000 0.000 base.py:834(_reset_identity)\n", + " 2 0.000 0.000 0.000 0.000 managers.py:2128(_fast_count_smallints)\n", + " 3 0.000 0.000 0.000 0.000 shape_base.py:81(atleast_2d)\n", + " 26 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x908780}\n", + " 7 0.000 0.000 0.000 0.000 blocks.py:267(make_block_same_class)\n", + " 12 0.000 0.000 0.000 0.000 common.py:346(apply_if_callable)\n", + " 4 0.000 0.000 0.000 0.000 fromnumeric.py:1755(ravel)\n", + " 2 0.000 0.000 0.000 0.000 missing.py:625(is_valid_na_for_dtype)\n", + " 8 0.000 0.000 0.000 0.000 warnings.py:477(__exit__)\n", + " 36 0.000 0.000 0.000 0.000 base.py:1650(name)\n", + " 9 0.000 0.000 0.000 0.000 cast.py:468(maybe_promote)\n", + " 2 0.000 0.000 0.000 0.000 base.py:2844(unique)\n", + " 3 0.000 0.000 0.000 0.000 missing.py:66(isna)\n", + " 9 0.000 0.000 0.000 0.000 managers.py:1837(dtype)\n", + " 1 0.000 0.000 0.000 0.000 frame.py:821(shape)\n", + " 1 0.000 0.000 0.000 0.000 random.py:721(getrandbits)\n", + " 23 0.000 0.000 0.000 0.000 _dtype.py:24(_kind_name)\n", + " 8 0.000 0.000 0.000 0.000 {method 'remove' of 'list' objects}\n", + " 8 0.000 0.000 0.000 0.000 types.py:171(__get__)\n", + " 9 0.000 0.000 0.000 0.000 generic.py:636(_info_axis)\n", + " 2 0.000 0.000 0.000 0.000 groupby.py:1734(_obj_1d_constructor)\n", + " 9 0.000 0.000 0.000 0.000 range.py:522(equals)\n", + " 6 0.000 0.000 0.000 0.000 {method 'reshape' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.000 0.000 generic.py:1399(_indexed_same)\n", + " 10 0.000 0.000 0.000 0.000 numerictypes.py:573(_can_coerce_all)\n", + " 11 0.000 0.000 0.000 0.000 {method 'format' of 'str' objects}\n", + " 4 0.000 0.000 0.000 0.000 managers.py:1992(_grouping_func)\n", + " 3 0.000 0.000 0.000 0.000 managers.py:2102()\n", + " 31 0.000 0.000 0.000 0.000 common.py:150(classes_and_not_datetimelike)\n", + " 2 0.000 0.000 0.000 0.000 base.py:4931(_validate_fill_value)\n", + " 18 0.000 0.000 0.000 0.000 generic.py:546(_get_axis_number)\n", + " 2 0.000 0.000 0.000 0.000 base.py:785(_view)\n", + " 3 0.000 0.000 0.000 0.000 missing.py:149(_isna)\n", + " 13 0.000 0.000 0.000 0.000 {built-in method builtins.max}\n", + " 1 0.000 0.000 0.000 0.000 base.py:841(_cleanup)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:212(set_axis)\n", + " 2 0.000 0.000 0.000 0.000 base.py:4226(_wrap_reindex_result)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.sum}\n", + " 2 0.000 0.000 0.000 0.000 contextlib.py:238(helper)\n", + " 3 0.000 0.000 0.000 0.000 base.py:358(size)\n", + " 6 0.000 0.000 0.000 0.000 missing.py:911(clean_reindex_fill_method)\n", + " 8 0.000 0.000 0.000 0.000 warnings.py:437(__init__)\n", + " 9 0.000 0.000 0.000 0.000 {pandas._libs.lib.maybe_indices_to_slice}\n", + " 24 0.000 0.000 0.000 0.000 {built-in method _warnings._filters_mutated}\n", + " 7 0.000 0.000 0.000 0.000 managers.py:217(is_single_block)\n", + " 3 0.000 0.000 0.000 0.000 managers.py:973(from_blocks)\n", + " 3 0.000 0.000 0.000 0.000 frame.py:3883(_ensure_valid_index)\n", + " 2 0.000 0.000 0.000 0.000 base.py:768(_shallow_copy)\n", + " 2 0.000 0.000 0.000 0.000 groupby.py:977()\n", + " 4 0.000 0.000 0.000 0.000 common.py:1747(pandas_dtype)\n", + " 11 0.000 0.000 0.000 0.000 {built-in method _abc._abc_instancecheck}\n", + " 24 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_float}\n", + " 2 0.000 0.000 0.000 0.000 generic.py:1401()\n", + " 1 0.000 0.000 0.000 0.000 api.py:322(default_index)\n", + " 4 0.000 0.000 0.000 0.000 frame.py:3920(_clear_item_cache)\n", + " 3 0.000 0.000 0.000 0.000 generic.py:3925(_set_is_copy)\n", + " 4 0.000 0.000 0.000 0.000 common.py:1148(needs_i8_conversion)\n", + " 3 0.000 0.000 0.000 0.000 _asarray.py:111()\n", + " 3 0.000 0.000 0.000 0.000 shape_base.py:218(_vhstack_dispatcher)\n", + " 19 0.000 0.000 0.000 0.000 multiarray.py:1071(copyto)\n", + " 1 0.000 0.000 0.000 0.000 base.py:57(_validate_set_axis)\n", + " 3 0.000 0.000 0.000 0.000 base.py:2642(_na_value)\n", + " 16 0.000 0.000 0.000 0.000 base.py:937(dtype)\n", + " 2 0.000 0.000 0.000 0.000 base.py:4230(_maybe_preserve_names)\n", + " 13 0.000 0.000 0.000 0.000 blocks.py:354(dtype)\n", + " 2 0.000 0.000 0.000 0.000 algorithms.py:237(_ensure_arraylike)\n", + " 2 0.000 0.000 0.000 0.000 cast.py:2147(can_hold_element)\n", + " 6 0.000 0.000 0.000 0.000 {pandas._libs.internals.get_blkno_placements}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method posix.urandom}\n", + " 15 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_scalar}\n", + " 17 0.000 0.000 0.000 0.000 multiarray.py:148(concatenate)\n", + " 9 0.000 0.000 0.000 0.000 blocks.py:310(_slice)\n", + " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(bincount)\n", + " 2 0.000 0.000 0.000 0.000 base.py:3554(_assert_can_do_setop)\n", + " 19 0.000 0.000 0.000 0.000 base.py:326(ndim)\n", + " 3 0.000 0.000 0.000 0.000 generic.py:566(_get_block_manager_axis)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:494()\n", + " 14 0.000 0.000 0.000 0.000 base.py:5905(_index_as_unique)\n", + " 3 0.000 0.000 0.000 0.000 {built-in method numpy.zeros}\n", + " 2 0.000 0.000 0.000 0.000 contextlib.py:82(__init__)\n", + " 14 0.000 0.000 0.000 0.000 managers.py:172(blklocs)\n", + " 5 0.000 0.000 0.000 0.000 numerictypes.py:649()\n", + " 10 0.000 0.000 0.000 0.000 {method 'insert' of 'list' objects}\n", + " 2 0.000 0.000 0.000 0.000 base.py:723(tolist)\n", + " 2 0.000 0.000 0.000 0.000 blocks.py:222(get_values)\n", + " 4 0.000 0.000 0.000 0.000 {method 'transpose' of 'numpy.ndarray' objects}\n", + " 15 0.000 0.000 0.000 0.000 {built-in method builtins.callable}\n", + " 1 0.000 0.000 0.000 0.000 range.py:167(_simple_new)\n", + " 5 0.000 0.000 0.000 0.000 blocks.py:358(iget)\n", + " 4 0.000 0.000 0.000 0.000 numeric.py:1391()\n", + " 6 0.000 0.000 0.000 0.000 base.py:518()\n", + " 14 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_integer}\n", + " 3 0.000 0.000 0.000 0.000 grouper.py:802()\n", + " 15 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_iterator}\n", + " 4 0.000 0.000 0.000 0.000 {method 'view' of 'numpy.ndarray' objects}\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:570(name)\n", + " 10 0.000 0.000 0.000 0.000 {method 'ravel' of 'numpy.ndarray' objects}\n", + " 9 0.000 0.000 0.000 0.000 base.py:540(_ensure_array)\n", + " 2 0.000 0.000 0.000 0.000 groupby.py:898(__getattr__)\n", + " 10 0.000 0.000 0.000 0.000 {method 'index' of 'list' objects}\n", + " 12 0.000 0.000 0.000 0.000 base.py:229(disallow_kwargs)\n", + " 9 0.000 0.000 0.000 0.000 blocks.py:244(mgr_locs)\n", + " 1 0.000 0.000 0.000 0.000 typing.py:868(__new__)\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:838(is_in_axis)\n", + " 3 0.000 0.000 0.000 0.000 shape_base.py:207(_arrays_for_stack_dispatcher)\n", + " 8 0.000 0.000 0.000 0.000 {built-in method numpy.core._multiarray_umath.normalize_axis_index}\n", + " 1 0.000 0.000 0.000 0.000 managers.py:1722(from_blocks)\n", + " 2 0.000 0.000 0.000 0.000 base.py:1701(_get_names)\n", + " 9 0.000 0.000 0.000 0.000 managers.py:919(__init__)\n", + " 9 0.000 0.000 0.000 0.000 managers.py:1257()\n", + " 8 0.000 0.000 0.000 0.000 enum.py:753(value)\n", + " 8 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 pipeline.py:140(get_rn_generator)\n", + " 2 0.000 0.000 0.000 0.000 range.py:347(dtype)\n", + " 5 0.000 0.000 0.000 0.000 frame.py:804(axes)\n", + " 3 0.000 0.000 0.000 0.000 {built-in method pandas._libs.missing.checknull}\n", + " 1 0.000 0.000 0.000 0.000 common.py:1042(is_numeric_v_string_like)\n", + " 5 0.000 0.000 0.000 0.000 {method 'clear' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 sorting.py:148()\n", + " 1 0.000 0.000 0.000 0.000 construction.py:486()\n", + " 3 0.000 0.000 0.000 0.000 grouper.py:804()\n", + " 5 0.000 0.000 0.000 0.000 generic.py:660(ndim)\n", + " 7 0.000 0.000 0.000 0.000 ops.py:680(groupings)\n", + " 5 0.000 0.000 0.000 0.000 fromnumeric.py:3164(ndim)\n", + " 3 0.000 0.000 0.000 0.000 grouper.py:803()\n", + " 1 0.000 0.000 0.000 0.000 ops.py:660(__init__)\n", + " 6 0.000 0.000 0.000 0.000 missing.py:107(clean_fill_method)\n", + " 1 0.000 0.000 0.000 0.000 sorting.py:135(_int64_cut_off)\n", + " 5 0.000 0.000 0.000 0.000 {pandas._libs.algos.ensure_int64}\n", + " 1 0.000 0.000 0.000 0.000 generic.py:4060(_check_inplace_and_allows_duplicate_labels)\n", + " 8 0.000 0.000 0.000 0.000 base.py:6022(_is_comparable_dtype)\n", + " 2 0.000 0.000 0.000 0.000 base.py:7046(ensure_has_len)\n", + " 5 0.000 0.000 0.000 0.000 base.py:692(_constructor)\n", + " 2 0.000 0.000 0.000 0.000 common.py:315(is_datetime64_dtype)\n", + " 5 0.000 0.000 0.000 0.000 numerictypes.py:650()\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:921(_is_label_like)\n", + " 5 0.000 0.000 0.000 0.000 base.py:1124(_maybe_disallow_fill)\n", + " 1 0.000 0.000 0.000 0.000 _decorators.py:214(_format_argument_list)\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:851(is_in_obj)\n", + " 2 0.000 0.000 0.000 0.000 common.py:389(is_timedelta64_dtype)\n", + " 1 0.000 0.000 0.000 0.000 base.py:890(__array__)\n", + " 5 0.000 0.000 0.000 0.000 fromnumeric.py:3160(_ndim_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 _validators.py:218(validate_bool_kwarg)\n", + " 3 0.000 0.000 0.000 0.000 generic.py:3948(_check_setitem_copy)\n", + " 3 0.000 0.000 0.000 0.000 fromnumeric.py:432(_repeat_dispatcher)\n", + " 3 0.000 0.000 0.000 0.000 {method 'upper' of 'str' objects}\n", + " 1 0.000 0.000 0.000 0.000 construction.py:233(mgr_to_mgr)\n", + " 3 0.000 0.000 0.000 0.000 managers.py:2111()\n", + " 4 0.000 0.000 0.000 0.000 fromnumeric.py:1751(_ravel_dispatcher)\n", + " 4 0.000 0.000 0.000 0.000 {built-in method _operator.index}\n", + " 3 0.000 0.000 0.000 0.000 function.py:49(__call__)\n", + " 3 0.000 0.000 0.000 0.000 frame.py:578(_constructor)\n", + " 2 0.000 0.000 0.000 0.000 cast.py:2185(np_can_hold_element)\n", + " 4 0.000 0.000 0.000 0.000 function_base.py:5333(_append_dispatcher)\n", + " 3 0.000 0.000 0.000 0.000 {method 'pop' of 'dict' objects}\n", + " 3 0.000 0.000 0.000 0.000 numeric.py:199(_ensure_dtype)\n", + " 1 0.000 0.000 0.000 0.000 pipeline.py:72(rng)\n", + " 3 0.000 0.000 0.000 0.000 numeric.py:188(_validate_dtype)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method pandas._libs.missing.is_matching_na}\n", + " 3 0.000 0.000 0.000 0.000 fromnumeric.py:1008(_argsort_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 base.py:3050(_validate_sort_keyword)\n", + " 3 0.000 0.000 0.000 0.000 shape_base.py:77(_atleast_2d_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 {method 'tolist' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.000 0.000 blocks.py:306(__len__)\n", + " 2 0.000 0.000 0.000 0.000 {method 'item' of 'numpy.ndarray' objects}\n", + " 2 0.000 0.000 0.000 0.000 shape_base.py:1243()\n", + " 2 0.000 0.000 0.000 0.000 shape_base.py:1253()\n", + " 2 0.000 0.000 0.000 0.000 grouper.py:588(_ilevel)\n", + " 2 0.000 0.000 0.000 0.000 function_base.py:1291(_diff_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 numeric.py:1466()\n", + " 1 0.000 0.000 0.000 0.000 managers.py:1118(value_getitem)\n", + " 1 0.000 0.000 0.000 0.000 range.py:241(start)\n", + " 2 0.000 0.000 0.000 0.000 generic.py:1772()\n", + " 2 0.000 0.000 0.000 0.000 base.py:7191(_maybe_try_sort)\n", + " 2 0.000 0.000 0.000 0.000 function_base.py:5139(_insert_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 fromnumeric.py:1862(_nonzero_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 multiarray.py:883(bincount)\n", + " 1 0.000 0.000 0.000 0.000 range.py:287(step)\n", + " 2 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_bool_list}\n", + " 2 0.000 0.000 0.000 0.000 arraysetops.py:515(_in1d_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 arraysetops.py:780(_setdiff1d_dispatcher)\n", + " 2 0.000 0.000 0.000 0.000 function_base.py:4954(_delete_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:70()\n", + " 2 0.000 0.000 0.000 0.000 {method 'add' of 'set' objects}\n", + " 1 0.000 0.000 0.000 0.000 {built-in method from_bytes}\n", + " 1 0.000 0.000 0.000 0.000 blocks.py:248(mgr_locs)\n", + " 2 0.000 0.000 0.000 0.000 base.py:1898(nlevels)\n", + " 2 0.000 0.000 0.000 0.000 numeric.py:1400(_moveaxis_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 multiarray.py:1106(putmask)\n", + " 1 0.000 0.000 0.000 0.000 {method 'keys' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 contextlib.py:59(_recreate_cm)\n", + " 1 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 {method 'append' of 'pandas._libs.internals.BlockPlacement' objects}\n", + " 1 0.000 0.000 0.000 0.000 range.py:183(_constructor)\n", + " 1 0.000 0.000 0.000 0.000 range.py:264(stop)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:1167(_tile_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2965(_prod_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 base.py:4786(values)\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.000 0.000 0.000 0.000 managers.py:282()\n", + " 1 0.000 0.000 0.000 0.000 {method 'clear_mapping' of 'pandas._libs.index.IndexEngine' objects}\n", + " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_bool}" ] }, - "execution_count": 39, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "test_df = temp_choices.copy()\n", - "del test_df['rand']\n", - "test_df.value_counts().to_frame(\"pick_count\").reset_index([\"prob\", \"alt_dest\"])" + "%prun -s cumulative run_previous()" ] }, { "cell_type": "code", - "execution_count": 31, - "id": "504b03dc", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
alt_destrandprobperson_id
01000.205884386008
11000.288773418442
22100.147571595686
3300.005248644292
4800.117232644478
...............
4202500.2657442566702
4211000.2887732877287
422900.3367083596365
423600.0321243891104
4241000.0970484171620
\n", - "

425 rows × 4 columns

\n", - "
" - ], - "text/plain": [ - " alt_dest rand prob person_id\n", - "0 10 0 0.205884 386008\n", - "1 10 0 0.288773 418442\n", - "2 21 0 0.147571 595686\n", - "3 3 0 0.005248 644292\n", - "4 8 0 0.117232 644478\n", - ".. ... ... ... ...\n", - "420 25 0 0.265744 2566702\n", - "421 10 0 0.288773 2877287\n", - "422 9 0 0.336708 3596365\n", - "423 6 0 0.032124 3891104\n", - "424 10 0 0.097048 4171620\n", - "\n", - "[425 rows x 4 columns]" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": null, + "id": "35a9df01-2b32-41c3-b9e2-259b21f214c6", + "metadata": {}, + "outputs": [], "source": [ - "test_df" + "37642 function calls (37233 primitive calls) in 3.187 seconds\n", + "406159 function calls (406079 primitive calls) in 60.697 seconds\n", + "\n", + " Ordered by: cumulative time\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.000 0.000 60.697 60.697 {built-in method builtins.exec}\n", + " 1 0.000 0.000 60.697 60.697 :1()\n", + " 1 0.033 0.033 60.697 60.697 2292568830.py:1(run_fru)\n", + " 1 37.147 37.147 60.664 60.664 3636660809.py:1(make_sample_choices_dev)\n", + " 30 0.745 0.025 22.155 0.738 random.py:562(random_for_df)\n", + " 30 0.192 0.006 13.381 0.446 random.py:598()\n", + " 404130 13.189 0.000 13.189 0.000 {method 'rand' of 'numpy.random.mtrand.RandomState' objects}\n", + " 32 8.025 0.251 8.025 0.251 {built-in method numpy.asanyarray}\n", + " 70/66 0.128 0.002 1.361 0.021 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 32 0.000 0.000 1.232 0.039 fromnumeric.py:51(_wrapfunc)\n", + " 30 0.000 0.000 1.104 0.037 <__array_function__ internals>:177(argmax)\n", + " 30 0.000 0.000 1.103 0.037 fromnumeric.py:1127(argmax)\n", + " 30 1.103 0.037 1.103 0.037 {method 'argmax' of 'numpy.ndarray' objects}\n", + " \n", + "w/o random calls (test where we simply take max(obs_utlils) but everything else is identical)\n", + " 1039 function calls (1019 primitive calls) in 1.403 seconds\n", + " Ordered by: cumulative time\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.000 0.000 1.403 1.403 {built-in method builtins.exec}\n", + " 1 0.000 0.000 1.403 1.403 :1()\n", + " 1 0.000 0.000 1.403 1.403 2292568830.py:1(run_fru)\n", + " 1 0.021 0.021 1.403 1.403 691721383.py:1(make_sample_choices_dev)\n", + " 40/36 0.120 0.003 1.380 0.038 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 32 0.000 0.000 1.260 0.039 fromnumeric.py:51(_wrapfunc)\n", + " 30 0.000 0.000 1.134 0.038 <__array_function__ internals>:177(argmax)\n", + " 30 0.000 0.000 1.133 0.038 fromnumeric.py:1127(argmax)\n", + " 30 1.133 0.038 1.133 0.038 {method 'argmax' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.127 0.127 <__array_function__ internals>:177(nonzero)\n", + " 1 0.000 0.000 0.127 0.127 fromnumeric.py:1866(nonzero)\n", + " 1 0.127 0.127 0.127 0.127 {method 'nonzero' of 'numpy.ndarray' objects}" ] }, { "cell_type": "code", - "execution_count": 40, - "id": "f332d85e", + "execution_count": 38, + "id": "082ecf8f-bf23-4a61-be4f-a0d914469f18", "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } + "tags": [] }, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " " + ] + }, { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
0123456789101112131415161718192021222324
person_id
3860080.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
4184420.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
5956860.3634421.3139322.0216621.5996513.2016453.7260504.5603105.6107585.2546575.2024085.3858281.836706-0.1492320.6045820.2071104.4690413.7408913.9913713.1458624.0055005.4888311.7809720.8465322.0469014.184942
6442920.4261421.4195082.2679471.7966783.7106344.9487995.8081545.9391385.5301235.1695705.0574491.508326-0.3790980.4983250.1330103.8104903.3124583.1371542.7516293.5845584.4215961.8436720.9092322.1153014.430042
6444781.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
19586780.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
21590590.0442420.9947321.7024621.1518222.6644083.6177504.3384395.2495405.6614755.9412635.0738671.056589-0.7199880.258925-0.1063903.8446903.3466583.3017733.5727564.9742214.9141661.4617720.5273321.7334013.962642
22199981.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
24585020.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
24585030.4090421.3702512.1530141.7310023.4807684.7189335.6439646.5794796.0719505.5143695.2380571.590421-0.3955170.4869250.1159103.7990903.3067583.1656542.8143293.6985584.5693671.8265720.8921322.0925014.355942
25667000.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667010.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
25667020.8808552.2568772.9903832.0101253.4643494.1442684.6424055.0491074.9810574.7404564.3972831.278460-0.3955170.6538390.3204293.8845903.3067582.9433542.3868293.2197584.0991682.5970801.3424323.0830895.864195
28772870.1126421.0631321.7708621.2202222.7442083.7032504.4453775.4958256.1376266.3681575.3201521.124989-0.6572880.321625-0.0436903.6508903.1528583.0972543.2443764.6622604.3723391.5301720.5957321.7961014.042442
35963650.1582421.1087321.8164621.2715222.8012083.9800784.8722715.9063006.6630346.1711295.0902861.045189-0.7370880.241825-0.1234903.5710903.0787583.0174542.9967294.3338804.1960681.5757720.6413321.8474014.099442
38911041.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
41716201.2420731.9120782.2679471.9280312.9389413.6177504.2871394.9123074.8613574.7233564.4086831.6725160.3433381.6553981.1742175.3556674.3648133.2454542.6889293.4306584.2902442.0659721.2341322.3934914.276142
\n", - "
" - ], "text/plain": [ - " 0 1 2 3 4 5 \\\n", - "person_id \n", - "386008 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n", - "418442 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n", - "595686 0.363442 1.313932 2.021662 1.599651 3.201645 3.726050 \n", - "644292 0.426142 1.419508 2.267947 1.796678 3.710634 4.948799 \n", - "644478 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", - "1958678 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n", - "2159059 0.044242 0.994732 1.702462 1.151822 2.664408 3.617750 \n", - "2219998 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", - "2458502 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n", - "2458503 0.409042 1.370251 2.153014 1.731002 3.480768 4.718933 \n", - "2566700 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n", - "2566701 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n", - "2566702 0.880855 2.256877 2.990383 2.010125 3.464349 4.144268 \n", - "2877287 0.112642 1.063132 1.770862 1.220222 2.744208 3.703250 \n", - "3596365 0.158242 1.108732 1.816462 1.271522 2.801208 3.980078 \n", - "3891104 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", - "4171620 1.242073 1.912078 2.267947 1.928031 2.938941 3.617750 \n", - "\n", - " 6 7 8 9 10 11 \\\n", - "person_id \n", - "386008 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n", - "418442 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n", - "595686 4.560310 5.610758 5.254657 5.202408 5.385828 1.836706 \n", - "644292 5.808154 5.939138 5.530123 5.169570 5.057449 1.508326 \n", - "644478 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", - "1958678 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n", - "2159059 4.338439 5.249540 5.661475 5.941263 5.073867 1.056589 \n", - "2219998 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", - "2458502 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n", - "2458503 5.643964 6.579479 6.071950 5.514369 5.238057 1.590421 \n", - "2566700 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n", - "2566701 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n", - "2566702 4.642405 5.049107 4.981057 4.740456 4.397283 1.278460 \n", - "2877287 4.445377 5.495825 6.137626 6.368157 5.320152 1.124989 \n", - "3596365 4.872271 5.906300 6.663034 6.171129 5.090286 1.045189 \n", - "3891104 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", - "4171620 4.287139 4.912307 4.861357 4.723356 4.408683 1.672516 \n", + " 406159 function calls (406079 primitive calls) in 61.192 seconds\n", "\n", - " 12 13 14 15 16 17 \\\n", - "person_id \n", - "386008 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n", - "418442 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n", - "595686 -0.149232 0.604582 0.207110 4.469041 3.740891 3.991371 \n", - "644292 -0.379098 0.498325 0.133010 3.810490 3.312458 3.137154 \n", - "644478 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", - "1958678 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n", - "2159059 -0.719988 0.258925 -0.106390 3.844690 3.346658 3.301773 \n", - "2219998 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", - "2458502 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n", - "2458503 -0.395517 0.486925 0.115910 3.799090 3.306758 3.165654 \n", - "2566700 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n", - "2566701 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n", - "2566702 -0.395517 0.653839 0.320429 3.884590 3.306758 2.943354 \n", - "2877287 -0.657288 0.321625 -0.043690 3.650890 3.152858 3.097254 \n", - "3596365 -0.737088 0.241825 -0.123490 3.571090 3.078758 3.017454 \n", - "3891104 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", - "4171620 0.343338 1.655398 1.174217 5.355667 4.364813 3.245454 \n", + " Ordered by: cumulative time\n", "\n", - " 18 19 20 21 22 23 \\\n", - "person_id \n", - "386008 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n", - "418442 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n", - "595686 3.145862 4.005500 5.488831 1.780972 0.846532 2.046901 \n", - "644292 2.751629 3.584558 4.421596 1.843672 0.909232 2.115301 \n", - "644478 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", - "1958678 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n", - "2159059 3.572756 4.974221 4.914166 1.461772 0.527332 1.733401 \n", - "2219998 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", - "2458502 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n", - "2458503 2.814329 3.698558 4.569367 1.826572 0.892132 2.092501 \n", - "2566700 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n", - "2566701 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n", - "2566702 2.386829 3.219758 4.099168 2.597080 1.342432 3.083089 \n", - "2877287 3.244376 4.662260 4.372339 1.530172 0.595732 1.796101 \n", - "3596365 2.996729 4.333880 4.196068 1.575772 0.641332 1.847401 \n", - "3891104 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", - "4171620 2.688929 3.430658 4.290244 2.065972 1.234132 2.393491 \n", - "\n", - " 24 \n", - "person_id \n", - "386008 4.099442 \n", - "418442 4.042442 \n", - "595686 4.184942 \n", - "644292 4.430042 \n", - "644478 4.276142 \n", - "1958678 4.099442 \n", - "2159059 3.962642 \n", - "2219998 4.276142 \n", - "2458502 4.355942 \n", - "2458503 4.355942 \n", - "2566700 5.864195 \n", - "2566701 5.864195 \n", - "2566702 5.864195 \n", - "2877287 4.042442 \n", - "3596365 4.099442 \n", - "3891104 4.276142 \n", - "4171620 4.276142 " + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.000 0.000 61.192 61.192 {built-in method builtins.exec}\n", + " 1 0.000 0.000 61.192 61.192 :1()\n", + " 1 0.027 0.027 61.192 61.192 2292568830.py:1(run_fru)\n", + " 1 37.470 37.470 61.165 61.165 1111942506.py:1(make_sample_choices_dev)\n", + " 30 0.778 0.026 22.305 0.744 random.py:562(random_for_df)\n", + " 30 0.195 0.006 13.419 0.447 random.py:598()\n", + " 404130 13.225 0.000 13.225 0.000 {method 'rand' of 'numpy.random.mtrand.RandomState' objects}\n", + " 32 8.104 0.253 8.104 0.253 {built-in method numpy.asanyarray}\n", + " 70/66 0.122 0.002 1.388 0.021 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", + " 32 0.000 0.000 1.265 0.040 fromnumeric.py:51(_wrapfunc)\n", + " 30 0.000 0.000 1.129 0.038 <__array_function__ internals>:177(argmax)\n", + " 30 0.000 0.000 1.128 0.038 fromnumeric.py:1127(argmax)\n", + " 30 1.128 0.038 1.128 0.038 {method 'argmax' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.137 0.137 <__array_function__ internals>:177(nonzero)\n", + " 1 0.000 0.000 0.137 0.137 fromnumeric.py:1866(nonzero)\n", + " 1 0.137 0.137 0.137 0.137 {method 'nonzero' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.122 0.122 <__array_function__ internals>:177(zeros_like)\n", + " 1 0.000 0.000 0.122 0.122 numeric.py:76(zeros_like)\n", + " 2 0.000 0.000 0.122 0.061 <__array_function__ internals>:177(copyto)\n", + " 30 0.000 0.000 0.003 0.000 contextlib.py:72(inner)\n", + " 30 0.002 0.000 0.002 0.000 {function SeedSequence.generate_state at 0x7fb5e410ac10}\n", + " 1 0.000 0.000 0.001 0.001 frame.py:587(__init__)\n", + " 1 0.000 0.000 0.001 0.001 construction.py:425(dict_to_mgr)\n", + " 1 0.000 0.000 0.001 0.001 construction.py:102(arrays_to_mgr)\n", + " 60 0.000 0.000 0.001 0.000 _ufunc_config.py:32(seterr)\n", + " 30 0.000 0.000 0.000 0.000 random.py:721(getrandbits)\n", + " 30 0.000 0.000 0.000 0.000 _ufunc_config.py:429(__enter__)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:1937(create_block_manager_from_column_arrays)\n", + " 158/83 0.000 0.000 0.000 0.000 {built-in method builtins.len}\n", + " 32 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(concatenate)\n", + " 30 0.000 0.000 0.000 0.000 {built-in method posix.urandom}\n", + " 2/1 0.000 0.000 0.000 0.000 base.py:397(__new__)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:1683(_consolidate_inplace)\n", + " 32 0.000 0.000 0.000 0.000 frame.py:1413(__len__)\n", + " 30 0.000 0.000 0.000 0.000 _ufunc_config.py:434(__exit__)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:2074(_consolidate)\n", + " 60 0.000 0.000 0.000 0.000 _ufunc_config.py:131(geterr)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:2008(_form_blocks)\n", + " 190 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(empty_like)\n", + " 5 0.000 0.000 0.000 0.000 construction.py:470(sanitize_array)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:596(_homogenize)\n", + " 38 0.000 0.000 0.000 0.000 abc.py:96(__instancecheck__)\n", + " 30 0.000 0.000 0.000 0.000 pipeline.py:140(get_rn_generator)\n", + " 3 0.000 0.000 0.000 0.000 managers.py:2091(_merge_blocks)\n", + " 60 0.000 0.000 0.000 0.000 {built-in method numpy.seterrobj}\n", + " 2 0.000 0.000 0.000 0.000 frame.py:1693(to_numpy)\n", + " 36 0.000 0.000 0.000 0.000 base.py:884(__len__)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method builtins.sorted}\n", + " 8 0.000 0.000 0.000 0.000 managers.py:2079()\n", + " 38 0.000 0.000 0.000 0.000 {built-in method _abc._abc_instancecheck}\n", + " 120 0.000 0.000 0.000 0.000 {built-in method numpy.geterrobj}\n", + " 1 0.000 0.000 0.000 0.000 base.py:7106(_maybe_cast_data_without_dtype)\n", + " 4 0.000 0.000 0.000 0.000 blocks.py:166(_consolidate_key)\n", + " 88 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}\n", + " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.maybe_convert_objects}\n", + " 4 0.000 0.000 0.000 0.000 managers.py:2060(_stack_arrays)\n", + " 3 0.000 0.000 0.000 0.000 generic.py:5646(_consolidate_inplace)\n", + " 4 0.000 0.000 0.000 0.000 _dtype.py:328(_name_get)\n", + " 3 0.000 0.000 0.000 0.000 generic.py:5632(_protect_consolidate)\n", + " 41 0.000 0.000 0.000 0.000 generic.py:43(_check)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:635(_extract_index)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(vstack)\n", + " 2 0.000 0.000 0.000 0.000 chunk.py:599(log_df)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:487()\n", + " 2 0.000 0.000 0.000 0.000 chunk.py:140(chunk_training_mode)\n", + " 1 0.000 0.000 0.000 0.000 generic.py:2063(__array__)\n", + " 4 0.000 0.000 0.000 0.000 common.py:348(is_datetime64tz_dtype)\n", + " 2 0.000 0.000 0.000 0.000 frame.py:821(shape)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:222(vstack)\n", + " 5 0.000 0.000 0.000 0.000 construction.py:379(extract_array)\n", + " 2 0.000 0.000 0.000 0.000 managers.py:1541(as_array)\n", + " 4 0.000 0.000 0.000 0.000 _asarray.py:22(require)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:483()\n", + " 2 0.000 0.000 0.000 0.000 common.py:229(asarray_tuplesafe)\n", + " 1 0.000 0.000 0.000 0.000 frame.py:898(_values)\n", + " 5 0.000 0.000 0.000 0.000 construction.py:695(_try_cast)\n", + " 4 0.000 0.000 0.000 0.000 _dtype.py:314(_name_includes_bit_suffix)\n", + " 2 0.000 0.000 0.000 0.000 config.py:108(setting)\n", + " 4 0.000 0.000 0.000 0.000 base.py:286(is_dtype)\n", + " 4 0.000 0.000 0.000 0.000 common.py:287(maybe_iterable_to_list)\n", + " 1 0.000 0.000 0.000 0.000 config.py:255(__call__)\n", + " 3 0.000 0.000 0.000 0.000 generic.py:5650(f)\n", + " 1 0.000 0.000 0.000 0.000 config.py:127(_get_option)\n", + " 30 0.000 0.000 0.000 0.000 {built-in method from_bytes}\n", + " 30 0.000 0.000 0.000 0.000 fromnumeric.py:1123(_argmax_dispatcher)\n", + " 30 0.000 0.000 0.000 0.000 pipeline.py:72(rng)\n", + " 4 0.000 0.000 0.000 0.000 managers.py:1665(is_consolidated)\n", + " 4 0.000 0.000 0.000 0.000 numerictypes.py:356(issubdtype)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method numpy.arange}\n", + " 1 0.000 0.000 0.000 0.000 {method '_rebuild_blknos_and_blklocs' of 'pandas._libs.internals.BlockManager' objects}\n", + " 1 0.000 0.000 0.000 0.000 base.py:554(_dtype_to_subclass)\n", + " 5 0.000 0.000 0.000 0.000 blocks.py:1989(get_block_type)\n", + " 32 0.000 0.000 0.000 0.000 multiarray.py:148(concatenate)\n", + " 1 0.000 0.000 0.000 0.000 numeric.py:289(full)\n", + " 2 0.000 0.000 0.000 0.000 inject.py:121(get_injectable)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:1673(_consolidate_check)\n", + " 2 0.000 0.000 0.000 0.000 base.py:7082(maybe_extract_name)\n", + " 5 0.000 0.000 0.000 0.000 common.py:1587(_is_dtype_type)\n", + " 1 0.000 0.000 0.000 0.000 config.py:109(_get_single_key)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(argsort)\n", + " 4 0.000 0.000 0.000 0.000 common.py:552(require_length_match)\n", + " 1 0.000 0.000 0.000 0.000 base.py:654(_simple_new)\n", + " 6 0.000 0.000 0.000 0.000 {built-in method numpy.empty}\n", + " 5 0.000 0.000 0.000 0.000 construction.py:627(_sanitize_ndim)\n", + " 2 0.000 0.000 0.000 0.000 common.py:1240(is_float_dtype)\n", + " 7 0.000 0.000 0.000 0.000 range.py:909(__len__)\n", + " 32 0.000 0.000 0.000 0.000 {built-in method builtins.issubclass}\n", + " 1 0.000 0.000 0.000 0.000 cast.py:1960(construct_1d_object_array_from_listlike)\n", + " 30 0.000 0.000 0.000 0.000 contextlib.py:59(_recreate_cm)\n", + " 4 0.000 0.000 0.000 0.000 managers.py:1992(_grouping_func)\n", + " 1 0.000 0.000 0.000 0.000 blocks.py:2030(new_block_2d)\n", + " 8 0.000 0.000 0.000 0.000 numerictypes.py:282(issubclass_)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:1012(argsort)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:1679()\n", + " 1 0.000 0.000 0.000 0.000 base.py:53(shape)\n", + " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(atleast_2d)\n", + " 6 0.000 0.000 0.000 0.000 {built-in method numpy.array}\n", + " 2 0.000 0.000 0.000 0.000 orca.py:1610(get_injectable)\n", + " 3 0.000 0.000 0.000 0.000 managers.py:618(consolidate)\n", + " 3 0.000 0.000 0.000 0.000 blocks.py:2119(extend_blocks)\n", + " 1 0.000 0.000 0.000 0.000 api.py:322(default_index)\n", + " 1 0.000 0.000 0.000 0.000 generic.py:239(__init__)\n", + " 4 0.000 0.000 0.000 0.000 common.py:1483(is_ea_or_datetimelike_dtype)\n", + " 4 0.000 0.000 0.000 0.000 _asarray.py:111()\n", + " 3 0.000 0.000 0.000 0.000 generic.py:5577(__setattr__)\n", + " 3 0.000 0.000 0.000 0.000 base.py:55()\n", + " 1 0.000 0.000 0.000 0.000 construction.py:494()\n", + " 2 0.000 0.000 0.000 0.000 :1017(_handle_fromlist)\n", + " 2 0.000 0.000 0.000 0.000 blocks.py:222(get_values)\n", + " 5 0.000 0.000 0.000 0.000 construction.py:664(_sanitize_str_dtypes)\n", + " 5 0.000 0.000 0.000 0.000 {built-in method numpy.asarray}\n", + " 17 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}\n", + " 1 0.000 0.000 0.000 0.000 config.py:589(_get_root)\n", + " 1 0.000 0.000 0.000 0.000 common.py:786(is_unsigned_integer_dtype)\n", + " 3 0.000 0.000 0.000 0.000 construction.py:438(ensure_wrapped_if_datetimelike)\n", + " 1 0.000 0.000 0.000 0.000 blocks.py:1960(maybe_coerce_values)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:81(atleast_2d)\n", + " 1 0.000 0.000 0.000 0.000 {method 'argsort' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.000 0.000 range.py:167(_simple_new)\n", + " 5 0.000 0.000 0.000 0.000 construction.py:684(_maybe_repeat)\n", + " 14 0.000 0.000 0.000 0.000 typing.py:1149(cast)\n", + " 1 0.000 0.000 0.000 0.000 common.py:161(is_object_dtype)\n", + " 1 0.000 0.000 0.000 0.000 config.py:642(_warn_if_deprecated)\n", + " 1 0.000 0.000 0.000 0.000 common.py:732(is_signed_integer_dtype)\n", + " 3 0.000 0.000 0.000 0.000 common.py:147()\n", + " 10 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_list_like}\n", + " 4 0.000 0.000 0.000 0.000 common.py:1416(is_1d_only_ea_dtype)\n", + " 2 0.000 0.000 0.000 0.000 inject.py:116(is_injectable)\n", + " 9 0.000 0.000 0.000 0.000 {built-in method builtins.hasattr}\n", + " 2 0.000 0.000 0.000 0.000 inference.py:321(is_hashable)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method numpy.zeros}\n", + " 1 0.000 0.000 0.000 0.000 managers.py:2102()\n", + " 1 0.000 0.000 0.000 0.000 construction.py:486()\n", + " 2 0.000 0.000 0.000 0.000 generic.py:5561(__getattr__)\n", + " 2 0.000 0.000 0.000 0.000 config.py:603(_get_deprecated_option)\n", + " 4 0.000 0.000 0.000 0.000 {method 'format' of 'str' objects}\n", + " 2 0.000 0.000 0.000 0.000 orca.py:1568(get_raw_injectable)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:218(_vhstack_dispatcher)\n", + " 4 0.000 0.000 0.000 0.000 cast.py:1711(sanitize_to_nanoseconds)\n", + " 2 0.000 0.000 0.000 0.000 {method 'transpose' of 'numpy.ndarray' objects}\n", + " 2 0.000 0.000 0.000 0.000 managers.py:217(is_single_block)\n", + " 1 0.000 0.000 0.000 0.000 common.py:1747(pandas_dtype)\n", + " 2 0.000 0.000 0.000 0.000 base.py:6987(ensure_index)\n", + " 1 0.000 0.000 0.000 0.000 {built-in method builtins.all}\n", + " 4 0.000 0.000 0.000 0.000 orca.py:1560(is_injectable)\n", + " 1 0.000 0.000 0.000 0.000 flags.py:47(__init__)\n", + " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_scalar}\n", + " 2 0.000 0.000 0.000 0.000 common.py:155()\n", + " 1 0.000 0.000 0.000 0.000 config.py:571(_select_options)\n", + " 4 0.000 0.000 0.000 0.000 blocks.py:354(dtype)\n", + " 4 0.000 0.000 0.000 0.000 _dtype.py:24(_kind_name)\n", + " 4 0.000 0.000 0.000 0.000 {method 'upper' of 'str' objects}\n", + " 2 0.000 0.000 0.000 0.000 base.py:4786(values)\n", + " 1 0.000 0.000 0.000 0.000 construction.py:233(mgr_to_mgr)\n", + " 2 0.000 0.000 0.000 0.000 base.py:518()\n", + " 2 0.000 0.000 0.000 0.000 base.py:834(_reset_identity)\n", + " 1 0.000 0.000 0.000 0.000 config.py:630(_translate_key)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:207(_arrays_for_stack_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 base.py:1650(name)\n", + " 2 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}\n", + " 3 0.000 0.000 0.000 0.000 common.py:145(classes)\n", + " 2 0.000 0.000 0.000 0.000 common.py:150(classes_and_not_datetimelike)\n", + " 2 0.000 0.000 0.000 0.000 blocks.py:244(mgr_locs)\n", + " 2 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x908780}\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.hash}\n", + " 2 0.000 0.000 0.000 0.000 multiarray.py:1071(copyto)\n", + " 1 0.000 0.000 0.000 0.000 {method 'split' of 'str' objects}\n", + " 2 0.000 0.000 0.000 0.000 {method 'setdefault' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:1862(_nonzero_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 {method 'astype' of 'numpy.ndarray' objects}\n", + " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_iterator}\n", + " 1 0.000 0.000 0.000 0.000 base.py:540(_ensure_array)\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.000 0.000 0.000 0.000 {method 'keys' of 'dict' objects}\n", + " 1 0.000 0.000 0.000 0.000 managers.py:2111()\n", + " 1 0.000 0.000 0.000 0.000 multiarray.py:80(empty_like)\n", + " 1 0.000 0.000 0.000 0.000 managers.py:919(__init__)\n", + " 1 0.000 0.000 0.000 0.000 base.py:229(disallow_kwargs)\n", + " 1 0.000 0.000 0.000 0.000 numeric.py:72(_zeros_like_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 shape_base.py:77(_atleast_2d_dispatcher)\n", + " 1 0.000 0.000 0.000 0.000 fromnumeric.py:1008(_argsort_dispatcher)" ] }, - "execution_count": 40, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "utilities" + "%prun -s cumulative run_fru()" ] }, { "cell_type": "code", "execution_count": null, - "id": "c582bf26", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "display(sample_size)\n", - "sample_list[2].groupby('person_id').pick_count.sum()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "de698ddc", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, + "id": "66c833e5-2ad3-4052-ae11-2aa887ad9a4f", + "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, - "id": "0c8cb9b5", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, + "id": "5e0f3f75-1150-4712-bfb4-435e85af8b52", + "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, - "id": "0301d604", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, + "id": "1fc21256-564e-4842-82fb-4ba5358fb644", + "metadata": {}, "outputs": [], "source": [] }, - { - "cell_type": "code", - "execution_count": 46, - "id": "60fb8dac", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(2, 6)\n", - "[[1 2 3 4 5 6]\n", - " [4 6 5 9 9 6]]\n", - "[5 3]\n" - ] - } - ], - "source": [ - "utils = np.array([[1,2,3,4,5,6],[4,6,5,9,9,6]])\n", - "print(utils.shape)\n", - "print(utils)\n", - "print(np.argmax(utils, axis=1))" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "d43aeee6", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "ename": "TypeError", - "evalue": "_argmax_dispatcher() got an unexpected keyword argument 'keepdim'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/tmp/ipykernel_10839/398049723.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mutils\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0margmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mutils\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkeepdim\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m<__array_function__ internals>\u001b[0m in \u001b[0;36margmax\u001b[0;34m(*args, **kwargs)\u001b[0m\n", - "\u001b[0;31mTypeError\u001b[0m: _argmax_dispatcher() got an unexpected keyword argument 'keepdim'" - ] - } - ], - "source": [ - "utils[np.argmax(utils, axis=1)]" - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "id": "85892d45", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "utils[np.arange(2), np.argmax(utils, axis=1)] += 1" - ] - }, - { - "cell_type": "code", - "execution_count": 77, - "id": "477c7f3d", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 77, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "isinstance(utils, np.ndarray)" - ] - }, - { - "cell_type": "code", - "execution_count": 74, - "id": "a576a8a0", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n", - " 18, 19, 20, 21, 22, 23, 24, 25])" - ] - }, - "execution_count": 74, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dest_size_terms.index.values" - ] - }, { "cell_type": "code", "execution_count": null, - "id": "72c709ab", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, + "id": "ab332607-5f2e-4c7f-bfe4-5407c32644fb", + "metadata": {}, "outputs": [], "source": [] } From 6451abd8ac1dcc10158d0aa045a84359d1a71f6c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 24 Jun 2022 10:24:33 +1000 Subject: [PATCH 090/298] use numpy's gumbel to draw from ev1 --- activitysim/core/interaction_sample.py | 10 ++- activitysim/core/logit.py | 25 +----- activitysim/core/random.py | 79 +++++++++++++++++++ .../frozen_impl_interaction_sample_dev.ipynb | 2 +- 4 files changed, 89 insertions(+), 27 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index d3838dde31..5e40f39dbb 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -13,7 +13,6 @@ from . import chunk from . import config from .simulate import set_skim_wrapper_targets -from .logit import inverse_ev1_cdf from . import interaction_simulate @@ -43,8 +42,13 @@ def make_sample_choices_utility_based( utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each? for i in range(sample_size): - rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) - choices[zero_dim_index, np.argmax(inverse_ev1_cdf(rands) + utils_array, axis=1)] += 1 + #rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) + #choices[zero_dim_index, np.argmax(inverse_ev1_cdf(rands) + utils_array, axis=1)] += 1 + choices[ + zero_dim_index, + np.argmax(pipeline.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + utils_array, axis=1) + ] += 1 + return choices diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index a79be72041..fac9007948 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -183,31 +183,10 @@ def utils_to_probs(utils, trace_label=None, exponentiated=False, allow_zero_prob return probs -# OPTIMISE for runtime in interaction_sample - do not need scale nor location -def inverse_ev1_cdf(x): #, location=0.0, scale=1.0): - # quantile function of EV1 - # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution (and biogeme and larch convention) where the scale - # is proportional to variance (not variance^{-1}). this means nested scales are between 0 and 1. - # x can be number or np array or pd df for vecops - #return location - scale * np.log(-np.log(x)) - return -np.log(-np.log(x)) - - -# def add_ev1_random(df, nest_spec): -# # TODO: generalise to logit for nest_spec==None by adding one rand (scale=1) to all columns -# nest_utils_for_choice = df.copy() -# for n in each_nest(nest_spec): -# if n.level == 1: -# continue # skip the root level, not needed -# uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice) -# rands = inverse_ev1_cdf(uniform_rands) -# nest_utils_for_choice.loc[:, n.name] += rands[:, 0] # inverse_ev1_cdf of single-row df adds dimension -# return nest_utils_for_choice + def add_ev1_random(df): nest_utils_for_choice = df.copy() - uniform_rands = pipeline.get_rn_generator().random_for_df(nest_utils_for_choice, n=df.shape[1]) - rands = inverse_ev1_cdf(uniform_rands) - nest_utils_for_choice += rands + nest_utils_for_choice += pipeline.get_rn_generator().gumbel_for_df(nest_utils_for_choice, n=df.shape[1]) return nest_utils_for_choice diff --git a/activitysim/core/random.py b/activitysim/core/random.py index a9c9770340..548b1d7b84 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -196,6 +196,8 @@ def _generators_for_df(self, df): df_row_states = self.row_states.loc[df.index] + # https://numpy.org/doc/stable/reference/random/generator.html + # np.random.default_rng() prng = np.random.RandomState() for row in df_row_states.itertuples(): @@ -248,6 +250,47 @@ def random_for_df(self, df, step_name, n=1): self.row_states.loc[df.index, 'offset'] += n return rands + def gumbel_for_df(self, df, step_name, n=1): + """ + Return n floating point gumbel-distributed numbers for each row in df + using the appropriate random channel for each row. + + Subsequent calls (in the same step) will return the next rand for each df row + + The resulting array will be the same length (and order) as df + This method is designed to support alternative selection from a probability array + + The columns in df are ignored; the index name and values are used to determine + which random number sequence to to use. + + If "true pseudo random" behavior is desired (i.e. NOT repeatable) the set_base_seed + method (q.v.) may be used to globally reseed all random streams. + + Parameters + ---------- + df : pandas.DataFrame + df with index name and values corresponding to a registered channel + + n : int + number of rands desired per df row + + Returns + ------- + rands : 2-D ndarray + array the same length as df, with n floats in range [0, 1) for each df row + """ + + assert self.step_name + assert self.step_name == step_name + + # - reminder: prng must be called when yielded as generated sequence, not serialized + generators = self._generators_for_df(df) + + rands = np.asanyarray([prng.gumbel(size=n) for prng in generators]) + # update offset for rows we handled + self.row_states.loc[df.index, 'offset'] += n + return rands + def normal_for_df(self, df, step_name, mu, sigma, lognormal=False): """ Return a floating point random number in normal (or lognormal) distribution @@ -602,6 +645,42 @@ def random_for_df(self, df, n=1): rands = channel.random_for_df(df, self.step_name, n) return rands + def gumbel_for_df(self, df, n=1): + """ + Return a single floating point gumbel for each row in df + using the appropriate random channel for each row. + + Subsequent calls (in the same step) will return the next rand for each df row + + The resulting array will be the same length (and order) as df + This method is designed to support alternative selection from a probability array + + The columns in df are ignored; the index name and values are used to determine + which random number sequence to to use. + + We assume that we can identify the channel to used based on the name of df.index + This channel should have already been registered by a call to add_channel (q.v.) + + If "true pseudo random" behavior is desired (i.e. NOT repeatable) the set_base_seed + method (q.v.) may be used to globally reseed all random streams. + + Parameters + ---------- + df : pandas.DataFrame + df with index name and values corresponding to a registered channel + + n : int + number of rands desired (default 1) + + Returns + ------- + choices : 1-D ndarray the same length as df + a single float in range [0, 1) for each row in df + """ + channel = self.get_channel_for_df(df) + rands = channel.gumbel_for_df(df, self.step_name, n) + return rands + def normal_for_df(self, df, mu=0, sigma=1, broadcast=False): """ Return a single floating point normal random number in range (-inf, inf) for each row in df diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index cf210fcccd..7980bfb87e 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -1658,4 +1658,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file From 3973bcde0f590408ab4fdc3587e1b04ebb758ac9 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 24 Jun 2022 10:34:24 +1000 Subject: [PATCH 091/298] clean up --- activitysim/core/logit.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index fac9007948..b4eaa56e43 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -202,6 +202,8 @@ def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alter raise ValueError("This should never happen - no alternative found") +# TODO [janzill Jun2022]: make everything in nested and here numpy from beginning to make choices consistent with +# previous impl (want column index and not alternative name) # Note: this is relatively slow due to the apply. # It could *maybe* be sped up by using the fact that the nesting structure is the same for all rows: Add ev1(0,1) to # all entries (as is currently being done). Then, at each level, pick the maximum of the available composite @@ -226,15 +228,10 @@ def make_choices_ru_frozen_nl(nested_utilities, alt_order_array, nest_spec): # In order for choice indexing to be consistent with MNL and cumsum MC choices, we need to index in the order # alternatives were originally created before adding nest nodes that are not elemental alternatives choices = choices.map({v: k for k, v in enumerate(alt_order_array)}) - ## the following is wrong, the order might be changed - #choices = choices.map({v: k for k, v in enumerate(nest_utils_for_choice.columns)}) return choices -# TODO [janzill Jun2022]: integrate with nested impl above -# TODO [janzill Jun2022]: make everything in nested and here numpy from beginning to make choices consistent with -# previous impl (want column index and not alternative name) def make_choices_ru_frozen_mnl(utilities): utilities_incl_unobs = add_ev1_random(utilities) choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) @@ -242,8 +239,9 @@ def make_choices_ru_frozen_mnl(utilities): choices = pd.Series(choices, index=utilities_incl_unobs.index) return choices + def make_choices_ru_frozen(utilities, alt_order_array, nest_spec=None, trace_label=None): - trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen_mnl') + trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') if nest_spec is None: choices = make_choices_ru_frozen_mnl(utilities) else: From c0d1ca17ab4fa8d5bd643a97687e21a8af8835dd Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 24 Jun 2022 16:04:36 +1000 Subject: [PATCH 092/298] gumbel by hand by inverse cdf --- activitysim/core/random.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 548b1d7b84..54def227bd 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -286,7 +286,10 @@ def gumbel_for_df(self, df, step_name, n=1): # - reminder: prng must be called when yielded as generated sequence, not serialized generators = self._generators_for_df(df) - rands = np.asanyarray([prng.gumbel(size=n) for prng in generators]) + # this is up to 20% faster for large arrays, like for destination choice + #rands = np.asanyarray([prng.gumbel(size=n) for prng in generators]) + rands = np.asanyarray([-np.log(-np.log(prng.rand(n))) for prng in generators]) + # update offset for rows we handled self.row_states.loc[df.index, 'offset'] += n return rands From 6b0b4e93e575bd5d31dd6930b5b526c74fe91be5 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 27 Jun 2022 11:13:44 +1000 Subject: [PATCH 093/298] memory logging --- activitysim/core/interaction_sample.py | 109 ++++++++++-------- activitysim/core/random.py | 2 +- .../frozen_impl_interaction_sample_dev.ipynb | 51 ++++---- 3 files changed, 87 insertions(+), 75 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 5e40f39dbb..441d3236b8 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -28,29 +28,48 @@ def make_sample_choices_utility_based( utilities, sample_size, alternative_count, - allow_zero_probs + allow_zero_probs, + trace_label ): assert isinstance(utilities, pd.DataFrame) assert utilities.shape == (len(choosers), alternative_count) - #choice_dimension = (len(choosers), alternative_count, sample_size) # Note [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension # (len(choosers), alternative_count, sample_size) can get very large - choices = np.zeros_like(utilities, dtype=np.uint32) - zero_dim_index = np.arange(utilities.shape[0]) + #choices = np.zeros_like(utilities, dtype=np.uint32) + #zero_dim_index = np.arange(utilities.shape[0]) + + utils_array = utilities.to_numpy() + chunk.log_df(trace_label, 'utils_array', utils_array) + chosen_destinations = [] + + rands = pipeline.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + chunk.log_df(trace_label, 'rands', rands) - utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each? for i in range(sample_size): #rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) #choices[zero_dim_index, np.argmax(inverse_ev1_cdf(rands) + utils_array, axis=1)] += 1 - choices[ - zero_dim_index, - np.argmax(pipeline.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + utils_array, axis=1) - ] += 1 + #choices[ + # zero_dim_index, + # np.argmax(pipeline.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + utils_array, axis=1) + #] += 1 + # created this once for memory logging + if i > 0: + rands = pipeline.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + chosen_destinations.append(np.argmax(utils_array + rands, axis=1)) + chosen_destinations = np.concatenate(chosen_destinations, axis=0) + + chunk.log_df(trace_label, 'chosen_destinations', chosen_destinations) + + del utils_array + chunk.log_df(trace_label, 'utils_array', None) + del rands + chunk.log_df(trace_label, 'rands', None) + chunk.log_df(trace_label, 'chosen_destinations', None) # handing off to caller - return choices + return chosen_destinations def make_sample_choices( @@ -341,16 +360,16 @@ def _interaction_sample( # sample size 0 is for estimation mode - see below if config.setting("freeze_unobserved_utilities", False) and (sample_size != 0): - choices_array = make_sample_choices_utility_based( - choosers, utilities, sample_size, alternative_count, allow_zero_probs + chosen_alts = make_sample_choices_utility_based( + choosers, utilities, sample_size, alternative_count, allow_zero_probs, trace_label ) - chunk.log_df(trace_label, 'choices_array', choices_array) - + chooser_idx = np.tile(np.arange(utilities.shape[0]), sample_size) + #chunk.log_df(trace_label, 'choices_array', choices_array) # choices array has same dim as utilities, with values indicating number of counts per chooser and alternative # let's turn the nonzero values into a dataframe - i, j = np.nonzero(choices_array) - chunk.log_df(trace_label, 'i', i) - chunk.log_df(trace_label, 'j', j) + #i, j = np.nonzero(choices_array) + chunk.log_df(trace_label, 'chooser_idx', chooser_idx) + chunk.log_df(trace_label, 'chosen_alts', chosen_alts) probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs, trace_label=trace_label, trace_choosers=choosers) @@ -360,21 +379,16 @@ def _interaction_sample( chunk.log_df(trace_label, 'utilities', None) choices_df = pd.DataFrame({ - alt_col_name: alternatives.index.values[j], - "pick_count": choices_array[i, j], - "prob": probs.to_numpy()[i, j], - choosers.index.name: choosers.index.values[i] + alt_col_name: alternatives.index.values[chosen_alts], + #"pick_count": choices_array[i, j], + "prob": probs.to_numpy()[chooser_idx, chosen_alts], + choosers.index.name: choosers.index.values[chooser_idx] }) - chunk.log_df(trace_label, 'choices_df', choices_df) - - del choices_array - chunk.log_df(trace_label, 'choices_array', None) - del i - chunk.log_df(trace_label, 'i', None) - del j - chunk.log_df(trace_label, 'j', None) - del probs - chunk.log_df(trace_label, 'probs', None) + del chooser_idx + chunk.log_df(trace_label, 'chooser_idx', None) + del chosen_alts + chunk.log_df(trace_label, 'chosen_alts', None) + else: # convert to probabilities (utilities exponentiated and normalized to probs) # probs is same shape as utilities, one row per chooser and one column for alternative @@ -412,26 +426,26 @@ def _interaction_sample( allow_zero_probs=allow_zero_probs, trace_label=trace_label) - chunk.log_df(trace_label, 'choices_df', choices_df) + chunk.log_df(trace_label, 'choices_df', choices_df) - del probs - chunk.log_df(trace_label, 'probs', None) + del probs + chunk.log_df(trace_label, 'probs', None) - # pick_count and pick_dup - # pick_count is number of duplicate picks - # pick_dup flag is True for all but first of duplicates - pick_group = choices_df.groupby([choosers.index.name, alt_col_name]) + # pick_count and pick_dup + # pick_count is number of duplicate picks + # pick_dup flag is True for all but first of duplicates + pick_group = choices_df.groupby([choosers.index.name, alt_col_name]) - # number each item in each group from 0 to the length of that group - 1. - choices_df['pick_count'] = pick_group.cumcount(ascending=True) - # flag duplicate rows after first - choices_df['pick_dup'] = choices_df['pick_count'] > 0 - # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge) - choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1 + # number each item in each group from 0 to the length of that group - 1. + choices_df['pick_count'] = pick_group.cumcount(ascending=True) + # flag duplicate rows after first + choices_df['pick_dup'] = choices_df['pick_count'] > 0 + # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge) + choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1 - # drop the duplicates - choices_df = choices_df[~choices_df['pick_dup']] - del choices_df['pick_dup'] + # drop the duplicates + choices_df = choices_df[~choices_df['pick_dup']] + del choices_df['pick_dup'] # set index after groupby so we can trace on it choices_df.set_index(choosers.index.name, inplace=True) @@ -449,7 +463,6 @@ def _interaction_sample( # don't need this after tracing if not config.setting("freeze_unobserved_utilities", False): del choices_df['rand'] - chunk.log_df(trace_label, 'choices_df', choices_df) # - NARROW choices_df['prob'] = choices_df['prob'].astype(np.float32) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 54def227bd..a92dfc44d9 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -286,8 +286,8 @@ def gumbel_for_df(self, df, step_name, n=1): # - reminder: prng must be called when yielded as generated sequence, not serialized generators = self._generators_for_df(df) - # this is up to 20% faster for large arrays, like for destination choice #rands = np.asanyarray([prng.gumbel(size=n) for prng in generators]) + # this is about 20% faster for large arrays, like for destination choice rands = np.asanyarray([-np.log(-np.log(prng.rand(n))) for prng in generators]) # update offset for rows we handled diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb index 7980bfb87e..79412c9265 100644 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ b/notebooks/frozen_impl_interaction_sample_dev.ipynb @@ -76,14 +76,26 @@ "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: '/home/jan/code/activitysim/test_runs/seq_asim'", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mFileNotFoundError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/tmp/ipykernel_24910/2284515201.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mos\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchdir\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mexample_dir\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;31mFileNotFoundError\u001B[0m: [Errno 2] No such file or directory: '/home/jan/code/activitysim/test_runs/seq_asim'" + ] + } + ], "source": [ "os.chdir(example_dir)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", "metadata": { "tags": [] @@ -101,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "9955cc9c", "metadata": { "collapsed": false, @@ -277,7 +289,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 7, "id": "7599d0d1-9063-48aa-95de-cef45e926a42", "metadata": {}, "outputs": [], @@ -419,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "id": "1611fe26", "metadata": { "collapsed": false, @@ -432,27 +444,14 @@ }, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle school_location not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "running university, 3\n", - "dropping 4288 of 4380 rows where size_term is zero\n", - "Running school_location with 5377 persons\n", - "running highschool, 2\n", - "dropping 4000 of 4380 rows where size_term is zero\n", - "Running school_location with 2888 persons\n", - "running gradeschool, 1\n", - "dropping 200 of 4380 rows where size_term is zero\n", - "Running school_location with 13471 persons\n", - "CPU times: user 3min 15s, sys: 1min 17s, total: 4min 32s\n", - "Wall time: 4min 19s\n" + "ename": "NameError", + "evalue": "name 'pipeline' is not defined", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n", + "\u001B[0;31mNameError\u001B[0m: name 'pipeline' is not defined" ] } ], From 792d183eb76010803c477e5c61fead77c71205f2 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 29 Mar 2025 17:24:25 +1000 Subject: [PATCH 094/298] merge bug --- activitysim/core/interaction_sample_simulate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index d7b9a78875..74f32b570c 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -283,7 +283,7 @@ def _interaction_sample_simulate( utilities_df, trace_label=trace_label, trace_choosers=choosers ) del utilities_df - chunk.log_df(trace_label, "utilities_df", None) + chunk_sizer.log_df(trace_label, "utilities_df", None) else: # convert to probabilities (utilities exponentiated and normalized to probs) # probs is same shape as utilities, one row per chooser and one column for alternative @@ -310,7 +310,7 @@ def _interaction_sample_simulate( chunk_sizer.log_df(trace_label, "probs", probs) del utilities_df - chunk.log_df(trace_label, "utilities_df", None) + chunk_sizer.log_df(trace_label, "utilities_df", None) if have_trace_targets: state.tracing.trace_df( From 08d013ab1fcfbdb944062b657e0b68fc38d711aa Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 29 Mar 2025 17:49:10 +1000 Subject: [PATCH 095/298] more merge bugs --- activitysim/core/interaction_sample.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index e1138abb31..8c3645da9f 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -81,8 +81,9 @@ def make_sample_choices_utility_based( #i, j = np.nonzero(choices_array) chunk_sizer.log_df(trace_label, 'chooser_idx', chooser_idx) - probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs, - trace_label=trace_label, trace_choosers=choosers) + probs = logit.utils_to_probs( + state, utilities, allow_zero_probs=allow_zero_probs, trace_label=trace_label, trace_choosers=choosers + ) chunk_sizer.log_df(trace_label, 'probs', probs) choices_df = pd.DataFrame({ From 75bb84d9423134ee5781bd61433db10793e0d035 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 29 Mar 2025 17:56:50 +1000 Subject: [PATCH 096/298] more merge fixes, example runs through --- activitysim/core/interaction_sample_simulate.py | 2 +- activitysim/core/logit.py | 2 +- activitysim/core/simulate.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 74f32b570c..0b112ce1f2 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -280,7 +280,7 @@ def _interaction_sample_simulate( # positions is series with the chosen alternative represented as a column index in utilities_df # which is an integer between zero and num alternatives in the alternative sample positions, rands = logit.make_choices_utility_based( - utilities_df, trace_label=trace_label, trace_choosers=choosers + state, utilities_df, trace_label=trace_label, trace_choosers=choosers ) del utilities_df chunk_sizer.log_df(trace_label, "utilities_df", None) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 5465f8e0c9..2a716a0acb 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -330,7 +330,7 @@ def make_choices_explicit_error_term_mnl(state, utilities): def make_choices_explicit_error_term(state, utilities, alt_order_array, nest_spec=None, trace_label=None): - trace_label = state.tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') + trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') if nest_spec is None: choices = make_choices_explicit_error_term_mnl(state, utilities) else: diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index e2c70b08c3..a395236624 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1304,7 +1304,7 @@ def eval_mnl( if state.settings.use_explicit_error_terms: if custom_chooser: - choices, rands = custom_chooser(state, utilities=utilities, choosers=choosers, spec=spec, trace_label=trace_label) + choices, rands = custom_chooser(state, utilities, choosers, spec, trace_label) else: choices, rands = logit.make_choices_utility_based(state, utilities, trace_label=trace_label) From c6feaefa22ce60dcf4d9557066aa095e61a1a4a5 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 29 Mar 2025 18:17:34 +1000 Subject: [PATCH 097/298] log eet setting on start up --- activitysim/core/configuration/top.py | 1 + 1 file changed, 1 insertion(+) diff --git a/activitysim/core/configuration/top.py b/activitysim/core/configuration/top.py index 2873cef760..486c5b78f4 100644 --- a/activitysim/core/configuration/top.py +++ b/activitysim/core/configuration/top.py @@ -647,6 +647,7 @@ def _check_store_skims_in_shm(self): "memory_profile", "instrument", "sharrow", + "use_explicit_error_terms", ) """ Setting to log on startup. From 114b747b2e9615a53c7cc453557f7ab07088372d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 2 Apr 2025 14:37:12 +1000 Subject: [PATCH 098/298] some variation checks, dev notebooks, to delete --- notebooks/check_sf_probs.ipynb | 396 -- notebooks/example_dev-Copy1.ipynb | 4295 ----------------- notebooks/frozen_impl_dev.ipynb | 661 --- .../frozen_impl_interaction_sample_dev.ipynb | 1660 ------- notebooks/frozen_rand_mode_choice_dev.ipynb | 3418 ------------- notebooks/fru_utils.py | 582 --- .../larch_probabilities_via_estimation.ipynb | 592 --- ...ested_logit_frozen_rand_individ_util.ipynb | 440 -- notebooks/validate_frozen_impl.ipynb | 1651 ------- 9 files changed, 13695 deletions(-) delete mode 100644 notebooks/check_sf_probs.ipynb delete mode 100644 notebooks/example_dev-Copy1.ipynb delete mode 100644 notebooks/frozen_impl_dev.ipynb delete mode 100644 notebooks/frozen_impl_interaction_sample_dev.ipynb delete mode 100644 notebooks/frozen_rand_mode_choice_dev.ipynb delete mode 100644 notebooks/fru_utils.py delete mode 100644 notebooks/larch_probabilities_via_estimation.ipynb delete mode 100644 notebooks/nested_logit_frozen_rand_individ_util.ipynb delete mode 100644 notebooks/validate_frozen_impl.ipynb diff --git a/notebooks/check_sf_probs.ipynb b/notebooks/check_sf_probs.ipynb deleted file mode 100644 index eb5c355cad..0000000000 --- a/notebooks/check_sf_probs.ipynb +++ /dev/null @@ -1,396 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", - "metadata": {}, - "source": [ - "# Compare results against larch\n", - "\n", - "We use estimation data as prepared by larch and draw the Asim way and our way, then compare to larch probabilities.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.385153Z", - "iopub.status.busy": "2022-05-01T10:17:57.384881Z", - "iopub.status.idle": "2022-05-01T10:17:57.534433Z", - "shell.execute_reply": "2022-05-01T10:17:57.533096Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%load_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c3dba451-1e10-403e-8614-35d57e6577f4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.536623Z", - "iopub.status.busy": "2022-05-01T10:17:57.536012Z", - "iopub.status.idle": "2022-05-01T10:17:57.542755Z", - "shell.execute_reply": "2022-05-01T10:17:57.541685Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:00.350944Z", - "iopub.status.busy": "2022-05-01T10:18:00.350730Z", - "iopub.status.idle": "2022-05-01T10:18:12.760977Z", - "shell.execute_reply": "2022-05-01T10:18:12.760013Z", - "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "import os\n", - "import argparse\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from activitysim.cli import run\n", - "from activitysim.core import inject\n", - "\n", - "from fru_utils import mode_choice_for_trip\n", - "\n", - "pd.set_option(\"max_columns\", 500)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:17.839947Z", - "iopub.status.busy": "2022-05-01T10:18:17.839070Z", - "iopub.status.idle": "2022-05-01T10:18:18.019676Z", - "shell.execute_reply": "2022-05-01T10:18:18.018689Z", - "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_example_sf\")\n", - "os.chdir(example_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "outputs": [], - "source": [ - "#!activitysim run -c configs -o output -d data_sf" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:22.111723Z", - "iopub.status.busy": "2022-05-01T10:18:22.111490Z", - "iopub.status.idle": "2022-05-01T10:18:22.297437Z", - "shell.execute_reply": "2022-05-01T10:18:22.296501Z", - "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "parser = argparse.ArgumentParser()\n", - "run.add_run_args(parser)\n", - "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data_sf'])\n", - "if not inject.is_injectable('preload_injectables'):\n", - " from activitysim import abm # register abm steps and other abm-specific injectables\n", - "run.handle_standard_args(args) # possibly update injectables" - ] - }, - { - "cell_type": "code", - "execution_count": 68, - "outputs": [], - "source": [ - "larch_chooser_data = pd.read_csv(os.path.join(root_dir, \"test_estimation_sf\", \"choosers_larch.csv\"))\n", - "larch_probs = pd.read_csv(os.path.join(root_dir, \"test_estimation_sf\", \"probabilities_larch.csv\"), index_col=0)\n", - "# __caseids__ are tour ids, with trips in same order\n", - "assert (larch_probs.index.values == larch_chooser_data.tour_id).all()\n", - "larch_probs.index = larch_chooser_data.trip_id" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 50, - "outputs": [], - "source": [ - "larch_tours = pd.read_csv(os.path.join(root_dir, \"test_estimation_sf\", \"output\", \"estimation_data_bundle\",\n", - " \"trip_mode_choice\", \"trip_mode_choice_values_combined.csv\"))\n", - "trips_merged_cols = ['person_id', 'household_id', 'primary_purpose', 'trip_num', 'outbound',\n", - " 'trip_count', 'destination', 'origin', 'tour_id', 'purpose',\n", - " 'destination_logsum', 'depart', 'hhsize', 'age', 'auto_ownership',\n", - " 'number_of_participants', 'tour_category', 'parent_tour_id',\n", - " 'tour_mode', 'duration', 'value_of_time', 'tour_type',\n", - " 'free_parking_at_work', 'trip_period', \"trip_id\"]\n", - "\n", - "larch_trips_merged = larch_tours[trips_merged_cols].set_index(\"trip_id\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 87, - "outputs": [], - "source": [ - "def compare_mode_share_simulation(trip_id_to_check, num_samples, larch_trips_merged, larch_probs):\n", - " t_, c_, ns_ = mode_choice_for_trip(choose_individual_max_utility=True, trip_id_to_check=trip_id_to_check,\n", - " num_samples=num_samples, trips_merged=larch_trips_merged)\n", - " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", - "\n", - " t_asim, c_asim, _ = mode_choice_for_trip(choose_individual_max_utility=False, trip_id_to_check=trip_id_to_check,\n", - " num_samples=num_samples, trips_merged=larch_trips_merged)\n", - " asim_mode_shares = c_asim.trip_mode.value_counts() / c_asim.shape[0]\n", - "\n", - " larch_mode_shares = larch_probs.loc[larch_probs.index == trip_id_to_check].T\n", - " larch_mode_shares.columns = [\"larch\"]\n", - "\n", - " comp = sim_mode_shares.to_frame(\"me\")\\\n", - " .merge(asim_mode_shares.to_frame(\"asim\"), left_index=True, right_index=True,how=\"outer\")\\\n", - " .merge(larch_mode_shares,left_index=True, right_index=True, how=\"outer\").fillna(0)\n", - "\n", - " return comp" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 124, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n23695361 0.000000 0.0 2.027363e-06 0.0 \n23695365 0.000000 0.0 2.943297e-09 0.0 \n23760961 0.999664 0.0 0.000000e+00 0.0 \n23760962 0.998934 0.0 0.000000e+00 0.0 \n23760963 0.997988 0.0 0.000000e+00 0.0 \n... ... ... ... ... \n2464724829 0.000000 0.0 3.268925e-05 0.0 \n2464847169 0.000000 0.0 2.321209e-03 0.0 \n2464847173 0.000000 0.0 1.269812e-03 0.0 \n2464980993 0.000000 0.0 8.280461e-05 0.0 \n2464980997 0.000000 0.0 6.298188e-05 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n23695361 1.061530e-06 0.0 0.111823 0.0 0.263130 0.625043 \n23695365 1.967559e-08 0.0 0.119603 0.0 0.272258 0.608139 \n23760961 0.000000e+00 0.0 0.000010 0.0 0.000000 0.000000 \n23760962 0.000000e+00 0.0 0.000579 0.0 0.000000 0.000000 \n23760963 0.000000e+00 0.0 0.001509 0.0 0.000000 0.000000 \n... ... ... ... ... ... ... \n2464724829 3.022226e-05 0.0 0.691838 0.0 0.308044 0.000000 \n2464847169 1.213314e-03 0.0 0.249261 0.0 0.745641 0.000000 \n2464847173 7.535248e-04 0.0 0.186394 0.0 0.810411 0.000000 \n2464980993 4.326905e-05 0.0 0.003217 0.0 0.996572 0.000000 \n2464980997 3.791076e-05 0.0 0.003749 0.0 0.996046 0.000000 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n23695361 0.0 0.0 0.0 0.0 0.0 0.0 \n23695365 0.0 0.0 0.0 0.0 0.0 0.0 \n23760961 0.0 0.0 0.0 0.0 0.0 0.0 \n23760962 0.0 0.0 0.0 0.0 0.0 0.0 \n23760963 0.0 0.0 0.0 0.0 0.0 0.0 \n... ... ... ... ... ... ... \n2464724829 0.0 0.0 0.0 0.0 0.0 0.0 \n2464847169 0.0 0.0 0.0 0.0 0.0 0.0 \n2464847173 0.0 0.0 0.0 0.0 0.0 0.0 \n2464980993 0.0 0.0 0.0 0.0 0.0 0.0 \n2464980997 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n23695361 0.0 0.0 2.489336e-08 4.965881e-08 4.072015e-07 \n23695365 0.0 0.0 2.435815e-08 4.996681e-08 4.311721e-07 \n23760961 0.0 0.0 4.724941e-05 1.754653e-04 1.035425e-04 \n23760962 0.0 0.0 1.066593e-04 2.780937e-04 1.021060e-04 \n23760963 0.0 0.0 1.039326e-04 2.850044e-04 1.143830e-04 \n... ... ... ... ... ... \n2464724829 0.0 0.0 3.606576e-05 4.836729e-07 1.874068e-05 \n2464847169 0.0 0.0 4.274883e-04 5.407950e-04 5.947334e-04 \n2464847173 0.0 0.0 3.063342e-04 4.098183e-04 4.563525e-04 \n2464980993 0.0 0.0 1.593300e-05 4.967854e-05 1.954461e-05 \n2464980997 0.0 0.0 2.239090e-05 6.033135e-05 2.206105e-05 \n\n[5428 rows x 21 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
236953610.0000000.02.027363e-060.01.061530e-060.00.1118230.00.2631300.6250430.00.00.00.00.00.00.00.02.489336e-084.965881e-084.072015e-07
236953650.0000000.02.943297e-090.01.967559e-080.00.1196030.00.2722580.6081390.00.00.00.00.00.00.00.02.435815e-084.996681e-084.311721e-07
237609610.9996640.00.000000e+000.00.000000e+000.00.0000100.00.0000000.0000000.00.00.00.00.00.00.00.04.724941e-051.754653e-041.035425e-04
237609620.9989340.00.000000e+000.00.000000e+000.00.0005790.00.0000000.0000000.00.00.00.00.00.00.00.01.066593e-042.780937e-041.021060e-04
237609630.9979880.00.000000e+000.00.000000e+000.00.0015090.00.0000000.0000000.00.00.00.00.00.00.00.01.039326e-042.850044e-041.143830e-04
..................................................................
24647248290.0000000.03.268925e-050.03.022226e-050.00.6918380.00.3080440.0000000.00.00.00.00.00.00.00.03.606576e-054.836729e-071.874068e-05
24648471690.0000000.02.321209e-030.01.213314e-030.00.2492610.00.7456410.0000000.00.00.00.00.00.00.00.04.274883e-045.407950e-045.947334e-04
24648471730.0000000.01.269812e-030.07.535248e-040.00.1863940.00.8104110.0000000.00.00.00.00.00.00.00.03.063342e-044.098183e-044.563525e-04
24649809930.0000000.08.280461e-050.04.326905e-050.00.0032170.00.9965720.0000000.00.00.00.00.00.00.00.01.593300e-054.967854e-051.954461e-05
24649809970.0000000.06.298188e-050.03.791076e-050.00.0037490.00.9960460.0000000.00.00.00.00.00.00.00.02.239090e-056.033135e-052.206105e-05
\n

5428 rows × 21 columns

\n
" - }, - "execution_count": 124, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "larch_probs" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 144, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'work' (100000 trips)\n", - "Done\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'work' (100000 trips)\n", - "Done\n", - "CPU times: user 1min 10s, sys: 3.98 s, total: 1min 14s\n", - "Wall time: 1min 9s\n" - ] - }, - { - "data": { - "text/plain": " me asim larch\nSHARED2FREE 0.395% 0.420% 0.359%\nSHARED3FREE 0.214% 0.226% 0.188%\nTNC_SINGLE 0.212% 0.231% 0.202%\nWALK 0.488% 0.466% 0.435%\nWALK_HVY 69.439% 69.422% 60.165%\nWALK_LOC 9.697% 9.596% 14.513%\nWALK_LRF 19.390% 19.469% 23.977%", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
measimlarch
SHARED2FREE0.395%0.420%0.359%
SHARED3FREE0.214%0.226%0.188%
TNC_SINGLE0.212%0.231%0.202%
WALK0.488%0.466%0.435%
WALK_HVY69.439%69.422%60.165%
WALK_LOC9.697%9.596%14.513%
WALK_LRF19.390%19.469%23.977%
\n
" - }, - "execution_count": 144, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%%time\n", - "\n", - "trip_id_to_check = 24106017 # obvious results with 1e4 samples: 23695361 24106017\n", - "# better with 1e5 samples: 1932588125\n", - "# requires higher precision, check when time: 38280865\n", - "num_samples = 100000 # 1e5 takes about 80s\n", - "\n", - "comp = compare_mode_share_simulation(trip_id_to_check, num_samples, larch_trips_merged, larch_probs)\n", - "comp.loc[(comp > 1e-3).any(axis=1), :].applymap(lambda x: f\"{x:.3%}\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 139, - "outputs": [], - "source": [ - "#comp.loc[(comp > 0).any(axis=1), :]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 140, - "outputs": [ - { - "data": { - "text/plain": "0.252" - }, - "execution_count": 140, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "0.72 * 0.35" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "# check what happens if you multiply them - like Train at the lower level? but then 0\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
person_idhousehold_idtour_idprimary_purposetrip_numoutboundtrip_countpurposedestinationorigindestination_logsumdeparttrip_modemode_choice_logsum
trip_id
13724872141844130403617156090escort1True1escort710NaN7.0WALK11.435800
13724872541844130403617156090escort1False1Home107NaN7.0WALK11.480440
21138820164447638676126423525escort1True1escort1116NaN5.0WALK_LOC4.789158
21138820564447638676126423525escort1False1Home1611NaN6.0WALK_LOC5.050171
80638840124585011173905100798550escort1True1escort168NaN15.0WALK_LOC6.451457
80638840524585011173905100798550escort1False1Home816NaN16.0WALK_LOC6.446188
\n", - "" - ], - "text/plain": [ - " person_id household_id tour_id primary_purpose trip_num \\\n", - "trip_id \n", - "137248721 418441 304036 17156090 escort 1 \n", - "137248725 418441 304036 17156090 escort 1 \n", - "211388201 644476 386761 26423525 escort 1 \n", - "211388205 644476 386761 26423525 escort 1 \n", - "806388401 2458501 1173905 100798550 escort 1 \n", - "806388405 2458501 1173905 100798550 escort 1 \n", - "\n", - " outbound trip_count purpose destination origin \\\n", - "trip_id \n", - "137248721 True 1 escort 7 10 \n", - "137248725 False 1 Home 10 7 \n", - "211388201 True 1 escort 11 16 \n", - "211388205 False 1 Home 16 11 \n", - "806388401 True 1 escort 16 8 \n", - "806388405 False 1 Home 8 16 \n", - "\n", - " destination_logsum depart trip_mode mode_choice_logsum \n", - "trip_id \n", - "137248721 NaN 7.0 WALK 11.435800 \n", - "137248725 NaN 7.0 WALK 11.480440 \n", - "211388201 NaN 5.0 WALK_LOC 4.789158 \n", - "211388205 NaN 6.0 WALK_LOC 5.050171 \n", - "806388401 NaN 15.0 WALK_LOC 6.451457 \n", - "806388405 NaN 16.0 WALK_LOC 6.446188 " - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trips_df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ca3206ce-df97-42d9-83ce-dc4067ea5675", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d6c2752e-68a8-4319-85c2-fe6bf1fd6d59", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3851df2c-bb8c-43b6-8a27-3b04a7f85a50", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b596af94-85d1-4d25-99b2-c19292397882", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "8eebea31-499c-41ca-8411-883a88ca800a", - "metadata": {}, - "source": [ - "## nested dev" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "3e2513e9-c03f-4891-9a1c-4053a4440a10", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:31:01.113060Z", - "iopub.status.busy": "2021-08-31T10:31:01.112829Z", - "iopub.status.idle": "2021-08-31T10:31:01.359912Z", - "shell.execute_reply": "2021-08-31T10:31:01.358948Z", - "shell.execute_reply.started": "2021-08-31T10:31:01.113034Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "spec = simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - "nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "36c100b3-3e39-4950-a586-4d42be695eaa", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:31:01.775068Z", - "iopub.status.busy": "2021-08-31T10:31:01.774846Z", - "iopub.status.idle": "2021-08-31T10:31:01.975816Z", - "shell.execute_reply": "2021-08-31T10:31:01.974499Z", - "shell.execute_reply.started": "2021-08-31T10:31:01.775044Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'name': 'root',\n", - " 'coefficient': 1.0,\n", - " 'alternatives': [{'name': 'AUTO',\n", - " 'coefficient': 0.72,\n", - " 'alternatives': [{'name': 'DRIVEALONE',\n", - " 'coefficient': 0.35,\n", - " 'alternatives': ['DRIVEALONEFREE', 'DRIVEALONEPAY']},\n", - " {'name': 'SHAREDRIDE2',\n", - " 'coefficient': 0.35,\n", - " 'alternatives': ['SHARED2FREE', 'SHARED2PAY']},\n", - " {'name': 'SHAREDRIDE3',\n", - " 'coefficient': 0.35,\n", - " 'alternatives': ['SHARED3FREE', 'SHARED3PAY']}]},\n", - " {'name': 'NONMOTORIZED',\n", - " 'coefficient': 0.72,\n", - " 'alternatives': ['WALK', 'BIKE']},\n", - " {'name': 'TRANSIT',\n", - " 'coefficient': 0.72,\n", - " 'alternatives': [{'name': 'WALKACCESS',\n", - " 'coefficient': 0.5,\n", - " 'alternatives': ['WALK_LOC',\n", - " 'WALK_LRF',\n", - " 'WALK_EXP',\n", - " 'WALK_HVY',\n", - " 'WALK_COM']},\n", - " {'name': 'DRIVEACCESS',\n", - " 'coefficient': 0.5,\n", - " 'alternatives': ['DRIVE_LOC',\n", - " 'DRIVE_LRF',\n", - " 'DRIVE_EXP',\n", - " 'DRIVE_HVY',\n", - " 'DRIVE_COM']}]},\n", - " {'name': 'RIDEHAIL',\n", - " 'coefficient': 0.36,\n", - " 'alternatives': ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']}]}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nest_spec" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "f442cf50-e85c-4991-af39-68a6839b146e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:31:03.459794Z", - "iopub.status.busy": "2021-08-31T10:31:03.459560Z", - "iopub.status.idle": "2021-08-31T10:31:03.660759Z", - "shell.execute_reply": "2021-08-31T10:31:03.658857Z", - "shell.execute_reply.started": "2021-08-31T10:31:03.459767Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nest name: root level: 1 coefficient: 1.0 product_of_coefficients: 1.0 ancestors: ['root'] alternatives: ['AUTO', 'NONMOTORIZED', 'TRANSIT', 'RIDEHAIL']\n", - "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] alternatives: ['DRIVEALONE', 'SHAREDRIDE2', 'SHAREDRIDE3']\n", - "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] alternatives: ['DRIVEALONEFREE', 'DRIVEALONEPAY']\n", - "Nest name: DRIVEALONEFREE level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE'] alternatives: None\n", - "Nest name: DRIVEALONEPAY level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY'] alternatives: None\n", - "Nest name: SHAREDRIDE2 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2'] alternatives: ['SHARED2FREE', 'SHARED2PAY']\n", - "Nest name: SHARED2FREE level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2FREE'] alternatives: None\n", - "Nest name: SHARED2PAY level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE2', 'SHARED2PAY'] alternatives: None\n", - "Nest name: SHAREDRIDE3 level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3'] alternatives: ['SHARED3FREE', 'SHARED3PAY']\n", - "Nest name: SHARED3FREE level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3FREE'] alternatives: None\n", - "Nest name: SHARED3PAY level: 4 coefficient: 0 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'SHAREDRIDE3', 'SHARED3PAY'] alternatives: None\n", - "Nest name: NONMOTORIZED level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED'] alternatives: ['WALK', 'BIKE']\n", - "Nest name: WALK level: 3 coefficient: 0 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'WALK'] alternatives: None\n", - "Nest name: BIKE level: 3 coefficient: 0 product_of_coefficients: 0.72 ancestors: ['root', 'NONMOTORIZED', 'BIKE'] alternatives: None\n", - "Nest name: TRANSIT level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'TRANSIT'] alternatives: ['WALKACCESS', 'DRIVEACCESS']\n", - "Nest name: WALKACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS'] alternatives: ['WALK_LOC', 'WALK_LRF', 'WALK_EXP', 'WALK_HVY', 'WALK_COM']\n", - "Nest name: WALK_LOC level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LOC'] alternatives: None\n", - "Nest name: WALK_LRF level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_LRF'] alternatives: None\n", - "Nest name: WALK_EXP level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_EXP'] alternatives: None\n", - "Nest name: WALK_HVY level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_HVY'] alternatives: None\n", - "Nest name: WALK_COM level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'WALKACCESS', 'WALK_COM'] alternatives: None\n", - "Nest name: DRIVEACCESS level: 3 coefficient: 0.5 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS'] alternatives: ['DRIVE_LOC', 'DRIVE_LRF', 'DRIVE_EXP', 'DRIVE_HVY', 'DRIVE_COM']\n", - "Nest name: DRIVE_LOC level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LOC'] alternatives: None\n", - "Nest name: DRIVE_LRF level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_LRF'] alternatives: None\n", - "Nest name: DRIVE_EXP level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_EXP'] alternatives: None\n", - "Nest name: DRIVE_HVY level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_HVY'] alternatives: None\n", - "Nest name: DRIVE_COM level: 4 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'TRANSIT', 'DRIVEACCESS', 'DRIVE_COM'] alternatives: None\n", - "Nest name: RIDEHAIL level: 2 coefficient: 0.36 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL'] alternatives: ['TAXI', 'TNC_SINGLE', 'TNC_SHARED']\n", - "Nest name: TAXI level: 3 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TAXI'] alternatives: None\n", - "Nest name: TNC_SINGLE level: 3 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SINGLE'] alternatives: None\n", - "Nest name: TNC_SHARED level: 3 coefficient: 0 product_of_coefficients: 0.36 ancestors: ['root', 'RIDEHAIL', 'TNC_SHARED'] alternatives: None\n" - ] - } - ], - "source": [ - "for nest in logit.each_nest(nest_spec):\n", - " nest.print()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d509b34d-a755-48ca-8079-386c14023d69", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 349, - "id": "e8d11f6a-f82a-40bd-8eef-fc28bcca8252", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:59:35.109079Z", - "iopub.status.busy": "2021-09-01T01:59:35.108781Z", - "iopub.status.idle": "2021-09-01T01:59:35.319301Z", - "shell.execute_reply": "2021-09-01T01:59:35.318402Z", - "shell.execute_reply.started": "2021-09-01T01:59:35.109050Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def simple_simulate_dev(choosers, spec, nest_spec,\n", - " skims=None, locals_d=None,\n", - " chunk_size=0, custom_chooser=None,\n", - " log_alt_losers=False,\n", - " want_logsums=False,\n", - " estimator=None,\n", - " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", - " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", - " assert len(choosers) > 0\n", - " result_list = []\n", - " # segment by person type and pick the right spec for each person type\n", - " for i, chooser_chunk, chunk_trace_label \\\n", - " in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", - " # the following replaces choices = _simple_simulate(...)\n", - " if skims is not None:\n", - " simulate.set_skim_wrapper_targets(choosers, skims)\n", - "\n", - " # only do this for nested, logit is straight forward\n", - " assert nest_spec is not None\n", - " choices = simulate.eval_nl(choosers, spec, nest_spec, locals_d, custom_chooser,\n", - " log_alt_losers=log_alt_losers,\n", - " want_logsums=want_logsums,\n", - " estimator=estimator,\n", - " trace_label=trace_label,\n", - " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", - "\n", - "\n", - " result_list.append(choices)\n", - " chunk.log_df(trace_label, f'result_list', result_list)\n", - "\n", - " if len(result_list) > 1:\n", - " choices = pd.concat(result_list)\n", - " assert len(choices.index == len(choosers.index))\n", - " return choices\n" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "05e06aa4-e943-4c34-ad1b-09c19c800bdc", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:35:06.100477Z", - "iopub.status.busy": "2021-08-31T10:35:06.100054Z", - "iopub.status.idle": "2021-08-31T10:35:06.974578Z", - "shell.execute_reply": "2021-08-31T10:35:06.973620Z", - "shell.execute_reply.started": "2021-08-31T10:35:06.100438Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "primary_purpose = 'escort'\n", - "trips_segment = trips_merged.loc[trips_merged.primary_purpose == primary_purpose].copy()\n", - "assert trips_segment.index.name == 'trip_id' # name index so tracing knows how to slice\n", - "coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "locals_dict = {}\n", - "locals_dict.update(constants)\n", - "locals_dict.update(coefficients)\n", - "segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - "locals_dict.update(skims)\n", - "\n", - "spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - "nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - "choices = simple_simulate_dev(\n", - " choosers=trips_segment,\n", - " spec=spec,\n", - " nest_spec=nest_spec,\n", - " skims=skims,\n", - " locals_d=locals_dict,\n", - " chunk_size=chunk_size,\n", - " want_logsums=logsum_column_name is not None,\n", - " trace_label=segment_trace_label,\n", - " trace_choice_name='trip_mode_choice',\n", - " estimator=estimator,\n", - " trace_column_names=None)\n", - "# for consistency, always return dataframe, whether or not logsums were requested\n", - "if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - "\n", - "choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - "\n", - "alts = spec.columns\n", - "choices[mode_column_name] = \\\n", - " choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ec4eb1ff-2f35-4919-96b4-5acffa01f597", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "07ef8fbc-beb2-4e57-9d56-939719026d58", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "c60656db-075a-4eab-96b3-fc54eb785075", - "metadata": {}, - "source": [ - "## eval_nl dev" - ] - }, - { - "cell_type": "code", - "execution_count": 372, - "id": "56c8923c-e946-48d8-b964-87695c30163d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:27:40.290389Z", - "iopub.status.busy": "2021-09-01T02:27:40.290139Z", - "iopub.status.idle": "2021-09-01T02:27:40.977526Z", - "shell.execute_reply": "2021-09-01T02:27:40.976594Z", - "shell.execute_reply.started": "2021-09-01T02:27:40.290363Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# replaces simple_simulate_dev:\n", - "\n", - "want_logsums = True\n", - "trace_choice_name='trip_mode_choice'\n", - "trace_column_names=None\n", - "log_alt_losers=False\n", - "\n", - "tr_label = tracing.extend_trace_label(segment_trace_label, 'simple_simulate')\n", - "assert len(trips_segment) > 0\n", - "result_list = []\n", - "# segment by person type and pick the right spec for each person type\n", - "for i, chooser_chunk, chunk_trace_label \\\n", - " in chunk.adaptive_chunked_choosers(trips_segment, chunk_size, tr_label):\n", - " # the following replaces choices = _simple_simulate(...)\n", - " if skims is not None:\n", - " simulate.set_skim_wrapper_targets(trips_segment, skims)\n", - "\n", - " # only do this for nested, logit is straight forward\n", - " assert nest_spec is not None\n", - " choices = simulate.eval_nl(trips_segment, spec, nest_spec, locals_dict, custom_chooser=None,\n", - " log_alt_losers=log_alt_losers,\n", - " want_logsums=want_logsums,\n", - " estimator=estimator,\n", - " trace_label=tr_label,\n", - " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", - "\n", - "\n", - " result_list.append(choices)\n", - "\n", - "if len(result_list) > 1:\n", - " choices = pd.concat(result_list)\n", - "assert len(choices.index == len(trips_segment.index))" - ] - }, - { - "cell_type": "code", - "execution_count": 93, - "id": "cbc2dfde-1bd9-4b03-96d9-ca9b7222d36c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T12:52:34.544865Z", - "iopub.status.busy": "2021-08-31T12:52:34.544586Z", - "iopub.status.idle": "2021-08-31T12:52:35.159615Z", - "shell.execute_reply": "2021-08-31T12:52:35.158682Z", - "shell.execute_reply.started": "2021-08-31T12:52:34.544840Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "locals_d = locals_dict\n", - "custom_chooser=None\n", - "\n", - "trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - "logit.validate_nest_spec(nest_spec, trace_label)\n", - "\n", - "raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - "nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - "\n", - "nested_probabilities = \\\n", - " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", - "\n", - "logsums = pd.Series(np.log(nested_exp_utilities.root), index=trips_segment.index)\n", - "\n", - "# global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - "base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", - "\n", - "choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)" - ] - }, - { - "cell_type": "code", - "execution_count": 95, - "id": "0cd9593b-b3fd-41f6-a7ca-4c5b73b63571", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T12:52:45.509057Z", - "iopub.status.busy": "2021-08-31T12:52:45.508819Z", - "iopub.status.idle": "2021-08-31T12:52:45.721956Z", - "shell.execute_reply": "2021-08-31T12:52:45.721064Z", - "shell.execute_reply.started": "2021-08-31T12:52:45.509021Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 0.990308\n", - "137248725 0.108433\n", - "211388201 0.806612\n", - "211388205 0.195994\n", - "806388401 0.854020\n", - "806388405 0.660550\n", - "dtype: float64" - ] - }, - "execution_count": 95, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rands" - ] - }, - { - "cell_type": "code", - "execution_count": 92, - "id": "b467330b-f1cc-43bc-9325-c6dfeaa9b364", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T12:50:51.735273Z", - "iopub.status.busy": "2021-08-31T12:50:51.734962Z", - "iopub.status.idle": "2021-08-31T12:50:52.023440Z", - "shell.execute_reply": "2021-08-31T12:50:52.022582Z", - "shell.execute_reply.started": "2021-08-31T12:50:51.735231Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.798480-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.360270-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.435800
137248725-1.732971-3966.018685-0.606540-0.324578-3964.610293-0.113602-3966.009714-7930.295428-inf0.26146615.945036-1368.16910311.480426-2737.292402-5512.292402-5512.292402-5512.292402-5512.292402-inf-2737.782457-5522.136798-5522.136798-5516.076156-5525.080218-inf-inf-24.341318-25.104291-24.140831-8.40687011.480440
211388201-3966.000225-7930.285939-inf-24.120135-3988.405850-8.442047-29.480718-3993.766432-10.318251-5.975663-4.216264-1381.397292-3.03571013.295720-2761.150907-2761.150907-2761.150907-2761.1509076.647860-2761.648763-5543.048131-5543.048131-2769.692185-2773.867648-inf4.786459-15.861515-3.864497-5.068360-1.2967574.789158
211388205-3967.791945-7932.077659-inf-25.129596-3989.415310-8.795359-30.177275-3994.462989-10.562046-6.219063-4.216264-1381.397292-3.03571014.022438-2760.977562-2760.977562-2760.977562-2760.9775627.011219-2761.294814-5546.132585-5546.132585-2764.829835-2774.132982-inf5.048078-15.776424-3.845077-5.005482-1.2860945.050171
806388401-3966.492870-7930.778584-inf-12.618378-3976.904092-4.416432-14.908422-3979.194136-5.217948-2.9129776.192499-1378.4807234.45859917.513379-2755.845870-2755.208370-2755.728926-2755.7930938.756690-2757.559519-5540.685990-5540.685990-5542.838278-5546.780761-inf6.304817-19.143696-19.495908-16.361074-5.8539176.451457
806388405-3969.835821-7934.121535-inf-14.515916-3978.801631-5.080571-16.227796-3980.513510-5.679728-3.3428055.998749-1378.5194734.31909917.553369-2755.987742-2755.350242-2755.870797-2755.9349648.776685-2757.571454-5542.673358-5542.673358-5537.799228-5547.515590-inf6.319213-18.886576-19.495133-16.300395-5.8285406.446188
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", - "trip_id \n", - "137248721 -1.798480 -3966.084195 -0.629468 -0.389611 \n", - "137248725 -1.732971 -3966.018685 -0.606540 -0.324578 \n", - "211388201 -3966.000225 -7930.285939 -inf -24.120135 \n", - "211388205 -3967.791945 -7932.077659 -inf -25.129596 \n", - "806388401 -3966.492870 -7930.778584 -inf -12.618378 \n", - "806388405 -3969.835821 -7934.121535 -inf -14.515916 \n", - "\n", - " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", - "trip_id \n", - "137248721 -3964.675326 -0.136364 -3966.074556 -7930.360270 -inf \n", - "137248725 -3964.610293 -0.113602 -3966.009714 -7930.295428 -inf \n", - "211388201 -3988.405850 -8.442047 -29.480718 -3993.766432 -10.318251 \n", - "211388205 -3989.415310 -8.795359 -30.177275 -3994.462989 -10.562046 \n", - "806388401 -3976.904092 -4.416432 -14.908422 -3979.194136 -5.217948 \n", - "806388405 -3978.801631 -5.080571 -16.227796 -3980.513510 -5.679728 \n", - "\n", - " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", - "trip_id \n", - "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", - "137248725 0.261466 15.945036 -1368.169103 11.480426 -2737.292402 \n", - "211388201 -5.975663 -4.216264 -1381.397292 -3.035710 13.295720 \n", - "211388205 -6.219063 -4.216264 -1381.397292 -3.035710 14.022438 \n", - "806388401 -2.912977 6.192499 -1378.480723 4.458599 17.513379 \n", - "806388405 -3.342805 5.998749 -1378.519473 4.319099 17.553369 \n", - "\n", - " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", - "trip_id \n", - "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", - "137248725 -5512.292402 -5512.292402 -5512.292402 -5512.292402 -inf \n", - "211388201 -2761.150907 -2761.150907 -2761.150907 -2761.150907 6.647860 \n", - "211388205 -2760.977562 -2760.977562 -2760.977562 -2760.977562 7.011219 \n", - "806388401 -2755.845870 -2755.208370 -2755.728926 -2755.793093 8.756690 \n", - "806388405 -2755.987742 -2755.350242 -2755.870797 -2755.934964 8.776685 \n", - "\n", - " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", - "trip_id \n", - "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", - "137248725 -2737.782457 -5522.136798 -5522.136798 -5516.076156 -5525.080218 \n", - "211388201 -2761.648763 -5543.048131 -5543.048131 -2769.692185 -2773.867648 \n", - "211388205 -2761.294814 -5546.132585 -5546.132585 -2764.829835 -2774.132982 \n", - "806388401 -2757.559519 -5540.685990 -5540.685990 -5542.838278 -5546.780761 \n", - "806388405 -2757.571454 -5542.673358 -5542.673358 -5537.799228 -5547.515590 \n", - "\n", - " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", - "trip_id \n", - "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", - "137248725 -inf -inf -24.341318 -25.104291 -24.140831 -8.406870 \n", - "211388201 -inf 4.786459 -15.861515 -3.864497 -5.068360 -1.296757 \n", - "211388205 -inf 5.048078 -15.776424 -3.845077 -5.005482 -1.286094 \n", - "806388401 -inf 6.304817 -19.143696 -19.495908 -16.361074 -5.853917 \n", - "806388405 -inf 6.319213 -18.886576 -19.495133 -16.300395 -5.828540 \n", - "\n", - " root \n", - "trip_id \n", - "137248721 11.435800 \n", - "137248725 11.480440 \n", - "211388201 4.789158 \n", - "211388205 5.050171 \n", - "806388401 6.451457 \n", - "806388405 6.446188 " - ] - }, - "execution_count": 92, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# root is logsum here, no need to take log again as for exponentiated utils\n", - "simulate.compute_nested_exp_utilities(raw_utilities, nest_spec, exponentiate=False)" - ] - }, - { - "cell_type": "markdown", - "id": "5c9ba018-167d-497f-ac65-f7c49036ee58", - "metadata": {}, - "source": [ - "#### scale of utils, including emus" - ] - }, - { - "cell_type": "code", - "execution_count": 283, - "id": "9febf6ef-7c6b-4f3c-bdd9-aa3326a82e95", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T23:23:00.020038Z", - "iopub.status.busy": "2021-08-31T23:23:00.019377Z", - "iopub.status.idle": "2021-08-31T23:23:00.220889Z", - "shell.execute_reply": "2021-08-31T23:23:00.220154Z", - "shell.execute_reply.started": "2021-08-31T23:23:00.019997Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "nests_ = list(logit.each_nest(nest_spec))" - ] - }, - { - "cell_type": "code", - "execution_count": 284, - "id": "48ff920e-0c9b-4b69-9cee-eb3c3427bd4f", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T23:23:01.085713Z", - "iopub.status.busy": "2021-08-31T23:23:01.085486Z", - "iopub.status.idle": "2021-08-31T23:23:01.283509Z", - "shell.execute_reply": "2021-08-31T23:23:01.282444Z", - "shell.execute_reply.started": "2021-08-31T23:23:01.085685Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Nest name: AUTO level: 2 coefficient: 0.72 product_of_coefficients: 0.72 ancestors: ['root', 'AUTO'] alternatives: ['DRIVEALONE', 'SHAREDRIDE2', 'SHAREDRIDE3']\n", - "Nest name: DRIVEALONE level: 3 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE'] alternatives: ['DRIVEALONEFREE', 'DRIVEALONEPAY']\n", - "Nest name: DRIVEALONEFREE level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEFREE'] alternatives: None\n", - "Nest name: DRIVEALONEPAY level: 4 coefficient: 0.35 product_of_coefficients: 0.252 ancestors: ['root', 'AUTO', 'DRIVEALONE', 'DRIVEALONEPAY'] alternatives: None\n" - ] - } - ], - "source": [ - "#list(map(lambda x: x.print(), nests_));\n", - "# have a look at \n", - "nests_[1].print()\n", - "nests_[2].print()\n", - "nests_[3].print()\n", - "nests_[4].print()" - ] - }, - { - "cell_type": "code", - "execution_count": 312, - "id": "7b7e453d-da45-4751-8889-561a349bd373", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:22.371464Z", - "iopub.status.busy": "2021-09-01T01:11:22.371192Z", - "iopub.status.idle": "2021-09-01T01:11:22.593424Z", - "shell.execute_reply": "2021-09-01T01:11:22.592472Z", - "shell.execute_reply.started": "2021-09-01T01:11:22.371415Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation belowi s equivalent.\n", - "def compute_nested_utilities(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) / nest.product_of_coefficients #coefficient\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", - " return nested_utilities\n", - "\n", - "\n", - "utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", - "\n", - "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", - "seed = 9326543345\n", - "rng = default_rng(seed=seed)\n", - "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", - "rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", - "\n", - "probs_arr = utils_df - np.log(-np.log(rands))\n", - "\n", - "# Now replace the following with recursive nested choice\n", - "# choices = np.argmax(probs_arr, axis=1)\n", - "# choices = pd.Series(choices, index=utils_df.index)\n", - "# rands = pd.Series(np.asanyarray(rands).flatten(), index=utils_df.index)" - ] - }, - { - "cell_type": "code", - "execution_count": 313, - "id": "39b71df9-12ac-4d9c-8134-43d22f7e6a6f", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:23.703299Z", - "iopub.status.busy": "2021-09-01T01:11:23.703088Z", - "iopub.status.idle": "2021-09-01T01:11:23.937077Z", - "shell.execute_reply": "2021-09-01T01:11:23.935627Z", - "shell.execute_reply.started": "2021-09-01T01:11:23.703274Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.79848-3966.084195-0.629468-0.389611-3964.675326-0.136364-3966.074556-7930.36027-inf0.24503215.883036-1368.20010211.435786-2737.198627-5512.198627-5512.198627-5512.198627-5512.198627-inf-2737.365441-5518.612509-5518.612509-5520.374306-5521.789773-inf-inf-24.503675-25.143041-24.249471-8.44847311.4358
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", - "trip_id \n", - "137248721 -1.79848 -3966.084195 -0.629468 -0.389611 \n", - "\n", - " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", - "trip_id \n", - "137248721 -3964.675326 -0.136364 -3966.074556 -7930.36027 -inf \n", - "\n", - " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", - "trip_id \n", - "137248721 0.245032 15.883036 -1368.200102 11.435786 -2737.198627 \n", - "\n", - " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", - "trip_id \n", - "137248721 -5512.198627 -5512.198627 -5512.198627 -5512.198627 -inf \n", - "\n", - " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", - "trip_id \n", - "137248721 -2737.365441 -5518.612509 -5518.612509 -5520.374306 -5521.789773 \n", - "\n", - " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", - "trip_id \n", - "137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n", - "\n", - " root \n", - "trip_id \n", - "137248721 11.4358 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-0.717963-3964.2474012.7217010.230429-3964.716506-0.274584-3965.126773-7930.610173-inf1.73337615.473633-1368.52552614.717027-2736.517471-5510.738449-5512.038516-5512.559241-5511.584293-inf-2738.143234-5518.606969-5519.009587-5519.830735-5521.525966-inf-inf-25.519792-21.698963-25.267185-7.44465211.689443
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\n", - "trip_id \n", - "137248721 -0.717963 -3964.247401 2.721701 0.230429 \n", - "\n", - " SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\n", - "trip_id \n", - "137248721 -3964.716506 -0.274584 -3965.126773 -7930.610173 -inf \n", - "\n", - " AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\n", - "trip_id \n", - "137248721 1.733376 15.473633 -1368.525526 14.717027 -2736.517471 \n", - "\n", - " WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\n", - "trip_id \n", - "137248721 -5510.738449 -5512.038516 -5512.559241 -5511.584293 -inf \n", - "\n", - " DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\n", - "trip_id \n", - "137248721 -2738.143234 -5518.606969 -5519.009587 -5519.830735 -5521.525966 \n", - "\n", - " DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\n", - "trip_id \n", - "137248721 -inf -inf -25.519792 -21.698963 -25.267185 -7.444652 \n", - "\n", - " root \n", - "trip_id \n", - "137248721 11.689443 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "display(utils_df.head(1), probs_arr.head(1))" - ] - }, - { - "cell_type": "markdown", - "id": "2f080150-c15a-4059-8a26-42a8c0072606", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T23:16:40.014024Z", - "iopub.status.busy": "2021-08-31T23:16:40.013770Z", - "iopub.status.idle": "2021-08-31T23:16:40.236326Z", - "shell.execute_reply": "2021-08-31T23:16:40.235553Z", - "shell.execute_reply.started": "2021-08-31T23:16:40.013973Z" - }, - "tags": [] - }, - "source": [ - "### make choice at each level" - ] - }, - { - "cell_type": "code", - "execution_count": 315, - "id": "2ed71b54-67e4-4087-957a-75f4fa184144", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:43.778081Z", - "iopub.status.busy": "2021-09-01T01:11:43.777757Z", - "iopub.status.idle": "2021-09-01T01:11:43.975332Z", - "shell.execute_reply": "2021-09-01T01:11:43.974588Z", - "shell.execute_reply.started": "2021-09-01T01:11:43.778055Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def get_alternatives(nests, name):\n", - " alts = list(filter(lambda x: x.name == name, nests))\n", - " assert len(alts) == 1, f\"{len(alts)} not one\"\n", - " alts = alts[0].alternatives\n", - " return alts\n", - "\n", - "def recursive_choice(row, columns, nest_levels, nests):\n", - " choices = row[columns].idxmax() #axis=1).values[0]\n", - " next_level_columns = get_alternatives(nests, choices)\n", - " #print(f\"{choices} leads to columns {next_level_columns}\")\n", - " if next_level_columns is None:\n", - " return choices \n", - " new_choice = recursive_choice(row, next_level_columns, nest_levels, nests)\n", - " return new_choice" - ] - }, - { - "cell_type": "code", - "execution_count": 316, - "id": "30424562-2627-446b-abd5-7c763c52060e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:49.444289Z", - "iopub.status.busy": "2021-09-01T01:11:49.444002Z", - "iopub.status.idle": "2021-09-01T01:11:49.664214Z", - "shell.execute_reply": "2021-09-01T01:11:49.663088Z", - "shell.execute_reply.started": "2021-09-01T01:11:49.444266Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "4\n" - ] - } - ], - "source": [ - "# group nests by level:\n", - "depth = np.max([x.level for x in nests_])\n", - "print(depth)\n", - "nest_levels = {x: [] for x in range(1, depth+1)}\n", - "for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - "assert len(nest_levels[1]) == 1\n", - "assert nest_levels[1][0] == 'root'" - ] - }, - { - "cell_type": "code", - "execution_count": 326, - "id": "bae6e7a8-d7c3-42a2-a82f-874b1e59a0b6", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:56:38.377472Z", - "iopub.status.busy": "2021-09-01T01:56:38.377253Z", - "iopub.status.idle": "2021-09-01T01:56:38.607858Z", - "shell.execute_reply": "2021-09-01T01:56:38.606913Z", - "shell.execute_reply.started": "2021-09-01T01:56:38.377447Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 WALK\n", - "137248725 WALK\n", - "211388201 WALK_LOC\n", - "211388205 WALK_LOC\n", - "806388401 WALK\n", - "806388405 WALK\n", - "dtype: object" - ] - }, - "execution_count": 326, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests_), axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 321, - "id": "fc315f63-542f-46fd-8d70-b7a1fcd492c6", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:53:15.500933Z", - "iopub.status.busy": "2021-09-01T01:53:15.500709Z", - "iopub.status.idle": "2021-09-01T01:53:15.703201Z", - "shell.execute_reply": "2021-09-01T01:53:15.702357Z", - "shell.execute_reply.started": "2021-09-01T01:53:15.500909Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#display(utils_df, probs_arr)" - ] - }, - { - "cell_type": "markdown", - "id": "8a45e1c0-0a32-4f7a-8803-7559d71abefd", - "metadata": {}, - "source": [ - "### some tests\n", - "seeds, average?" - ] - }, - { - "cell_type": "code", - "execution_count": 333, - "id": "f0e00814-2a14-4da1-bc6c-89b52b7fb286", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:58:26.002425Z", - "iopub.status.busy": "2021-09-01T01:58:26.002200Z", - "iopub.status.idle": "2021-09-01T01:58:26.247954Z", - "shell.execute_reply": "2021-09-01T01:58:26.247054Z", - "shell.execute_reply.started": "2021-09-01T01:58:26.002401Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 WALK\n", - "137248725 WALK\n", - "211388201 WALK_LOC\n", - "211388205 WALK_LOC\n", - "806388401 WALK\n", - "806388405 WALK\n", - "dtype: object" - ] - }, - "execution_count": 333, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", - "seed = 9326543345\n", - "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", - "\n", - "def make_choice(utils_df, nests, nest_levels, seed=None):\n", - " rng = default_rng(seed=seed)\n", - " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", - " probs_arr = utils_df - np.log(-np.log(rands))\n", - " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", - " return choices\n", - " \n", - "make_choice(utils_df, nests_, nest_levels, seed)" - ] - }, - { - "cell_type": "code", - "execution_count": 348, - "id": "bca659db-4c88-4287-8f5e-9c9516e3ef41", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:59:00.410062Z", - "iopub.status.busy": "2021-09-01T01:59:00.409811Z", - "iopub.status.idle": "2021-09-01T01:59:00.621940Z", - "shell.execute_reply": "2021-09-01T01:59:00.621156Z", - "shell.execute_reply.started": "2021-09-01T01:59:00.410037Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "137248721 WALK\n", - "137248725 WALK\n", - "211388201 WALK_LOC\n", - "211388205 WALK_LOC\n", - "806388401 WALK_LOC\n", - "806388405 WALK_LOC\n", - "dtype: object" - ] - }, - "execution_count": 348, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "make_choice(utils_df, nests_, nest_levels)" - ] - }, - { - "cell_type": "code", - "execution_count": 453, - "id": "806ce752-0927-4d5b-a6cc-68d6c9b8a05e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:55:59.542669Z", - "iopub.status.busy": "2021-09-01T04:55:59.542360Z", - "iopub.status.idle": "2021-09-01T04:55:59.771722Z", - "shell.execute_reply": "2021-09-01T04:55:59.770801Z", - "shell.execute_reply.started": "2021-09-01T04:55:59.542615Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - " nested_probabilities = \\\n", - " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", - " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) \n", - " return base_probabilities\n", - "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)" - ] - }, - { - "cell_type": "code", - "execution_count": 451, - "id": "054fd9b1-72fc-49cc-a790-48ef75bcbaed", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:55:08.260140Z", - "iopub.status.busy": "2021-09-01T04:55:08.259914Z", - "iopub.status.idle": "2021-09-01T04:55:09.140095Z", - "shell.execute_reply": "2021-09-01T04:55:09.139119Z", - "shell.execute_reply.started": "2021-09-01T04:55:08.260116Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "86627409 WALK_LRF\n", - "86627413 WALK_LRF\n", - "86673657 WALK_LOC\n", - "86673658 WALK\n", - "86673659 WALK\n", - " ... \n", - "2464446025 WALK\n", - "2464446029 WALK\n", - "2464449633 WALK\n", - "2464449634 WALK\n", - "2464449637 WALK\n", - "Length: 168, dtype: object" - ] - }, - "execution_count": 451, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def simple_simulate_rum(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", - "\n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices\n", - "\n", - "simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" - ] - }, - { - "cell_type": "code", - "execution_count": 380, - "id": "2da81b5e-7992-4437-8e0f-905caf026804", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:33:10.886502Z", - "iopub.status.busy": "2021-09-01T02:33:10.886252Z", - "iopub.status.idle": "2021-09-01T02:33:11.092145Z", - "shell.execute_reply": "2021-09-01T02:33:11.090725Z", - "shell.execute_reply.started": "2021-09-01T02:33:10.886465Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'trip_mode_choice'" - ] - }, - "execution_count": 380, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trace_label" - ] - }, - { - "cell_type": "code", - "execution_count": 393, - "id": "f3c5c1ba-e9c3-4511-98bf-9ac98c987da7", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:39:52.539983Z", - "iopub.status.busy": "2021-09-01T02:39:52.539668Z", - "iopub.status.idle": "2021-09-01T02:39:52.796170Z", - "shell.execute_reply": "2021-09-01T02:39:52.795498Z", - "shell.execute_reply.started": "2021-09-01T02:39:52.539944Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " segment_trace_label, log_alt_losers=log_alt_losers, \n", - " trace_column_names=trace_column_names, seed=seed)\n", - "\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " choices_list.append(choices)\n", - "\n", - " choices_df = pd.concat(choices_list)\n", - " return choices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 454, - "id": "74eeedd3-4ade-4729-8170-79fa9dcf11f8", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:58:52.825042Z", - "iopub.status.busy": "2021-09-01T04:58:52.824768Z", - "iopub.status.idle": "2021-09-01T04:58:53.042884Z", - "shell.execute_reply": "2021-09-01T04:58:53.042061Z", - "shell.execute_reply.started": "2021-09-01T04:58:52.825004Z" - } - }, - "outputs": [], - "source": [ - "## caculate probabilities with Asim methodology, should be correct\n", - "def gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None):\n", - " full_probs = [] # analytical probs\n", - "\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " #choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " # segment_trace_label, log_alt_losers=log_alt_losers, \n", - " # trace_column_names=trace_column_names, seed=seed)\n", - " probs = simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, \n", - " estimator, segment_trace_label, log_alt_losers, \n", - " trace_column_names)\n", - " full_probs.append(probs)\n", - " probs_df = pd.concat(full_probs)\n", - " return probs_df\n" - ] - }, - { - "cell_type": "code", - "execution_count": 455, - "id": "295a4fd1-8e85-47e2-af82-9f1beac31d63", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:59:46.126555Z", - "iopub.status.busy": "2021-09-01T04:59:46.126262Z", - "iopub.status.idle": "2021-09-01T04:59:52.979908Z", - "shell.execute_reply": "2021-09-01T04:59:52.979043Z", - "shell.execute_reply.started": "2021-09-01T04:59:46.126522Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2021-09-01 14:59:46.353007 Start\n", - "2021-09-01 14:59:52.976736 End\n" - ] - } - ], - "source": [ - "print(f\"{datetime.now()} Start\")\n", - "pipeline.open_pipeline(resume_after)\n", - "inject.get_injectable('preload_injectables', None)\n", - "model_name = \"trip_mode_choice\"\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "\n", - "step_name = model_name\n", - "args = {}\n", - "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "chunk_size = 0\n", - "\n", - "trace_label = 'trip_mode_choice'\n", - "model_settings_file_name = 'trip_mode_choice.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - "mode_column_name = 'trip_mode'\n", - "trips_df = trips.to_frame()\n", - "#print(\"Running with %d trips\", trips_df.shape[0])\n", - "tours_merged = tours_merged.to_frame()\n", - "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "# - trips_merged - merge trips and tours_merged\n", - "trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - "assert trips_merged.index.equals(trips.index)\n", - "\n", - "# setup skim keys\n", - "assert ('trip_period' not in trips_merged)\n", - "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - "orig_col = 'origin'\n", - "dest_col = 'destination'\n", - "\n", - "constants = {}\n", - "constants.update(config.get_model_constants(model_settings))\n", - "constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - "})\n", - "\n", - "skim_dict = network_los.get_default_skim_dict()\n", - "\n", - "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "\n", - "skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - "}\n", - "\n", - "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - "nest_spec = config.get_logit_model_settings(model_settings)\n", - "\n", - "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - "\n", - "all_choices = []\n", - "for i in range(100):\n", - " if i % 10 == 0:\n", - " print(f\"{datetime.now()} iteration {i}\")\n", - " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=None)\n", - " all_choices.append(choices_df)\n", - "all_choices = pd.concat(all_choices, axis=1)\n", - "\n", - "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", - " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", - " log_alt_losers=None, trace_column_names=None)\n", - "\n", - "# update trips table with choices (and potionally logssums)\n", - "#trips_df = trips.to_frame()\n", - "#\n", - "#assign_in_place(trips_df, choices_df)\n", - "#assert not trips_df[mode_column_name].isnull().any()\n", - "\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - "print(f\"{datetime.now()} End\")" - ] - }, - { - "cell_type": "code", - "execution_count": 463, - "id": "dc87e904-16d1-44d4-81b7-cafbe4e223c3", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:01:40.901982Z", - "iopub.status.busy": "2021-09-01T05:01:40.901759Z", - "iopub.status.idle": "2021-09-01T05:01:41.337758Z", - "shell.execute_reply": "2021-09-01T05:01:41.336757Z", - "shell.execute_reply.started": "2021-09-01T05:01:40.901946Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#all_choices.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", - "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts = val_counts / all_choices.shape[1]\n", - "#val_counts = val_counts.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", - "#val_counts['prob_of_asim_choice'] = val_counts.apply(lambda x: x[x.asim], axis=1) # this is what our simulation says w.r.t. to asim choice\n", - "# for 100% and many samples should mostly agree" - ] - }, - { - "cell_type": "code", - "execution_count": 464, - "id": "09bf151c-a39a-4823-acfd-9c8ae747c338", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:01:46.119117Z", - "iopub.status.busy": "2021-09-01T05:01:46.118849Z", - "iopub.status.idle": "2021-09-01T05:01:46.354114Z", - "shell.execute_reply": "2021-09-01T05:01:46.353248Z", - "shell.execute_reply.started": "2021-09-01T05:01:46.119092Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#val_counts['prob_of_asim_choice'].hist(bins=100);" - ] - }, - { - "cell_type": "code", - "execution_count": 490, - "id": "f3b6ade9-d875-4104-b438-b53ef42e342c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:10:08.653227Z", - "iopub.status.busy": "2021-09-01T05:10:08.652978Z", - "iopub.status.idle": "2021-09-01T05:10:08.915620Z", - "shell.execute_reply": "2021-09-01T05:10:08.914513Z", - "shell.execute_reply.started": "2021-09-01T05:10:08.653189Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", - "analytical 3.152 0.852 0.0 0.0 0.0 \n", - "experiment 3.243 0.880 0.0 0.0 0.0 \n", - "\n", - " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", - "analytical 0.0 0.0 0.0 0.685 0.0 \n", - "experiment 0.0 0.0 0.0 0.772 0.0 \n", - "\n", - " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK \\\n", - "analytical 0.129 0.0 0.182 0.260 1.334 63.708 \n", - "experiment 0.156 0.0 0.174 0.259 1.434 63.243 \n", - "\n", - " WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", - "analytical 0.0 0.0 0.455 18.355 10.887 \n", - "experiment 0.0 0.0 0.394 18.639 10.807 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mode_share_analytical = (probs_nl.sum(axis=0) / probs_nl.shape[0]).to_frame('analytical')\n", - "assert np.allclose(mode_share_analytical.sum(), 1)\n", - "mode_share_rum = (val_counts.sum(axis=0) / val_counts.shape[0]).to_frame('experiment')\n", - "assert np.allclose(mode_share_rum.sum(), 1)\n", - "full_share = mode_share_analytical.join(mode_share_rum, how='outer').fillna(0)\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * full_share).T)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e48a56e-d613-4dfd-bef6-62bee524f12f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50914e1d-750d-440c-9617-f96bd2a46c56", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f2d70e0e-520e-49a6-8024-ae31345f6ead", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "f9e8ef02-1d57-4b21-8aef-25a6e1095c02", - "metadata": {}, - "source": [ - "### try zenith normalisation of simple_simulate_rum" - ] - }, - { - "cell_type": "code", - "execution_count": 629, - "id": "db235e90-d2e5-4e90-b41c-6d90a670e41d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:24.045881Z", - "iopub.status.busy": "2021-09-01T11:37:24.045637Z", - "iopub.status.idle": "2021-09-01T11:37:24.276488Z", - "shell.execute_reply": "2021-09-01T11:37:24.274898Z", - "shell.execute_reply.started": "2021-09-01T11:37:24.045844Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation belowi s equivalent.\n", - "def compute_nested_utilities_zenith(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) # / nest.product_of_coefficients #coefficient\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", - " \n", - " # now go over all leaves and correct for scale\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] /= nest.coefficient\n", - " \n", - " return nested_utilities\n", - "\n", - "\n", - "def simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " utils_df = compute_nested_utilities_zenith(raw_utilities, nest_spec)\n", - "\n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices\n", - "\n", - "#simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" - ] - }, - { - "cell_type": "code", - "execution_count": 630, - "id": "f69bc3dc-84e4-4dd5-9a57-c053cadba4b5", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:24.828926Z", - "iopub.status.busy": "2021-09-01T11:37:24.828707Z", - "iopub.status.idle": "2021-09-01T11:37:25.046409Z", - "shell.execute_reply": "2021-09-01T11:37:25.045399Z", - "shell.execute_reply.started": "2021-09-01T11:37:24.828902Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " choices = simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " segment_trace_label, log_alt_losers=log_alt_losers, \n", - " trace_column_names=trace_column_names, seed=seed)\n", - "\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " choices_list.append(choices)\n", - "\n", - " choices_df = pd.concat(choices_list)\n", - " return choices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 631, - "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:32.250808Z", - "iopub.status.busy": "2021-09-01T11:37:32.250548Z", - "iopub.status.idle": "2021-09-01T20:20:35.257463Z", - "shell.execute_reply": "2021-09-01T20:20:35.255964Z", - "shell.execute_reply.started": "2021-09-01T11:37:32.250782Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "num_samples = 10\n", - "\n", - "rng_test = default_rng(23423)\n", - "\n", - "print(f\"{datetime.now()} Start\")\n", - "pipeline.open_pipeline(resume_after)\n", - "inject.get_injectable('preload_injectables', None)\n", - "model_name = \"trip_mode_choice\"\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "\n", - "step_name = model_name\n", - "args = {}\n", - "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "chunk_size = 0\n", - "\n", - "trace_label = 'trip_mode_choice'\n", - "model_settings_file_name = 'trip_mode_choice.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - "mode_column_name = 'trip_mode'\n", - "trips_df = trips.to_frame()\n", - "#print(\"Running with %d trips\", trips_df.shape[0])\n", - "tours_merged = tours_merged.to_frame()\n", - "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "# - trips_merged - merge trips and tours_merged\n", - "trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - "assert trips_merged.index.equals(trips.index)\n", - "\n", - "# setup skim keys\n", - "assert ('trip_period' not in trips_merged)\n", - "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - "orig_col = 'origin'\n", - "dest_col = 'destination'\n", - "\n", - "constants = {}\n", - "constants.update(config.get_model_constants(model_settings))\n", - "constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - "})\n", - "\n", - "skim_dict = network_los.get_default_skim_dict()\n", - "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - "}\n", - "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - "nest_spec = config.get_logit_model_settings(model_settings)\n", - "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - "all_choices_zenith = []\n", - "all_choices = []\n", - "for i in range(num_samples):\n", - " \n", - " seed = rng_test.integers(0, 100000) #int(9.3 * (i+1)**3) # why not\n", - " \n", - " if i % 50 == 0:\n", - " print(f\"{datetime.now()} iteration {i}\")\n", - " choices_df_zenith = stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", - " all_choices_zenith.append(choices_df_zenith)\n", - "\n", - " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", - " all_choices.append(choices_df)\n", - " \n", - " t_ = choices_df_zenith.merge(choices_df, left_index=True, right_index=True, suffixes=['_zenith', '_asim'])\n", - " diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", - " \n", - " #print(f\"seed {seed} leads to {diffs.shape[0]} differences. tripids {diffs.index}\")\n", - "\n", - "all_choices_zenith = pd.concat(all_choices_zenith, axis=1)\n", - "all_choices = pd.concat(all_choices, axis=1)\n", - "\n", - "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", - " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", - " log_alt_losers=None, trace_column_names=None)\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - "print(f\"{datetime.now()} End\")" - ] - }, - { - "cell_type": "code", - "execution_count": 643, - "id": "3abd7949-dc9c-469b-845e-26c17741f70e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:33:21.533110Z", - "iopub.status.busy": "2021-09-01T20:33:21.531915Z", - "iopub.status.idle": "2021-09-01T20:33:21.740946Z", - "shell.execute_reply": "2021-09-01T20:33:21.739713Z", - "shell.execute_reply.started": "2021-09-01T20:33:21.533074Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Have 2038 samples\n" - ] - } - ], - "source": [ - "print(f\"Have {all_choices_zenith.shape[1]} samples\")" - ] - }, - { - "cell_type": "code", - "execution_count": 633, - "id": "8762bd6d-ac04-458c-869f-be86d8297351", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:21:27.778803Z", - "iopub.status.busy": "2021-09-01T20:21:27.778531Z", - "iopub.status.idle": "2021-09-01T20:21:28.675645Z", - "shell.execute_reply": "2021-09-01T20:21:28.674734Z", - "shell.execute_reply.started": "2021-09-01T20:21:27.778754Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "val_counts_zenith = all_choices_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts_zenith = val_counts_zenith / all_choices_zenith.shape[1]\n", - "\n", - "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts = val_counts / all_choices.shape[1]" - ] - }, - { - "cell_type": "code", - "execution_count": 634, - "id": "0f566105-9424-457b-86f5-d60c1d63aac5", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:21:30.954894Z", - "iopub.status.busy": "2021-09-01T20:21:30.954654Z", - "iopub.status.idle": "2021-09-01T20:21:31.179689Z", - "shell.execute_reply": "2021-09-01T20:21:31.178304Z", - "shell.execute_reply.started": "2021-09-01T20:21:30.954867Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
experiment_zenith3.1500.8740.00.00.00.00.00.00.6800.00.1360.00.1540.2361.28563.7670.00.00.51518.82410.379
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", - "analytical 3.152 0.852 0.0 0.0 0.0 \n", - "experiment 3.243 0.880 0.0 0.0 0.0 \n", - "experiment_zenith 3.150 0.874 0.0 0.0 0.0 \n", - "\n", - " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", - "analytical 0.0 0.0 0.0 0.685 0.0 \n", - "experiment 0.0 0.0 0.0 0.772 0.0 \n", - "experiment_zenith 0.0 0.0 0.0 0.680 0.0 \n", - "\n", - " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE \\\n", - "analytical 0.129 0.0 0.182 0.260 1.334 \n", - "experiment 0.156 0.0 0.174 0.259 1.434 \n", - "experiment_zenith 0.136 0.0 0.154 0.236 1.285 \n", - "\n", - " WALK WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", - "analytical 63.708 0.0 0.0 0.455 18.355 10.887 \n", - "experiment 63.243 0.0 0.0 0.394 18.639 10.807 \n", - "experiment_zenith 63.767 0.0 0.0 0.515 18.824 10.379 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mode_share_rum_zenith = (val_counts_zenith.sum(axis=0) / val_counts_zenith.shape[0]).to_frame('experiment_zenith')\n", - "assert np.allclose(mode_share_rum_zenith.sum(), 1)\n", - "full_share_incl_zenith = full_share.merge(mode_share_rum_zenith, left_index=True, right_index=True, how='outer').fillna(0)\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * full_share_incl_zenith).T)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c0d3081-b45b-4573-9246-e4a9d39591dd", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "612b2f04-45e2-4eb2-b52a-9f753427877f", - "metadata": {}, - "source": [ - "## investigate diverging seed and look at diff in formulation" - ] - }, - { - "cell_type": "code", - "execution_count": 619, - "id": "ac09dc75-a92f-43fb-a0cd-fe7de1f8b66d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:12:02.988141Z", - "iopub.status.busy": "2021-09-01T11:12:02.987900Z", - "iopub.status.idle": "2021-09-01T11:12:03.212179Z", - "shell.execute_reply": "2021-09-01T11:12:03.210993Z", - "shell.execute_reply.started": "2021-09-01T11:12:02.988102Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation based on asim probabilities is equivalent\n", - "def compute_nested_utilities_asim(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) / nest.product_of_coefficients\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", - " return nested_utilities\n", - "\n", - "def compute_nested_utilities_zenith_check(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) # scale correction is below\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", - " \n", - " # now go over all leaves and correct for scale\n", - " for nest in logit.each_nest(nest_spec):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] /= nest.coefficient\n", - " \n", - " return nested_utilities" - ] - }, - { - "cell_type": "code", - "execution_count": 688, - "id": "4b7c6368-21ca-4442-969e-f2f8ff868842", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:49:56.958862Z", - "iopub.status.busy": "2021-09-01T20:49:56.958623Z", - "iopub.status.idle": "2021-09-01T20:49:57.191884Z", - "shell.execute_reply": "2021-09-01T20:49:57.190975Z", - "shell.execute_reply.started": "2021-09-01T20:49:56.958827Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None, use_zenith=True, raw_utilities=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " \n", - " if raw_utilities is None:\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " if use_zenith:\n", - " utils_df = compute_nested_utilities_zenith_check(raw_utilities, nest_spec)\n", - " else:\n", - " utils_df = compute_nested_utilities_asim(raw_utilities, nest_spec)\n", - "\n", - " # test\n", - " #return compute_nested_utilities_zenith_check(raw_utilities, nest_spec), compute_nested_utilities_asim(raw_utilities, nest_spec)\n", - " \n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices" - ] - }, - { - "cell_type": "code", - "execution_count": 626, - "id": "bd6cadc3-79f5-4824-b55e-639c39d5ab18", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:13:12.077886Z", - "iopub.status.busy": "2021-09-01T11:13:12.077652Z", - "iopub.status.idle": "2021-09-01T11:13:12.290222Z", - "shell.execute_reply": "2021-09-01T11:13:12.288861Z", - "shell.execute_reply.started": "2021-09-01T11:13:12.077863Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# t_z, t_a = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed)\n", - "# tr_id = 86673661\n", - "# display(t_z.loc[t_z.index==tr_id])\n", - "# display(t_a.loc[t_a.index==tr_id])" - ] - }, - { - "cell_type": "code", - "execution_count": 625, - "id": "42e6da62-58e8-4b16-9169-9faa1fc8a162", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:13:06.847583Z", - "iopub.status.busy": "2021-09-01T11:13:06.847204Z", - "iopub.status.idle": "2021-09-01T11:13:08.401700Z", - "shell.execute_reply": "2021-09-01T11:13:08.400711Z", - "shell.execute_reply.started": "2021-09-01T11:13:06.847554Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "seed 51006 leads to 34 differences out of 168. tripids Int64Index([ 86673657, 86673661, 106741681, 106741682, 106741685,\n", - " 106741686, 106741687, 106741688, 211327433, 211327437,\n", - " 444793574, 484173905, 484173909, 535170694, 535620053,\n", - " 708171014, 943749470, 943749471, 1060575853, 1091770617,\n", - " 1146472489, 1146472493, 1276281769, 1276281773, 1658748793,\n", - " 1658748797, 1767013726, 1767186577, 1767186578, 1768237161,\n", - " 1768237165, 1768237166, 2463663417, 2463663421],\n", - " dtype='int64', name='trip_id')\n" - ] - } - ], - "source": [ - "seed = 51006\n", - "x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True)\n", - "y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False)\n", - "t_ = x_.to_frame('trip_mode_zenith').merge(y_.to_frame('trip_mode_asim'), left_index=True, right_index=True)\n", - "diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", - "print(f\"seed {seed} leads to {diffs.shape[0]} differences out of {t_.shape[0]}. tripids {diffs.index}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2f3c0506-a79d-4a31-9d56-71b9966d0a92", - "metadata": {}, - "outputs": [], - "source": [ - "seed 51006 leads to 3 differences. tripids Int64Index([86673661, 535170689, 1060575849], dtype='int64', name='trip_id')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1a5f332a-b1d7-4a80-bb21-d37a1611cf9f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "24c5e4a1-6142-49d4-8da8-a1e266b3cb9b", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 753, - "id": "2098907f-28af-4f53-af74-6aaee8a6c53c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T23:04:23.861048Z", - "iopub.status.busy": "2021-09-01T23:04:23.860748Z", - "iopub.status.idle": "2021-09-01T23:04:24.069307Z", - "shell.execute_reply": "2021-09-01T23:04:24.068434Z", - "shell.execute_reply.started": "2021-09-01T23:04:23.861024Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "rng_ = default_rng(seed=100)\n", - "new_utils = pd.DataFrame(0.1 * -np.log(-np.log(rng_.uniform(0,1,raw_utilities.shape))), columns=raw_utilities.columns)\n", - "new_utils.index = raw_utilities.index" - ] - }, - { - "cell_type": "code", - "execution_count": 767, - "id": "da48a178-82b2-44a5-b45a-e9de3bb5e5c2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T23:17:20.369675Z", - "iopub.status.busy": "2021-09-01T23:17:20.369424Z", - "iopub.status.idle": "2021-09-01T23:17:20.666506Z", - "shell.execute_reply": "2021-09-01T23:17:20.665626Z", - "shell.execute_reply.started": "2021-09-01T23:17:20.369637Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
1372487210.0566420.0373050.0427800.0295760.0861540.0265860.0883130.1003310.0280210.0185160.0643910.0200090.0263990.0226220.0230070.0217540.0871260.0211710.0364750.0276500.135171
1372487250.1019020.0228760.0312270.0699900.0569130.0466850.0884110.0877850.0254140.0381620.0239880.0288000.0393180.0201600.0483640.0230030.0339590.0345970.0377080.0995230.041213
2113882010.0375620.0700330.0559510.0403890.0200410.0959740.0946780.0943680.0360140.0250510.0348600.0349530.0354990.0280400.0436500.0247130.0313880.0259310.0669990.0526780.051228
2113882050.0354800.0562560.0439440.0480850.0420780.0622210.1068190.0872650.0351170.0303990.0351540.0264690.0506460.0357270.0242840.0329050.0394270.0410920.0547140.0484590.063458
8063884010.0549180.0404880.0331490.0542940.0434770.0679820.1524650.0741520.0334040.0287350.0592690.0243390.0369680.0277600.0235220.0609320.0234450.0239310.0446430.0337730.058353
8063884050.0327640.0573010.0184370.0770990.0469630.0635210.0949510.0890720.0308260.0378960.0357520.0198780.0495360.0796100.0269220.0248330.0248400.0266950.0799360.0401460.043022
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n", - "trip_id \n", - "137248721 0.056642 0.037305 0.042780 0.029576 \n", - "137248725 0.101902 0.022876 0.031227 0.069990 \n", - "211388201 0.037562 0.070033 0.055951 0.040389 \n", - "211388205 0.035480 0.056256 0.043944 0.048085 \n", - "806388401 0.054918 0.040488 0.033149 0.054294 \n", - "806388405 0.032764 0.057301 0.018437 0.077099 \n", - "\n", - " SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\n", - "trip_id \n", - "137248721 0.086154 0.026586 0.088313 0.100331 0.028021 0.018516 \n", - "137248725 0.056913 0.046685 0.088411 0.087785 0.025414 0.038162 \n", - "211388201 0.020041 0.095974 0.094678 0.094368 0.036014 0.025051 \n", - "211388205 0.042078 0.062221 0.106819 0.087265 0.035117 0.030399 \n", - "806388401 0.043477 0.067982 0.152465 0.074152 0.033404 0.028735 \n", - "806388405 0.046963 0.063521 0.094951 0.089072 0.030826 0.037896 \n", - "\n", - " WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\n", - "trip_id \n", - "137248721 0.064391 0.020009 0.026399 0.022622 0.023007 0.021754 \n", - "137248725 0.023988 0.028800 0.039318 0.020160 0.048364 0.023003 \n", - "211388201 0.034860 0.034953 0.035499 0.028040 0.043650 0.024713 \n", - "211388205 0.035154 0.026469 0.050646 0.035727 0.024284 0.032905 \n", - "806388401 0.059269 0.024339 0.036968 0.027760 0.023522 0.060932 \n", - "806388405 0.035752 0.019878 0.049536 0.079610 0.026922 0.024833 \n", - "\n", - " DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n", - "trip_id \n", - "137248721 0.087126 0.021171 0.036475 0.027650 0.135171 \n", - "137248725 0.033959 0.034597 0.037708 0.099523 0.041213 \n", - "211388201 0.031388 0.025931 0.066999 0.052678 0.051228 \n", - "211388205 0.039427 0.041092 0.054714 0.048459 0.063458 \n", - "806388401 0.023445 0.023931 0.044643 0.033773 0.058353 \n", - "806388405 0.024840 0.026695 0.079936 0.040146 0.043022 " - ] - }, - "execution_count": 767, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nested_exp_utilities_cf = simulate.compute_nested_exp_utilities(new_utils, nest_spec)\n", - "nested_probabilities_cf = simulate.compute_nested_probabilities(nested_exp_utilities_cf, nest_spec, trace_label=None)\n", - "base_probabilities_cf = simulate.compute_base_probabilities(nested_probabilities_cf, nest_spec, spec)\n", - "base_probabilities_cf" - ] - }, - { - "cell_type": "code", - "execution_count": 799, - "id": "50dfd1ae-10c3-475c-94be-de783c2fa5c2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:15:29.056902Z", - "iopub.status.busy": "2021-09-02T00:15:29.056683Z", - "iopub.status.idle": "2021-09-02T00:27:21.965206Z", - "shell.execute_reply": "2021-09-02T00:27:21.964340Z", - "shell.execute_reply.started": "2021-09-02T00:15:29.056877Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 11min 49s, sys: 766 ms, total: 11min 50s\n", - "Wall time: 11min 52s\n" - ] - } - ], - "source": [ - "%%time\n", - "\n", - "num_samples = 10000 # 7.5s per 100\n", - "\n", - "data_zenith = []\n", - "data_asim = []\n", - "for i in range(num_samples):\n", - " seed = rng_.integers(0, 100000)\n", - " x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True, raw_utilities=new_utils)\n", - " y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False, raw_utilities=new_utils)\n", - " data_zenith.append(x_)\n", - " data_asim.append(y_)\n", - " \n", - "data_asim = pd.concat(data_asim, axis=1)\n", - "data_zenith = pd.concat(data_zenith, axis=1)\n", - "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", - "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_asim = counts_asim / data_asim.shape[1]\n", - "\n", - "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", - "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", - "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", - "# assert np.allclose(mode_share_zenith.sum(), 1)\n", - "# assert np.allclose(mode_share_asim.sum(), 1)\n", - "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", - "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 796, - "id": "bb16f9d7-44f5-4316-b981-d65216a5e217", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:14:11.772592Z", - "iopub.status.busy": "2021-09-02T00:14:11.772347Z", - "iopub.status.idle": "2021-09-02T00:14:12.001077Z", - "shell.execute_reply": "2021-09-02T00:14:12.000166Z", - "shell.execute_reply.started": "2021-09-02T00:14:11.772556Z" - } - }, - "outputs": [], - "source": [ - "#temp_z = data_zenith.copy()\n", - "#temp_a = data_asim.copy()\n", - "#data_asim = data_asim.join(temp_a, lsuffix=\"_o\", rsuffix=\"_n\")\n", - "#data_zenith = data_zenith.join(temp_z, lsuffix=\"_o\", rsuffix=\"_n\")\n", - "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", - "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_asim = counts_asim / data_asim.shape[1]\n", - "\n", - "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", - "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", - "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", - "# assert np.allclose(mode_share_zenith.sum(), 1)\n", - "# assert np.allclose(mode_share_asim.sum(), 1)\n", - "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", - "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 805, - "id": "031d8340-0af0-4608-8a83-ed2af4aaa3b2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:41:01.948669Z", - "iopub.status.busy": "2021-09-02T00:41:01.948422Z", - "iopub.status.idle": "2021-09-02T00:41:02.175972Z", - "shell.execute_reply": "2021-09-02T00:41:02.174759Z", - "shell.execute_reply.started": "2021-09-02T00:41:01.948633Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
zenith8.0555.1674.6313.5023.6064.2663.9633.4983.8955.1764.7995.8514.9996.1214.6689.4864.2284.1252.9773.6713.317
asim6.0966.6984.9773.2023.5224.3344.1293.1843.2245.1496.2838.2964.7936.5844.5327.4844.1524.2732.6293.4333.023
probs8.8835.3214.7382.8903.1364.0033.5653.1623.7585.3244.9276.0495.3416.5415.03710.4273.9734.2242.5743.1472.979
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP DRIVE_HVY \\\n", - "zenith 8.055 5.167 4.631 3.502 3.606 4.266 \n", - "asim 6.096 6.698 4.977 3.202 3.522 4.334 \n", - "probs 8.883 5.321 4.738 2.890 3.136 4.003 \n", - "\n", - " DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY SHARED3FREE \\\n", - "zenith 3.963 3.498 3.895 5.176 4.799 \n", - "asim 4.129 3.184 3.224 5.149 6.283 \n", - "probs 3.565 3.162 3.758 5.324 4.927 \n", - "\n", - " SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK WALK_COM WALK_EXP \\\n", - "zenith 5.851 4.999 6.121 4.668 9.486 4.228 4.125 \n", - "asim 8.296 4.793 6.584 4.532 7.484 4.152 4.273 \n", - "probs 6.049 5.341 6.541 5.037 10.427 3.973 4.224 \n", - "\n", - " WALK_HVY WALK_LOC WALK_LRF \n", - "zenith 2.977 3.671 3.317 \n", - "asim 2.629 3.433 3.023 \n", - "probs 2.574 3.147 2.979 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# sample of 20000\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * mode_share_comp).T)" - ] - }, - { - "cell_type": "code", - "execution_count": 806, - "id": "aece448f-4055-4d2a-b90d-8530caec9cb9", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:43:32.971972Z", - "iopub.status.busy": "2021-09-02T00:43:32.971684Z", - "iopub.status.idle": "2021-09-02T00:43:33.223345Z", - "shell.execute_reply": "2021-09-02T00:43:33.222488Z", - "shell.execute_reply.started": "2021-09-02T00:43:32.971932Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
trip_id137248721_z137248721_a137248721
BIKE0.092700.054350.100331
DRIVEALONEFREE0.053650.050050.056642
DRIVEALONEPAY0.038800.031550.037305
DRIVE_COM0.028700.024700.021171
DRIVE_EXP0.028800.026050.021754
DRIVE_HVY0.078150.099700.087126
DRIVE_LOC0.028750.027000.022622
DRIVE_LRF0.027800.024450.023007
SHARED2FREE0.043250.023000.042780
SHARED2PAY0.033600.015850.029576
SHARED3FREE0.073650.119200.086154
SHARED3PAY0.032600.039600.026586
TAXI0.035100.050300.036475
TNC_SHARED0.126200.181150.135171
TNC_SINGLE0.026250.038450.027650
WALK0.079400.048250.088313
WALK_COM0.031850.024600.026399
WALK_EXP0.057050.058950.064391
WALK_HVY0.025200.018400.020009
WALK_LOC0.034550.027400.028021
WALK_LRF0.023950.017000.018516
\n", - "
" - ], - "text/plain": [ - "trip_id 137248721_z 137248721_a 137248721\n", - "BIKE 0.09270 0.05435 0.100331\n", - "DRIVEALONEFREE 0.05365 0.05005 0.056642\n", - "DRIVEALONEPAY 0.03880 0.03155 0.037305\n", - "DRIVE_COM 0.02870 0.02470 0.021171\n", - "DRIVE_EXP 0.02880 0.02605 0.021754\n", - "DRIVE_HVY 0.07815 0.09970 0.087126\n", - "DRIVE_LOC 0.02875 0.02700 0.022622\n", - "DRIVE_LRF 0.02780 0.02445 0.023007\n", - "SHARED2FREE 0.04325 0.02300 0.042780\n", - "SHARED2PAY 0.03360 0.01585 0.029576\n", - "SHARED3FREE 0.07365 0.11920 0.086154\n", - "SHARED3PAY 0.03260 0.03960 0.026586\n", - "TAXI 0.03510 0.05030 0.036475\n", - "TNC_SHARED 0.12620 0.18115 0.135171\n", - "TNC_SINGLE 0.02625 0.03845 0.027650\n", - "WALK 0.07940 0.04825 0.088313\n", - "WALK_COM 0.03185 0.02460 0.026399\n", - "WALK_EXP 0.05705 0.05895 0.064391\n", - "WALK_HVY 0.02520 0.01840 0.020009\n", - "WALK_LOC 0.03455 0.02740 0.028021\n", - "WALK_LRF 0.02395 0.01700 0.018516" - ] - }, - "execution_count": 806, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# TODO: look at probs per trip, i.e. do not sum across trips\n", - "trip_id = 137248721\n", - "\n", - "counts_zenith.loc[counts_zenith.index == trip_id].T.merge(\n", - " counts_asim.loc[counts_asim.index == trip_id].T, suffixes=['_z', '_a'], left_index=True, right_index=True, how='outer').merge(\n", - " base_probabilities_cf.loc[base_probabilities_cf.index == trip_id].T, suffixes=['', '_probs'], left_index=True, right_index=True, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ef84f10-d616-40e4-87e5-425499027ff3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "431cee49-40a9-4dc7-93a5-75308fb302dd", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1fcdc0ff-c510-4878-8672-8365e046442c", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/frozen_impl_dev.ipynb b/notebooks/frozen_impl_dev.ipynb deleted file mode 100644 index d4838d8b09..0000000000 --- a/notebooks/frozen_impl_dev.ipynb +++ /dev/null @@ -1,661 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", - "metadata": {}, - "source": [ - "# validate results\n", - "\n", - "## TODO\n", - "what happened to tracing when I fixed probability calcs - it seems like all trip ids are attached when I add one by\n", - "hand below - why?" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.385153Z", - "iopub.status.busy": "2022-05-01T10:17:57.384881Z", - "iopub.status.idle": "2022-05-01T10:17:57.534433Z", - "shell.execute_reply": "2022-05-01T10:17:57.533096Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%load_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c3dba451-1e10-403e-8614-35d57e6577f4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.536623Z", - "iopub.status.busy": "2022-05-01T10:17:57.536012Z", - "iopub.status.idle": "2022-05-01T10:17:57.542755Z", - "shell.execute_reply": "2022-05-01T10:17:57.541685Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:00.350944Z", - "iopub.status.busy": "2022-05-01T10:18:00.350730Z", - "iopub.status.idle": "2022-05-01T10:18:12.760977Z", - "shell.execute_reply": "2022-05-01T10:18:12.760013Z", - "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "import os\n", - "import argparse\n", - "from datetime import datetime\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "from numpy.random import default_rng\n", - "from statsmodels.stats.gof import powerdiscrepancy\n", - "from statsmodels.stats.proportion import multinomial_proportions_confint\n", - "import numba\n", - "import matplotlib.pyplot as plt\n", - "\n", - "from activitysim.cli import run\n", - "from activitysim.core import inject\n", - "\n", - "from fru_utils import run_trip_mode_choice, comp_mode_shares, mode_choice_for_trip" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:16.944537Z", - "iopub.status.busy": "2022-05-01T10:18:16.944291Z", - "iopub.status.idle": "2022-05-01T10:18:17.124764Z", - "shell.execute_reply": "2022-05-01T10:18:17.123725Z", - "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "pd.set_option(\"max_columns\", 500)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "outputs": [ - { - "data": { - "text/plain": "array([2, 1, 2])" - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "np.argmax(np.array([[1,2,3],[4,6,5],[7,8,9]]), axis=1)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 34, - "outputs": [ - { - "data": { - "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": "0 c\n1 b\n2 c\ndtype: object" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", - "display(df_)\n", - "ch_ = df_.idxmax(1)\n", - "display(ch_)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:17.839947Z", - "iopub.status.busy": "2022-05-01T10:18:17.839070Z", - "iopub.status.idle": "2022-05-01T10:18:18.019676Z", - "shell.execute_reply": "2022-05-01T10:18:18.018689Z", - "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_example_mtc\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:18.894533Z", - "iopub.status.busy": "2022-05-01T10:18:18.894303Z", - "iopub.status.idle": "2022-05-01T10:18:19.078807Z", - "shell.execute_reply": "2022-05-01T10:18:19.077951Z", - "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "os.chdir(example_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:22.111723Z", - "iopub.status.busy": "2022-05-01T10:18:22.111490Z", - "iopub.status.idle": "2022-05-01T10:18:22.297437Z", - "shell.execute_reply": "2022-05-01T10:18:22.296501Z", - "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "parser = argparse.ArgumentParser()\n", - "run.add_run_args(parser)\n", - "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", - "#run.run(args) # 2mins full example run\n", - "if not inject.is_injectable('preload_injectables'):\n", - " from activitysim import abm # register abm steps and other abm-specific injectables\n", - "run.handle_standard_args(args) # possibly update injectables" - ] - }, - { - "cell_type": "code", - "execution_count": 69, - "outputs": [], - "source": [ - "from activitysim.core import inject\n", - "from activitysim.core import pipeline\n", - "from activitysim.core import config\n", - "from activitysim.core import expressions\n", - "from activitysim.core import simulate\n", - "from activitysim.abm.models.util import estimation\n", - "\n", - "def run_mandatory_tour_frequency(choose_individual_max_utility=False):\n", - " resume_after = \"cdap_simulate\"\n", - " model_name = \"mandatory_tour_frequency\"\n", - " chunk_size = 0 # test_mtc means no chunking\n", - "\n", - " pipeline.open_pipeline(resume_after)\n", - " # preload any bulky injectables (e.g. skims) not in pipeline\n", - " inject.get_injectable('preload_injectables', None)\n", - " pipeline._PIPELINE.rng().begin_step(model_name)\n", - " #step_name = model_name\n", - " args = {}\n", - " #checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - " inject.set_step_args(args)\n", - "\n", - " #network_los = inject.get_injectable('network_los')\n", - " persons_merged = inject.get_table('persons_merged')\n", - "\n", - " trace_label = 'mandatory_tour_frequency'\n", - " model_settings_file_name = 'mandatory_tour_frequency.yaml'\n", - " model_settings = config.read_model_settings(model_settings_file_name)\n", - " choosers = persons_merged.to_frame()\n", - " # filter based on results of CDAP\n", - " choosers = choosers[choosers.cdap_activity == 'M']\n", - " print(\"Running mandatory_tour_frequency with %d persons\", len(choosers))\n", - " preprocessor_settings = model_settings.get('preprocessor', None)\n", - " if preprocessor_settings:\n", - " locals_dict = {}\n", - " expressions.assign_columns(\n", - " df=choosers,\n", - " model_settings=preprocessor_settings,\n", - " locals_dict=locals_dict,\n", - " trace_label=trace_label)\n", - "\n", - " estimator = estimation.manager.begin_estimation('mandatory_tour_frequency')\n", - " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - " coefficients_df = simulate.read_model_coefficients(model_settings)\n", - " model_spec = simulate.eval_coefficients(model_spec, coefficients_df, estimator)\n", - " nest_spec = config.get_logit_model_settings(model_settings)\n", - " constants = config.get_model_constants(model_settings)\n", - " choices = simulate.simple_simulate(\n", - " choosers=choosers,\n", - " spec=model_spec,\n", - " nest_spec=nest_spec,\n", - " locals_d=constants,\n", - " chunk_size=chunk_size,\n", - " trace_label=trace_label,\n", - " trace_choice_name='mandatory_tour_frequency',\n", - " estimator=estimator,\n", - " choose_individual_max_utility=choose_individual_max_utility)\n", - "\n", - " # convert indexes to alternative names\n", - " choices = pd.Series(model_spec.columns[choices.values], index=choices.index)\n", - "\n", - " finalise = True\n", - " if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - " return choices" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 70, - "outputs": [], - "source": [ - "#pipeline.close_pipeline()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 71, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle mandatory_tour_frequency not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running mandatory_tour_frequency with %d persons 89\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle mandatory_tour_frequency not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running mandatory_tour_frequency with %d persons 89\n" - ] - } - ], - "source": [ - "ch_prev = run_mandatory_tour_frequency(choose_individual_max_utility=False)\n", - "ch_new = run_mandatory_tour_frequency(choose_individual_max_utility=True)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 72, - "outputs": [ - { - "data": { - "text/plain": " count_prev count_new\nschool1 22 22.0\nwork1 61 65.0\nwork2 2 2.0\nwork_and_school 4 0.0", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
count_prevcount_new
school12222.0
work16165.0
work222.0
work_and_school40.0
\n
" - }, - "execution_count": 72, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ch_prev.value_counts().to_frame(\"count_prev\").merge(\n", - " ch_new.value_counts().to_frame(\"count_new\"), how=\"outer\", left_index=True, right_index=True\n", - ").fillna(0)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 73, - "outputs": [ - { - "data": { - "text/plain": "{'input_table_list': [{'tablename': 'households',\n 'filename': 'households.csv',\n 'index_col': 'household_id',\n 'rename_columns': {'HHID': 'household_id',\n 'PERSONS': 'hhsize',\n 'workers': 'num_workers',\n 'VEHICL': 'auto_ownership',\n 'TAZ': 'home_zone_id'},\n 'keep_columns': ['home_zone_id',\n 'income',\n 'hhsize',\n 'HHT',\n 'auto_ownership',\n 'num_workers']},\n {'tablename': 'persons',\n 'filename': 'persons.csv',\n 'index_col': 'person_id',\n 'rename_columns': {'PERID': 'person_id'},\n 'keep_columns': ['household_id',\n 'age',\n 'PNUM',\n 'sex',\n 'pemploy',\n 'pstudent',\n 'ptype']},\n {'tablename': 'land_use',\n 'filename': 'land_use.csv',\n 'index_col': 'zone_id',\n 'rename_columns': {'TAZ': 'zone_id', 'COUNTY': 'county_id'},\n 'keep_columns': ['DISTRICT',\n 'SD',\n 'county_id',\n 'TOTHH',\n 'TOTPOP',\n 'TOTACRE',\n 'RESACRE',\n 'CIACRE',\n 'TOTEMP',\n 'AGE0519',\n 'RETEMPN',\n 'FPSEMPN',\n 'HEREMPN',\n 'OTHEMPN',\n 'AGREMPN',\n 'MWTEMPN',\n 'PRKCST',\n 'OPRKCST',\n 'area_type',\n 'HSENROLL',\n 'COLLFTE',\n 'COLLPTE',\n 'TOPOLOGY',\n 'TERMINAL']}],\n 'households_sample_size': 100,\n 'check_for_variability': False,\n 'use_shadow_pricing': False,\n 'want_dest_choice_sample_tables': False,\n 'log_alt_losers': False,\n 'chunk_size': 0,\n 'min_available_chunk_ratio': 0.05,\n 'default_initial_rows_per_chunk': 500,\n 'chunk_method': 'hybrid_uss',\n 'chunk_training_mode': 'disabled',\n 'keep_chunk_logs': True,\n 'keep_mem_logs': True,\n 'trace_hh_id': [982875],\n 'trace_od': None,\n 'resume_after': None,\n 'checkpoints': True,\n 'models': ['initialize_landuse',\n 'initialize_households',\n 'compute_accessibility',\n 'school_location',\n 'workplace_location',\n 'auto_ownership_simulate',\n 'free_parking',\n 'cdap_simulate',\n 'mandatory_tour_frequency',\n 'mandatory_tour_scheduling',\n 'joint_tour_frequency',\n 'joint_tour_composition',\n 'joint_tour_participation',\n 'joint_tour_destination',\n 'joint_tour_scheduling',\n 'non_mandatory_tour_frequency',\n 'non_mandatory_tour_destination',\n 'non_mandatory_tour_scheduling',\n 'tour_mode_choice_simulate',\n 'atwork_subtour_frequency',\n 'atwork_subtour_destination',\n 'atwork_subtour_scheduling',\n 'atwork_subtour_mode_choice',\n 'stop_frequency',\n 'trip_purpose',\n 'trip_destination',\n 'trip_purpose_and_destination',\n 'trip_scheduling',\n 'trip_mode_choice',\n 'write_data_dictionary',\n 'track_skim_usage',\n 'write_trip_matrices',\n 'write_tables'],\n 'output_tables': {'h5_store': False,\n 'action': 'include',\n 'prefix': 'final_',\n 'tables': ['checkpoints',\n 'accessibility',\n 'land_use',\n 'households',\n 'persons',\n 'tours',\n 'trips',\n 'joint_tour_participants']},\n 'urban_threshold': 4,\n 'cbd_threshold': 2,\n 'rural_threshold': 6,\n 'min_value_of_time': 1,\n 'max_value_of_time': 50,\n 'distributed_vot_mu': 0.684,\n 'distributed_vot_sigma': 0.85,\n 'household_median_value_of_time': {1: 6.01, 2: 8.81, 3: 10.44, 4: 12.86},\n 'source_file_paths': ['configs/settings.yaml']}" - }, - "execution_count": 73, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "inject.get_injectable('settings')" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 74, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle mandatory_tour_frequency not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running mandatory_tour_frequency with %d persons 89\n" - ] - } - ], - "source": [ - "resume_after = \"cdap_simulate\"\n", - "model_name = \"mandatory_tour_frequency\"\n", - "chunk_size = 0 # test_mtc means no chunking\n", - "\n", - "pipeline.open_pipeline(resume_after)\n", - "# preload any bulky injectables (e.g. skims) not in pipeline\n", - "inject.get_injectable('preload_injectables', None)\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "#step_name = model_name\n", - "args = {}\n", - "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "#network_los = inject.get_injectable('network_los')\n", - "persons_merged = inject.get_table('persons_merged')\n", - "\n", - "trace_label = 'mandatory_tour_frequency'\n", - "model_settings_file_name = 'mandatory_tour_frequency.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "choosers = persons_merged.to_frame()\n", - "# filter based on results of CDAP\n", - "choosers = choosers[choosers.cdap_activity == 'M']\n", - "print(\"Running mandatory_tour_frequency with %d persons\", len(choosers))\n", - "preprocessor_settings = model_settings.get('preprocessor', None)\n", - "if preprocessor_settings:\n", - " locals_dict = {}\n", - " expressions.assign_columns(\n", - " df=choosers,\n", - " model_settings=preprocessor_settings,\n", - " locals_dict=locals_dict,\n", - " trace_label=trace_label)\n", - "\n", - "estimator = estimation.manager.begin_estimation('mandatory_tour_frequency')\n", - "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - "coefficients_df = simulate.read_model_coefficients(model_settings)\n", - "model_spec = simulate.eval_coefficients(model_spec, coefficients_df, estimator)\n", - "nest_spec = config.get_logit_model_settings(model_settings)\n", - "constants = config.get_model_constants(model_settings)\n", - "choices = simulate.simple_simulate(\n", - " choosers=choosers,\n", - " spec=model_spec,\n", - " nest_spec=nest_spec,\n", - " locals_d=constants,\n", - " chunk_size=chunk_size,\n", - " trace_label=trace_label,\n", - " trace_choice_name='mandatory_tour_frequency',\n", - " estimator=estimator,\n", - " choose_individual_max_utility=True)\n", - "\n", - "# convert indexes to alternative names\n", - "choices = pd.Series(model_spec.columns[choices.values], index=choices.index)\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 58, - "outputs": [ - { - "data": { - "text/plain": " a b c\n0 1 2 3\n1 4 6 5\n2 7 8 9", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
abc
0123
1465
2789
\n
" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "df_ = pd.DataFrame([[1,2,3],[4,6,5],[7,8,9]], columns=[\"a\", \"b\", \"c\"])\n", - "display(df_)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 64, - "outputs": [ - { - "data": { - "text/plain": "{'a': 0, 'b': 1, 'c': 2}" - }, - "execution_count": 64, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "{v: k for k,v in enumerate(df_.columns)}" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/notebooks/frozen_impl_interaction_sample_dev.ipynb b/notebooks/frozen_impl_interaction_sample_dev.ipynb deleted file mode 100644 index 79412c9265..0000000000 --- a/notebooks/frozen_impl_interaction_sample_dev.ipynb +++ /dev/null @@ -1,1660 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "%load_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c3dba451-1e10-403e-8614-35d57e6577f4", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import os\n", - "import argparse\n", - "from datetime import datetime\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from activitysim.cli import run\n", - "from activitysim.core import inject" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "pd.set_option(\"display.max_columns\", 500)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "root_dir = \"/home/jan/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_runs\", \"seq_asim\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "ename": "FileNotFoundError", - "evalue": "[Errno 2] No such file or directory: '/home/jan/code/activitysim/test_runs/seq_asim'", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mFileNotFoundError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m/tmp/ipykernel_24910/2284515201.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mos\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchdir\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mexample_dir\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", - "\u001B[0;31mFileNotFoundError\u001B[0m: [Errno 2] No such file or directory: '/home/jan/code/activitysim/test_runs/seq_asim'" - ] - } - ], - "source": [ - "os.chdir(example_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "parser = argparse.ArgumentParser()\n", - "run.add_run_args(parser)\n", - "args = parser.parse_args(['-c', 'configs_lessmodes', '-o', 'output_fru', '-d', 'data'])\n", - "#run.run(args) # 2mins full example run\n", - "if not inject.is_injectable('preload_injectables'):\n", - " from activitysim import abm # register abm steps and other abm-specific injectables\n", - "run.handle_standard_args(args) # possibly update injectables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9955cc9c", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "from activitysim.core import inject\n", - "from activitysim.core import pipeline\n", - "from activitysim.core import config\n", - "from activitysim.core import simulate\n", - "from activitysim.abm.models.util import estimation\n", - "from activitysim.abm.tables import shadow_pricing\n", - "from activitysim.core import interaction_simulate\n", - "from activitysim.core import logit\n", - "from activitysim.core.simulate import set_skim_wrapper_targets\n", - "from activitysim.core import chunk" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "3a923505", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "from activitysim.core.logit import inverse_ev1_cdf\n", - "\n", - "def hack_make_sample_choices(\n", - " choosers, probs,\n", - " alternatives,\n", - " sample_size, alternative_count, alt_col_name,\n", - " allow_zero_probs,\n", - " trace_label,\n", - " utilities=None,\n", - " choose_individual_max_utility=False\n", - "):\n", - " assert isinstance(probs, pd.DataFrame)\n", - " assert probs.shape == (len(choosers), alternative_count)\n", - " assert isinstance(alternatives, pd.DataFrame)\n", - " assert len(alternatives) == alternative_count\n", - "\n", - " if allow_zero_probs:\n", - " zero_probs = (probs.sum(axis=1) == 0)\n", - " if zero_probs.all():\n", - " return pd.DataFrame(columns=[alt_col_name, 'rand', 'prob', choosers.index.name])\n", - " if zero_probs.any():\n", - " # remove from sample\n", - " probs = probs[~zero_probs]\n", - " choosers = choosers[~zero_probs]\n", - " # TODO [janzill Jun2022]: do we want this for consistency?\n", - " # might need this in other places too?\n", - " if utilities is not None:\n", - " utilities = utilities[~zero_probs]\n", - "\n", - " if choose_individual_max_utility:\n", - " assert isinstance(utilities, pd.DataFrame)\n", - " #print(utilities.head(3))\n", - " assert utilities.shape == (len(choosers), alternative_count)\n", - " #print(utilities.shape)\n", - "\n", - " choice_dimension = (len(choosers), alternative_count, sample_size)\n", - " rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count*sample_size)\n", - " #print(f\"after generation rands shape = {rands.shape}\", flush=True)\n", - " rands = rands.reshape(choice_dimension)\n", - " #print(f\"before inverse_ev1 rands shape = {rands.shape}\", flush=True)\n", - " rands = inverse_ev1_cdf(rands)\n", - " #print(f\"after inverse_ev1 rands shape = {rands.shape}\", flush=True)\n", - " utilities = utilities.to_numpy() # this should be much cleaner once xarray changes are implemented\n", - " utilities = np.repeat(utilities[:,:,None], sample_size, axis=2)\n", - " #print(f\"after utils reshape: {utilities.shape}\", flush=True)\n", - " utilities += rands\n", - " # this gives us len(choosers), sample_size dimensions, with values the chosen alternative\n", - " choices_array = np.argmax(utilities, axis=1)\n", - " print(choices_array.shape)\n", - "\n", - " choosers_index_rep = np.tile(np.arange(0,choices_array.shape[0]), sample_size)\n", - " #np.repeat(np.arange(0,choices_array.shape[0]), sample_size)\n", - " choices_flattened = choices_array.flatten(order='F')\n", - " #print(f\"choices flattened shape = {choices_flattened.shape}\")\n", - "\n", - " print(choosers_index_rep.shape, flush=True)\n", - " print(probs.shape, flush=True)\n", - " print(probs.head(3), flush=True)\n", - " probs_look_up = probs.to_numpy()[choosers_index_rep, choices_flattened]\n", - " #print(f\"probs_look_up shape = {probs_look_up.shape}\", flush=True)\n", - "\n", - " # choices_flattened are 0-based index into alternatives, need to map to alternative values given by\n", - " # alternatives.index.values (they are in this order by construction)\n", - " # explode to one row per chooser.index, alt_zone_id\n", - " choices_df = pd.DataFrame({\n", - " alt_col_name: alternatives.index.values[choices_flattened],\n", - " #'rand': rands.flatten(order='F'),\n", - " 'rand': np.zeros_like(choosers_index_rep), # TODO: zero out for now\n", - " 'prob': probs_look_up.flatten(order='F'),\n", - " # repeat is wrong here - we do not want 1,1,2,2,3,3, etc, but 1,2,3,1,2,3 by construction\n", - " #choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", - " choosers.index.name: np.tile(choosers.index.values, sample_size)\n", - " })\n", - "\n", - " else:\n", - " cum_probs_array = probs.values.cumsum(axis=1)\n", - " # alt probs in convenient layout to return prob of chose alternative\n", - " # (same layout as cum_probs_arr)\n", - " alt_probs_array = probs.values.flatten()\n", - " # get sample_size rands for each chooser\n", - " rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size)\n", - " # transform as we iterate over alternatives\n", - " # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr\n", - " # i.e rands[i] is a 2-D array of one alt choice rand for each chooser\n", - " rands = rands.T.reshape(sample_size, -1, 1)\n", - " # the alternative value chosen\n", - " choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype)\n", - " # chunk log these later after we populate them...\n", - " # the probability of the chosen alternative\n", - " choice_probs_array = np.empty([sample_size, len(choosers)])\n", - " # chunk log these later after we populate them...\n", - " alts = np.tile(alternatives.index.values, len(choosers))\n", - " # FIXME - do this all at once rather than iterate?\n", - " for i in range(sample_size):\n", - " # FIXME - do this in numpy, not pandas?\n", - " # rands for this alt in broadcastable shape\n", - " r = rands[i]\n", - "\n", - " # position of first occurrence of positive value\n", - " positions = np.argmax(cum_probs_array > r, axis=1)\n", - "\n", - " # FIXME - leave positions as numpy array, not pandas series?\n", - " # positions is series with the chosen alternative represented as a column index in probs\n", - " # which is an integer between zero and num alternatives in the alternative sample\n", - " positions = pd.Series(positions, index=probs.index)\n", - "\n", - " # need to get from an integer offset into the alternative sample to the alternative index\n", - " # that is, we want the index value of the row that is offset by rows into the\n", - " # tranche of this choosers alternatives created by cross join of alternatives and choosers\n", - "\n", - " # offsets is the offset into model_design df of first row of chooser alternatives\n", - " offsets = np.arange(len(positions)) * alternative_count\n", - "\n", - " # choices and choice_probs have one element per chooser and is in same order as choosers\n", - " choices_array[i] = np.take(alts, positions + offsets)\n", - " choice_probs_array[i] = np.take(alt_probs_array, positions + offsets)\n", - " del positions\n", - " del offsets\n", - "\n", - " del alts\n", - " del cum_probs_array\n", - " del alt_probs_array\n", - "\n", - " # explode to one row per chooser.index, alt_zone_id\n", - " choices_df = pd.DataFrame(\n", - " {alt_col_name: choices_array.flatten(order='F'),\n", - " 'rand': rands.flatten(order='F'),\n", - " 'prob': choice_probs_array.flatten(order='F'),\n", - " choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", - " })\n", - "\n", - " return choices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "7599d0d1-9063-48aa-95de-cef45e926a42", - "metadata": {}, - "outputs": [], - "source": [ - "def make_sample_choices_dev(\n", - " choosers, probs,\n", - " alternatives,\n", - " sample_size, alternative_count, alt_col_name,\n", - " allow_zero_probs,\n", - " trace_label,\n", - " utilities=None,\n", - " choose_individual_max_utility=False\n", - "):\n", - " assert isinstance(probs, pd.DataFrame)\n", - " assert probs.shape == (len(choosers), alternative_count)\n", - " assert isinstance(alternatives, pd.DataFrame)\n", - " assert len(alternatives) == alternative_count\n", - "\n", - " if allow_zero_probs:\n", - " zero_probs = (probs.sum(axis=1) == 0)\n", - " if zero_probs.all():\n", - " return pd.DataFrame(columns=[alt_col_name, 'rand', 'prob', choosers.index.name])\n", - " if zero_probs.any():\n", - " # remove from sample\n", - " probs = probs[~zero_probs]\n", - " choosers = choosers[~zero_probs]\n", - " # TODO [janzill Jun2022]: do we want this for consistency?\n", - " # might need this in other places too?\n", - " if utilities is not None:\n", - " utilities = utilities[~zero_probs]\n", - "\n", - "\n", - " if choose_individual_max_utility:\n", - " # Note [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension\n", - " # (len(choosers), alternative_count, sample_size) can get very large\n", - " choices_array = np.zeros_like(utilities, dtype=np.uint32)\n", - " zero_dim_index = np.arange(utilities.shape[0])\n", - "\n", - " utils_array = utilities.to_numpy() # TODO [janzill Jun2022]: once or for each?\n", - " for i in range(sample_size):\n", - " rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count)\n", - " choices_array[zero_dim_index, np.argmax(utils_array - np.log(-np.log(rands)), axis=1)] += 1\n", - "\n", - " i, j = np.nonzero(choices_array)\n", - " chunk.log_df(trace_label, 'i', i)\n", - " chunk.log_df(trace_label, 'j', j)\n", - "\n", - " #probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", - " # trace_label=trace_label, trace_choosers=choosers)\n", - " del utilities\n", - " choices_df = pd.DataFrame({\n", - " alt_col_name: alternatives.index.values[j],\n", - " \"pick_count\": choices_array[i, j],\n", - " \"prob\": probs.to_numpy()[i, j],\n", - " choosers.index.name: choosers.index.values[i]\n", - " })\n", - " del choices_array\n", - " del i\n", - " del j\n", - " # del probs\n", - " \n", - " else:\n", - " cum_probs_array = probs.values.cumsum(axis=1)\n", - " # alt probs in convenient layout to return prob of chose alternative\n", - " # (same layout as cum_probs_arr)\n", - " alt_probs_array = probs.values.flatten()\n", - " # get sample_size rands for each chooser\n", - " rands = pipeline.get_rn_generator().random_for_df(probs, n=sample_size)\n", - " # transform as we iterate over alternatives\n", - " # reshape so rands[i] is in broadcastable (2-D) shape for cum_probs_arr\n", - " # i.e rands[i] is a 2-D array of one alt choice rand for each chooser\n", - " rands = rands.T.reshape(sample_size, -1, 1)\n", - " # the alternative value chosen\n", - " choices_array = np.empty([sample_size, len(choosers)]).astype(alternatives.index.dtype)\n", - " # chunk log these later after we populate them...\n", - " # the probability of the chosen alternative\n", - " choice_probs_array = np.empty([sample_size, len(choosers)])\n", - " # chunk log these later after we populate them...\n", - " alts = np.tile(alternatives.index.values, len(choosers))\n", - " # FIXME - do this all at once rather than iterate?\n", - " for i in range(sample_size):\n", - " # FIXME - do this in numpy, not pandas?\n", - " # rands for this alt in broadcastable shape\n", - " r = rands[i]\n", - "\n", - " # position of first occurrence of positive value\n", - " positions = np.argmax(cum_probs_array > r, axis=1)\n", - "\n", - " # FIXME - leave positions as numpy array, not pandas series?\n", - " # positions is series with the chosen alternative represented as a column index in probs\n", - " # which is an integer between zero and num alternatives in the alternative sample\n", - " positions = pd.Series(positions, index=probs.index)\n", - "\n", - " # need to get from an integer offset into the alternative sample to the alternative index\n", - " # that is, we want the index value of the row that is offset by rows into the\n", - " # tranche of this choosers alternatives created by cross join of alternatives and choosers\n", - "\n", - " # offsets is the offset into model_design df of first row of chooser alternatives\n", - " offsets = np.arange(len(positions)) * alternative_count\n", - "\n", - " # choices and choice_probs have one element per chooser and is in same order as choosers\n", - " choices_array[i] = np.take(alts, positions + offsets)\n", - " choice_probs_array[i] = np.take(alt_probs_array, positions + offsets)\n", - " del positions\n", - " del offsets\n", - "\n", - " del alts\n", - " del cum_probs_array\n", - " del alt_probs_array\n", - "\n", - " # explode to one row per chooser.index, alt_zone_id\n", - " choices_df = pd.DataFrame(\n", - " {alt_col_name: choices_array.flatten(order='F'),\n", - " 'rand': rands.flatten(order='F'),\n", - " 'prob': choice_probs_array.flatten(order='F'),\n", - " choosers.index.name: np.repeat(np.asanyarray(choosers.index), sample_size)\n", - " })\n", - "\n", - " # pick_count and pick_dup\n", - " # pick_count is number of duplicate picks\n", - " # pick_dup flag is True for all but first of duplicates\n", - " pick_group = choices_df.groupby([choosers.index.name, alt_col_name])\n", - " # number each item in each group from 0 to the length of that group - 1.\n", - " choices_df['pick_count'] = pick_group.cumcount(ascending=True)\n", - " # flag duplicate rows after first\n", - " choices_df['pick_dup'] = choices_df['pick_count'] > 0\n", - " # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge)\n", - " choices_df['pick_count'] += pick_group.cumcount(ascending=False) + 1\n", - " # drop the duplicates\n", - " choices_df = choices_df[~choices_df['pick_dup']]\n", - " del choices_df['pick_dup']\n", - " # set index after groupby so we can trace on it\n", - " choices_df.set_index(choosers.index.name, inplace=True)\n", - " # don't need this after tracing\n", - " del choices_df['rand']\n", - " \n", - " return choices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "1611fe26", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'pipeline' is not defined", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n", - "\u001B[0;31mNameError\u001B[0m: name 'pipeline' is not defined" - ] - } - ], - "source": [ - "%%time\n", - "\n", - "choose_individual_max_utility = True\n", - "\n", - "resume_after = \"work_from_home\"\n", - "model_name = \"school_location\"\n", - "chunk_size = 0 # no chunking\n", - "\n", - "pipeline.open_pipeline(resume_after)\n", - "# preload any bulky injectables (e.g. skims) not in pipeline\n", - "inject.get_injectable('preload_injectables', None)\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "#step_name = model_name\n", - "args = {}\n", - "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "\n", - "persons_merged = inject.get_table('persons_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "households = inject.get_table('households')\n", - "persons = inject.get_table('persons')\n", - "locutor = inject.get_injectable('locutor')\n", - "\n", - "trace_label = model_name #'school_location'\n", - "model_settings_file_name = f\"{model_name}.yaml\" #'school_location.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "estimator = estimation.manager.begin_estimation(model_name)\n", - "# iterate_location_choice()\n", - "chunk_tag = trace_label\n", - "\n", - "# boolean to filter out persons not needing location modeling (e.g. is_worker, is_student)\n", - "chooser_filter_column = model_settings['CHOOSER_FILTER_COLUMN_NAME']\n", - "dest_choice_column_name = model_settings['DEST_CHOICE_COLUMN_NAME']\n", - "logsum_column_name = model_settings.get('DEST_CHOICE_LOGSUM_COLUMN_NAME')\n", - "sample_table_name = model_settings.get('DEST_CHOICE_SAMPLE_TABLE_NAME')\n", - "want_sample_table = config.setting('want_dest_choice_sample_tables') and sample_table_name is not None\n", - "persons_merged_df = persons_merged.to_frame()\n", - "persons_merged_df = persons_merged_df[persons_merged_df[chooser_filter_column]]\n", - "persons_merged_df.sort_index(inplace=True) # interaction_sample expects chooser index to be monotonic increasing\n", - "\n", - "# chooser segmentation allows different sets coefficients for e.g. different income_segments or tour_types\n", - "chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME']\n", - "assert chooser_segment_column in persons_merged_df, f\"CHOOSER_SEGMENT_COLUMN '{chooser_segment_column}' not in \" \\\n", - " f\"persons_merged table.\"\n", - "shadow_price_calculator = shadow_pricing.load_shadow_price_calculator(model_settings)\n", - "chooser_segment_column = model_settings['CHOOSER_SEGMENT_COLUMN_NAME']\n", - "# maps segment names to compact (integer) ids\n", - "segment_ids = model_settings['SEGMENT_IDS']\n", - "\n", - "sample_list = []\n", - "for segment_name, segment_id in segment_ids.items():\n", - " print(f\"running {segment_name}, {segment_id}\")\n", - " choosers = persons_merged_df[persons_merged_df[chooser_segment_column] == segment_id]\n", - " # size_term and shadow price adjustment - one row per zone\n", - " dest_size_terms = shadow_price_calculator.dest_size_terms(segment_name)\n", - " assert dest_size_terms.index.is_monotonic_increasing, f\"shadow_price_calculator.dest_size_terms({segment_name}) \" \\\n", - " f\"not monotonic_increasing\"\n", - " if choosers.shape[0] == 0:\n", - " print(f\"{trace_label} skipping segment {segment_name}: no choosers\")\n", - " continue\n", - " print(f\"dropping {(~(dest_size_terms.size_term > 0)).sum()} \"\n", - " f\"of {len(dest_size_terms)} rows where size_term is zero\")\n", - " dest_size_terms = dest_size_terms[dest_size_terms.size_term > 0]\n", - " chooser_columns = model_settings['SIMULATE_CHOOSER_COLUMNS']\n", - " choosers_location_sample = choosers[chooser_columns]\n", - " skim_dict = network_los.get_default_skim_dict()\n", - " skims = skim_dict.wrap('home_zone_id', 'zone_id')\n", - " alt_dest_col_name = model_settings['ALT_DEST_COL_NAME']\n", - " assert not choosers_location_sample.empty\n", - " print(\"Running %s with %d persons\" % (trace_label, len(choosers_location_sample.index)))\n", - " sample_size = model_settings[\"SAMPLE_SIZE\"]\n", - " locals_d = {\n", - " 'skims': skims,\n", - " 'segment_size': segment_name\n", - " }\n", - " constants = config.get_model_constants(model_settings)\n", - " locals_d.update(constants)\n", - " spec = simulate.spec_for_segment(model_settings, spec_id='SAMPLE_SPEC',\n", - " segment_name=segment_name, estimator=estimator)\n", - " ### choices = interaction_sample()\n", - " alt_col_name=alt_dest_col_name\n", - " allow_zero_probs=False\n", - " log_alt_losers=False\n", - " # we return alternatives ordered in (index, alt_col_name)\n", - " # if choosers index is not ordered, it is probably a mistake, since the alts wont line up\n", - " assert alt_col_name is not None\n", - " assert choosers.index.is_monotonic_increasing\n", - "\n", - " # FIXME - legacy logic - not sure this is needed or even correct?\n", - " sample_size = min(sample_size, len(dest_size_terms.index))\n", - "\n", - " result_list = []\n", - " for i, chooser_chunk, chunk_trace_label in chunk.adaptive_chunked_choosers(choosers_location_sample, chunk_size, trace_label,\n", - " chunk_tag):\n", - "\n", - " ### choices = hack_interaction_sample\n", - " # chooser = chooser_chunk\n", - " #alternatives = dest_size_terms\n", - " #trace_label=chunk_trace_label\n", - "\n", - " num_choosers = len(chooser_chunk.index)\n", - " assert num_choosers > 0\n", - " if len(spec.columns) > 1:\n", - " raise RuntimeError('spec must have only one column')\n", - " # if using skims, copy index into the dataframe, so it will be\n", - " # available as the \"destination\" for set_skim_wrapper_targets\n", - " if skims is not None and dest_size_terms.index.name not in dest_size_terms:\n", - " dest_size_terms = dest_size_terms.copy()\n", - " dest_size_terms[dest_size_terms.index.name] = dest_size_terms.index\n", - "\n", - " chooser_index_id = interaction_simulate.ALT_CHOOSER_ID if log_alt_losers else None\n", - "\n", - " # - cross join choosers and alternatives (cartesian product)\n", - " # for every chooser, there will be a row for each alternative\n", - " # index values (non-unique) are from alternatives df\n", - " alternative_count = dest_size_terms.shape[0]\n", - " interaction_df =\\\n", - " logit.interaction_dataset(chooser_chunk, dest_size_terms, sample_size=alternative_count,\n", - " chooser_index_id=chooser_index_id)\n", - "\n", - " assert alternative_count == len(interaction_df.index) / len(chooser_chunk.index)\n", - "\n", - " if skims is not None:\n", - " set_skim_wrapper_targets(interaction_df, skims)\n", - "\n", - " # evaluate expressions from the spec multiply by coefficients and sum\n", - " # spec is df with one row per spec expression and one col with utility coefficient\n", - " # column names of interaction_df match spec index values\n", - " # utilities has utility value for element in the cross product of choosers and alternatives\n", - " # interaction_utilities is a df with one utility column and one row per row in interaction_df\n", - " trace_rows = trace_ids = None\n", - "\n", - " # interaction_utilities is a df with one utility column and one row per interaction_df row\n", - " interaction_utilities, trace_eval_results = interaction_simulate.eval_interaction_utilities(\n", - " spec, interaction_df, locals_d, chunk_trace_label, trace_rows, estimator=None,\n", - " log_alt_losers=log_alt_losers\n", - " )\n", - " # ########### HWM - high water mark (point of max observed memory usage)\n", - " #del interaction_df\n", - "\n", - " # reshape utilities (one utility column and one row per row in interaction_utilities)\n", - " # to a dataframe with one row per chooser and one column per alternative\n", - " utilities = pd.DataFrame(\n", - " interaction_utilities.values.reshape(len(chooser_chunk), alternative_count),\n", - " index=chooser_chunk.index)\n", - " #del interaction_utilities\n", - "\n", - " # convert to probabilities (utilities exponentiated and normalized to probs)\n", - " # probs is same shape as utilities, one row per chooser and one column for alternative\n", - " probs = logit.utils_to_probs(utilities, allow_zero_probs=allow_zero_probs,\n", - " trace_label=chunk_trace_label, trace_choosers=chooser_chunk)\n", - " #del utilities\n", - "\n", - " choices_df = make_sample_choices_dev(\n", - " chooser_chunk, probs, dest_size_terms,\n", - " sample_size, alternative_count, alt_col_name,\n", - " allow_zero_probs=allow_zero_probs,\n", - " trace_label=chunk_trace_label,\n", - " utilities=utilities,\n", - " choose_individual_max_utility=choose_individual_max_utility\n", - " )\n", - " # - NARROW\n", - " choices_df['prob'] = choices_df['prob'].astype(np.float32)\n", - " assert (choices_df['pick_count'].max() < 4294967295) or (choices_df.empty)\n", - " choices_df['pick_count'] = choices_df['pick_count'].astype(np.uint32)\n", - "\n", - " if choices_df.shape[0] > 0:\n", - " result_list.append(choices_df)\n", - "\n", - " if len(result_list) > 1:\n", - " choices_df = pd.concat(result_list)\n", - " \n", - " # TODO: why does this fail\n", - " #assert allow_zero_probs or (len(choosers_location_sample.index) == len(np.unique(choices_df.index.values))), \\\n", - " # \"what is this\"\n", - " \n", - " # keep alts in canonical order so choices based on their probs are stable across runs\n", - " choices_df = choices_df.sort_values(by=alt_col_name).sort_index(kind='mergesort')\n", - "\n", - " sample_list.append(choices_df)\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0db07776-70ab-41d1-8dff-84cd7c30065a", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "87f5534d-ae21-4f7b-9c85-cd910cc375ae", - "metadata": {}, - "outputs": [], - "source": [ - "def run_fru():\n", - " test_df = make_sample_choices_dev(\n", - " chooser_chunk, probs, dest_size_terms,\n", - " sample_size, alternative_count, alt_col_name,\n", - " allow_zero_probs=allow_zero_probs,\n", - " trace_label=chunk_trace_label,\n", - " utilities=utilities,\n", - " choose_individual_max_utility=True\n", - " )\n", - " \n", - "\n", - "def run_previous():\n", - " test_df = make_sample_choices_dev(\n", - " chooser_chunk, probs, dest_size_terms,\n", - " sample_size, alternative_count, alt_col_name,\n", - " allow_zero_probs=allow_zero_probs,\n", - " trace_label=chunk_trace_label,\n", - " utilities=None,\n", - " choose_individual_max_utility=False\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "22315306-6ec6-4daf-9176-ba397493c945", - "metadata": { - "collapsed": true, - "jupyter": { - "outputs_hidden": true - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " " - ] - }, - { - "data": { - "text/plain": [ - " 37642 function calls (37233 primitive calls) in 3.187 seconds\n", - "\n", - " Ordered by: cumulative time\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 1 0.000 0.000 3.187 3.187 {built-in method builtins.exec}\n", - " 1 0.003 0.003 3.187 3.187 :1()\n", - " 1 0.000 0.000 3.185 3.185 2292568830.py:12(run_previous)\n", - " 1 2.165 2.165 3.185 3.185 3493973527.py:3(make_sample_choices_dev)\n", - " 170/148 0.006 0.000 0.344 0.002 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", - " 3 0.332 0.111 0.332 0.111 {method 'cumsum' of 'numpy.ndarray' objects}\n", - " 4 0.241 0.060 0.241 0.060 {method 'repeat' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.236 0.236 <__array_function__ internals>:177(tile)\n", - " 1 0.000 0.000 0.236 0.236 shape_base.py:1171(tile)\n", - " 4 0.221 0.055 0.221 0.055 {method 'flatten' of 'numpy.ndarray' objects}\n", - " 98 0.000 0.000 0.101 0.001 fromnumeric.py:51(_wrapfunc)\n", - " 30 0.000 0.000 0.077 0.003 <__array_function__ internals>:177(argmax)\n", - " 30 0.000 0.000 0.077 0.003 fromnumeric.py:1127(argmax)\n", - " 30 0.076 0.003 0.076 0.003 {method 'argmax' of 'numpy.ndarray' objects}\n", - " 2 0.000 0.000 0.065 0.033 groupby.py:3040(cumcount)\n", - " 2 0.004 0.002 0.058 0.029 groupby.py:1699(_cumcount_array)\n", - " 1 0.000 0.000 0.042 0.042 ops.py:832(group_info)\n", - " 1 0.000 0.000 0.042 0.042 ops.py:854(_get_compressed_codes)\n", - " 1 0.000 0.000 0.030 0.030 sorting.py:661(compress_group_index)\n", - " 73 0.023 0.000 0.023 0.000 {method 'take' of 'numpy.ndarray' objects}\n", - " 1 0.001 0.001 0.022 0.022 random.py:562(random_for_df)\n", - " 60 0.000 0.000 0.019 0.000 <__array_function__ internals>:177(take)\n", - " 60 0.000 0.000 0.019 0.000 fromnumeric.py:93(take)\n", - " 1 0.000 0.000 0.018 0.018 sorting.py:683(_reorder_by_uniques)\n", - " 1 0.005 0.005 0.018 0.018 random.py:598()\n", - " 63 0.000 0.000 0.016 0.000 common.py:55(new_method)\n", - " 4 0.000 0.000 0.015 0.004 frame.py:587(__init__)\n", - " 1 0.000 0.000 0.015 0.015 construction.py:425(dict_to_mgr)\n", - " 1 0.000 0.000 0.015 0.015 construction.py:102(arrays_to_mgr)\n", - " 62 0.000 0.000 0.015 0.000 arraylike.py:98(__add__)\n", - " 1 0.000 0.000 0.015 0.015 managers.py:1937(create_block_manager_from_column_arrays)\n", - " 62 0.000 0.000 0.015 0.000 series.py:5637(_arith_method)\n", - " 62 0.001 0.000 0.014 0.000 base.py:1286(_arith_method)\n", - " 9 0.000 0.000 0.013 0.001 frame.py:3463(__getitem__)\n", - " 13471 0.013 0.000 0.013 0.000 {method 'rand' of 'numpy.random.mtrand.RandomState' objects}\n", - " 101 0.001 0.000 0.012 0.000 series.py:323(__init__)\n", - " 1 0.011 0.011 0.011 0.011 {method 'get_labels_groupby' of 'pandas._libs.hashtable.Int64HashTable' objects}\n", - " 3 0.000 0.000 0.011 0.004 generic.py:3708(_take_with_is_copy)\n", - " 3 0.000 0.000 0.011 0.004 generic.py:3609(take)\n", - " 1 0.000 0.000 0.011 0.011 ops.py:790(codes)\n", - " 1 0.000 0.000 0.011 0.011 ops.py:793()\n", - " 2 0.000 0.000 0.011 0.005 grouper.py:616(codes)\n", - " 2 0.000 0.000 0.011 0.005 grouper.py:659(_codes_and_uniques)\n", - " 2 0.000 0.000 0.011 0.005 algorithms.py:568(factorize)\n", - " 3 0.000 0.000 0.011 0.004 managers.py:1683(_consolidate_inplace)\n", - " 3 0.000 0.000 0.010 0.003 managers.py:2074(_consolidate)\n", - " 8 0.005 0.001 0.010 0.001 managers.py:2091(_merge_blocks)\n", - " 2 0.000 0.000 0.009 0.005 algorithms.py:524(factorize_array)\n", - " 2 0.009 0.004 0.009 0.004 {method 'factorize' of 'pandas._libs.hashtable.Int64HashTable' objects}\n", - " 63 0.000 0.000 0.009 0.000 series.py:2988(_construct_result)\n", - " 8 0.009 0.001 0.009 0.001 {method 'argsort' of 'numpy.ndarray' objects}\n", - " 3 0.000 0.000 0.008 0.003 managers.py:875(take)\n", - " 1 0.000 0.000 0.007 0.007 managers.py:2008(_form_blocks)\n", - " 3 0.007 0.002 0.007 0.002 managers.py:2060(_stack_arrays)\n", - " 1 0.000 0.000 0.007 0.007 frame.py:3530(_getitem_bool_array)\n", - " 19 0.000 0.000 0.006 0.000 take.py:57(take_nd)\n", - " 19 0.000 0.000 0.006 0.000 take.py:120(_take_nd_ndarray)\n", - " 59/44 0.000 0.000 0.006 0.000 groupby.py:908(__getattribute__)\n", - " 3 0.000 0.000 0.006 0.002 managers.py:634(reindex_indexer)\n", - " 2 0.000 0.000 0.006 0.003 groupby.py:675(_selected_obj)\n", - " 7 0.000 0.000 0.006 0.001 blocks.py:1114(take_nd)\n", - " 17 0.000 0.000 0.005 0.000 <__array_function__ internals>:177(concatenate)\n", - " 3 0.000 0.000 0.005 0.002 <__array_function__ internals>:177(vstack)\n", - " 3 0.000 0.000 0.005 0.002 shape_base.py:222(vstack)\n", - " 3 0.000 0.000 0.005 0.002 <__array_function__ internals>:177(repeat)\n", - " 3 0.000 0.000 0.005 0.002 fromnumeric.py:436(repeat)\n", - " 1 0.004 0.004 0.004 0.004 {method 'put' of 'numpy.ndarray' objects}\n", - " 38 0.004 0.000 0.004 0.000 {built-in method numpy.arange}\n", - " 4861 0.003 0.000 0.004 0.000 {built-in method builtins.isinstance}\n", - " 1 0.000 0.000 0.003 0.003 managers.py:692()\n", - " 5 0.000 0.000 0.003 0.001 generic.py:5646(_consolidate_inplace)\n", - " 5 0.000 0.000 0.003 0.001 generic.py:5632(_protect_consolidate)\n", - " 5 0.000 0.000 0.003 0.001 generic.py:5650(f)\n", - " 5 0.000 0.000 0.003 0.001 managers.py:618(consolidate)\n", - " 102 0.001 0.000 0.003 0.000 construction.py:470(sanitize_array)\n", - " 5 0.000 0.000 0.003 0.001 managers.py:713(_slice_take_blocks_ax0)\n", - " 21 0.003 0.000 0.003 0.000 {built-in method numpy.asanyarray}\n", - " 62 0.000 0.000 0.002 0.000 array_ops.py:183(arithmetic_op)\n", - " 95 0.000 0.000 0.002 0.000 managers.py:1731(from_array)\n", - " 2 0.000 0.000 0.002 0.001 sorting.py:618(get_group_index_sorter)\n", - " 2 0.002 0.001 0.002 0.001 {pandas._libs.algos.groupsort_indexer}\n", - " 63 0.000 0.000 0.002 0.000 array_ops.py:134(_na_arithmetic_op)\n", - " 181 0.000 0.000 0.002 0.000 generic.py:5577(__setattr__)\n", - " 3 0.000 0.000 0.002 0.001 frame.py:3630(__setitem__)\n", - " 3 0.000 0.000 0.002 0.001 frame.py:3822(_set_item)\n", - " 96 0.000 0.000 0.002 0.000 blocks.py:2041(new_block)\n", - " 63 0.000 0.000 0.002 0.000 expressions.py:223(evaluate)\n", - " 63 0.000 0.000 0.002 0.000 expressions.py:92(_evaluate_numexpr)\n", - " 96 0.000 0.000 0.002 0.000 config.py:255(__call__)\n", - " 2 0.000 0.000 0.002 0.001 take.py:350(wrapper)\n", - " 1354 0.001 0.000 0.002 0.000 generic.py:43(_check)\n", - " 223 0.000 0.000 0.001 0.000 series.py:640(name)\n", - " 15/12 0.000 0.000 0.001 0.000 base.py:397(__new__)\n", - " 96 0.000 0.000 0.001 0.000 config.py:127(_get_option)\n", - " 63 0.000 0.000 0.001 0.000 expressions.py:63(_evaluate_standard)\n", - " 3 0.000 0.000 0.001 0.000 frame.py:3797(_set_item_mgr)\n", - " 2 0.000 0.000 0.001 0.001 base.py:5768(_get_indexer_strict)\n", - " 72 0.001 0.000 0.001 0.000 generic.py:5517(__finalize__)\n", - "1525/1157 0.001 0.000 0.001 0.000 {built-in method builtins.len}\n", - " 2 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis0_float64_float64}\n", - " 10 0.001 0.000 0.001 0.000 {method 'nonzero' of 'numpy.ndarray' objects}\n", - " 4 0.000 0.000 0.001 0.000 {built-in method builtins.next}\n", - " 4 0.000 0.000 0.001 0.000 groupby.py:998(_group_selection_context)\n", - " 171 0.000 0.000 0.001 0.000 construction.py:379(extract_array)\n", - " 62 0.001 0.000 0.001 0.000 {built-in method _operator.add}\n", - " 2 0.000 0.000 0.001 0.001 algorithms.py:1663(safe_sort)\n", - " 2 0.000 0.000 0.001 0.001 contextlib.py:108(__enter__)\n", - " 2 0.000 0.000 0.001 0.001 groupby.py:958(_set_group_selection)\n", - " 6 0.000 0.000 0.001 0.000 base.py:3706(get_indexer)\n", - " 5 0.000 0.000 0.001 0.000 base.py:1098(take)\n", - " 223 0.000 0.000 0.001 0.000 common.py:1721(validate_all_hashable)\n", - " 8 0.000 0.000 0.001 0.000 base.py:672(_with_infer)\n", - " 1 0.001 0.001 0.001 0.001 sorting.py:100(get_group_index)\n", - " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis1_int64_int64}\n", - " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis1_float64_float64}\n", - " 128 0.000 0.000 0.001 0.000 _ufunc_config.py:32(seterr)\n", - " 96 0.000 0.000 0.001 0.000 config.py:109(_get_single_key)\n", - " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis0_bool_bool}\n", - " 4 0.000 0.000 0.001 0.000 base.py:5744(get_indexer_for)\n", - " 228 0.000 0.000 0.001 0.000 {built-in method builtins.all}\n", - " 5 0.000 0.000 0.001 0.000 algorithms.py:1352(take)\n", - " 2 0.000 0.000 0.001 0.000 <__array_function__ internals>:177(nonzero)\n", - " 2 0.000 0.000 0.001 0.000 fromnumeric.py:1866(nonzero)\n", - " 1 0.000 0.000 0.001 0.001 generic.py:11363(__iadd__)\n", - " 1 0.000 0.000 0.001 0.001 generic.py:11337(_inplace_method)\n", - " 106 0.000 0.000 0.001 0.000 base.py:6987(ensure_index)\n", - " 1709 0.001 0.000 0.001 0.000 {built-in method builtins.getattr}\n", - " 2 0.000 0.000 0.001 0.000 managers.py:1219(insert)\n", - " 64 0.000 0.000 0.001 0.000 _ufunc_config.py:429(__enter__)\n", - " 2 0.000 0.000 0.001 0.000 base.py:3400(difference)\n", - " 110 0.000 0.000 0.001 0.000 base.py:7082(maybe_extract_name)\n", - " 3 0.000 0.000 0.001 0.000 generic.py:4020(__delitem__)\n", - " 2 0.000 0.000 0.001 0.000 base.py:4109(reindex)\n", - " 1 0.000 0.000 0.001 0.001 frame.py:3790(_iset_item_mgr)\n", - " 1 0.001 0.001 0.001 0.001 {pandas._libs.algos.take_2d_axis1_bool_bool}\n", - " 1 0.000 0.000 0.001 0.001 managers.py:1061(iset)\n", - " 12 0.001 0.000 0.001 0.000 {pandas._libs.algos.take_1d_int64_int64}\n", - " 102 0.000 0.000 0.001 0.000 construction.py:695(_try_cast)\n", - " 6 0.000 0.000 0.001 0.000 utils.py:249(maybe_convert_indices)\n", - " 5 0.000 0.000 0.001 0.000 index_tricks.py:322(__getitem__)\n", - " 3 0.000 0.000 0.001 0.000 managers.py:1299(idelete)\n", - " 446 0.000 0.000 0.001 0.000 common.py:1740()\n", - " 102 0.000 0.000 0.001 0.000 blocks.py:1960(maybe_coerce_values)\n", - " 105 0.000 0.000 0.001 0.000 generic.py:239(__init__)\n", - " 134 0.000 0.000 0.001 0.000 series.py:687(_values)\n", - " 1 0.000 0.000 0.001 0.001 blocks.py:378(delete)\n", - " 2 0.000 0.000 0.000 0.000 base.py:6561(insert)\n", - " 1 0.000 0.000 0.000 0.000 _decorators.py:302(wrapper)\n", - " 1 0.000 0.000 0.000 0.000 frame.py:5365(set_index)\n", - " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(delete)\n", - " 3 0.000 0.000 0.000 0.000 frame.py:4515(_sanitize_column)\n", - " 2 0.000 0.000 0.000 0.000 function_base.py:4958(delete)\n", - " 64 0.000 0.000 0.000 0.000 _ufunc_config.py:434(__exit__)\n", - " 99 0.000 0.000 0.000 0.000 common.py:552(require_length_match)\n", - " 3 0.000 0.000 0.000 0.000 frame.py:10986(_reindex_for_setitem)\n", - " 96 0.000 0.000 0.000 0.000 blocks.py:2055(check_ndim)\n", - " 2 0.000 0.000 0.000 0.000 base.py:3451(_difference)\n", - " 344 0.000 0.000 0.000 0.000 inference.py:321(is_hashable)\n", - " 6 0.000 0.000 0.000 0.000 {method 'copy' of 'numpy.ndarray' objects}\n", - " 105 0.000 0.000 0.000 0.000 blocks.py:1989(get_block_type)\n", - " 293 0.000 0.000 0.000 0.000 series.py:590(name)\n", - " 1 0.000 0.000 0.000 0.000 arraylike.py:54(__gt__)\n", - " 1 0.000 0.000 0.000 0.000 series.py:5613(_cmp_method)\n", - " 1 0.000 0.000 0.000 0.000 frame.py:7595(groupby)\n", - " 67 0.000 0.000 0.000 0.000 common.py:75(get_op_result_name)\n", - " 6 0.000 0.000 0.000 0.000 frame.py:3923(_get_item_cache)\n", - " 1 0.000 0.000 0.000 0.000 groupby.py:839(__init__)\n", - " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(diff)\n", - " 2 0.000 0.000 0.000 0.000 function_base.py:1295(diff)\n", - " 1 0.000 0.000 0.000 0.000 grouper.py:700(get_grouper)\n", - " 15 0.000 0.000 0.000 0.000 {pandas._libs.lib.maybe_convert_objects}\n", - " 61 0.000 0.000 0.000 0.000 series.py:825(__array__)\n", - " 13 0.000 0.000 0.000 0.000 base.py:5178(equals)\n", - " 134 0.000 0.000 0.000 0.000 managers.py:1848(internal_values)\n", - " 16 0.000 0.000 0.000 0.000 {method 'astype' of 'numpy.ndarray' objects}\n", - " 261 0.000 0.000 0.000 0.000 base.py:884(__len__)\n", - " 19 0.000 0.000 0.000 0.000 take.py:554(_take_preprocess_indexer_and_fill_value)\n", - " 63 0.000 0.000 0.000 0.000 dispatch.py:11(should_extension_dispatch)\n", - " 102 0.000 0.000 0.000 0.000 construction.py:627(_sanitize_ndim)\n", - " 9 0.000 0.000 0.000 0.000 base.py:7106(_maybe_cast_data_without_dtype)\n", - " 6 0.000 0.000 0.000 0.000 base.py:6293(_maybe_cast_listlike_indexer)\n", - " 1 0.000 0.000 0.000 0.000 {pandas._libs.algos.take_2d_axis0_int64_int64}\n", - " 128 0.000 0.000 0.000 0.000 _ufunc_config.py:131(geterr)\n", - " 95 0.000 0.000 0.000 0.000 construction.py:802(is_empty_data)\n", - " 96 0.000 0.000 0.000 0.000 config.py:589(_get_root)\n", - " 90 0.000 0.000 0.000 0.000 common.py:1587(_is_dtype_type)\n", - " 96 0.000 0.000 0.000 0.000 config.py:642(_warn_if_deprecated)\n", - " 178 0.000 0.000 0.000 0.000 construction.py:438(ensure_wrapped_if_datetimelike)\n", - " 5 0.000 0.000 0.000 0.000 frame.py:3411(_ixs)\n", - " 38 0.000 0.000 0.000 0.000 base.py:286(is_dtype)\n", - " 218 0.000 0.000 0.000 0.000 generic.py:5561(__getattr__)\n", - " 108 0.000 0.000 0.000 0.000 base.py:4820(_values)\n", - " 1 0.000 0.000 0.000 0.000 array_ops.py:227(comparison_op)\n", - " 28 0.000 0.000 0.000 0.000 common.py:497(is_categorical_dtype)\n", - " 12 0.000 0.000 0.000 0.000 base.py:554(_dtype_to_subclass)\n", - " 51 0.000 0.000 0.000 0.000 {built-in method numpy.empty}\n", - " 192 0.000 0.000 0.000 0.000 config.py:603(_get_deprecated_option)\n", - " 1 0.000 0.000 0.000 0.000 ops.py:684(shape)\n", - " 31 0.000 0.000 0.000 0.000 series.py:743(__len__)\n", - " 3 0.000 0.000 0.000 0.000 ops.py:686()\n", - " 8 0.000 0.000 0.000 0.000 base.py:6004(_should_compare)\n", - " 23 0.000 0.000 0.000 0.000 _dtype.py:328(_name_get)\n", - " 11 0.000 0.000 0.000 0.000 base.py:3577(get_loc)\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:603(ngroups)\n", - " 62 0.000 0.000 0.000 0.000 array_ops.py:441(maybe_prepare_scalar_for_op)\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:650(group_index)\n", - " 40 0.000 0.000 0.000 0.000 common.py:161(is_object_dtype)\n", - " 13 0.000 0.000 0.000 0.000 {pandas._libs.lib.infer_dtype}\n", - " 1 0.000 0.000 0.000 0.000 range.py:189(_data)\n", - " 4 0.000 0.000 0.000 0.000 algorithms.py:115(_ensure_data)\n", - " 62 0.000 0.000 0.000 0.000 __init__.py:152(align_method_SERIES)\n", - " 11 0.000 0.000 0.000 0.000 base.py:2596(inferred_type)\n", - " 1 0.000 0.000 0.000 0.000 {built-in method _operator.gt}\n", - " 322 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_list_like}\n", - " 30 0.000 0.000 0.000 0.000 {method 'reduce' of 'numpy.ufunc' objects}\n", - " 95 0.000 0.000 0.000 0.000 series.py:542(_set_axis)\n", - " 396 0.000 0.000 0.000 0.000 {built-in method builtins.issubclass}\n", - " 17 0.000 0.000 0.000 0.000 base.py:53(shape)\n", - " 102 0.000 0.000 0.000 0.000 construction.py:664(_sanitize_str_dtypes)\n", - " 5 0.000 0.000 0.000 0.000 {built-in method builtins.sorted}\n", - " 128 0.000 0.000 0.000 0.000 {built-in method numpy.seterrobj}\n", - " 359 0.000 0.000 0.000 0.000 {built-in method builtins.hash}\n", - " 8 0.000 0.000 0.000 0.000 warnings.py:130(filterwarnings)\n", - " 2 0.000 0.000 0.000 0.000 algorithms.py:299(_get_data_algo)\n", - " 22 0.000 0.000 0.000 0.000 managers.py:2079()\n", - " 11 0.000 0.000 0.000 0.000 base.py:6284(_maybe_cast_indexer)\n", - " 14 0.000 0.000 0.000 0.000 common.py:229(asarray_tuplesafe)\n", - " 14 0.000 0.000 0.000 0.000 {method 'any' of 'numpy.ndarray' objects}\n", - " 104 0.000 0.000 0.000 0.000 common.py:1416(is_1d_only_ea_dtype)\n", - " 31 0.000 0.000 0.000 0.000 base.py:45(__len__)\n", - " 105 0.000 0.000 0.000 0.000 flags.py:47(__init__)\n", - " 256 0.000 0.000 0.000 0.000 {built-in method numpy.geterrobj}\n", - " 6 0.000 0.000 0.000 0.000 base.py:3823(_check_indexing_method)\n", - " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(putmask)\n", - " 15 0.000 0.000 0.000 0.000 numeric.py:289(full)\n", - " 1 0.000 0.000 0.000 0.000 generic.py:1516(__invert__)\n", - " 102 0.000 0.000 0.000 0.000 construction.py:684(_maybe_repeat)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:596(_homogenize)\n", - " 8 0.000 0.000 0.000 0.000 blocks.py:166(_consolidate_key)\n", - " 96 0.000 0.000 0.000 0.000 config.py:630(_translate_key)\n", - " 23 0.000 0.000 0.000 0.000 common.py:581(is_dtype_equal)\n", - " 101 0.000 0.000 0.000 0.000 managers.py:1700(__init__)\n", - " 5 0.000 0.000 0.000 0.000 frame.py:3906(_box_col_values)\n", - " 13 0.000 0.000 0.000 0.000 base.py:5023(__getitem__)\n", - " 14 0.000 0.000 0.000 0.000 _methods.py:54(_any)\n", - " 167/166 0.000 0.000 0.000 0.000 {built-in method numpy.asarray}\n", - " 172 0.000 0.000 0.000 0.000 {built-in method builtins.hasattr}\n", - " 24 0.000 0.000 0.000 0.000 base.py:654(_simple_new)\n", - " 101 0.000 0.000 0.000 0.000 cast.py:1711(sanitize_to_nanoseconds)\n", - " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(insert)\n", - " 51 0.000 0.000 0.000 0.000 base.py:55()\n", - " 19 0.000 0.000 0.000 0.000 common.py:1240(is_float_dtype)\n", - " 16 0.000 0.000 0.000 0.000 base.py:2272(is_boolean)\n", - " 2 0.000 0.000 0.000 0.000 algorithms.py:289(_get_values_for_rank)\n", - " 2 0.000 0.000 0.000 0.000 function_base.py:5143(insert)\n", - " 11 0.000 0.000 0.000 0.000 base.py:2344(is_floating)\n", - " 72 0.000 0.000 0.000 0.000 flags.py:83(allows_duplicate_labels)\n", - " 96 0.000 0.000 0.000 0.000 config.py:571(_select_options)\n", - " 5 0.000 0.000 0.000 0.000 {method '_rebuild_blknos_and_blklocs' of 'pandas._libs.internals.BlockManager' objects}\n", - " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(setdiff1d)\n", - " 23 0.000 0.000 0.000 0.000 _dtype.py:314(_name_includes_bit_suffix)\n", - " 1 0.000 0.000 0.000 0.000 contextlib.py:72(inner)\n", - " 12 0.000 0.000 0.000 0.000 common.py:1274(is_bool_dtype)\n", - " 6 0.000 0.000 0.000 0.000 base.py:3786(_get_indexer)\n", - " 63 0.000 0.000 0.000 0.000 missing.py:138(dispatch_fill_zeros)\n", - " 6 0.000 0.000 0.000 0.000 common.py:105(is_bool_indexer)\n", - " 2 0.000 0.000 0.000 0.000 arraysetops.py:784(setdiff1d)\n", - " 5 0.000 0.000 0.000 0.000 managers.py:2137(_preprocess_slice_or_indexer)\n", - " 2 0.000 0.000 0.000 0.000 managers.py:1277(_insert_update_blklocs_and_blknos)\n", - " 4 0.000 0.000 0.000 0.000 blocks.py:238(fill_value)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:253(apply)\n", - " 2 0.000 0.000 0.000 0.000 frame.py:10808(values)\n", - " 30 0.000 0.000 0.000 0.000 common.py:1483(is_ea_or_datetimelike_dtype)\n", - " 19 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(copyto)\n", - " 15 0.000 0.000 0.000 0.000 base.py:4973(__contains__)\n", - " 209 0.000 0.000 0.000 0.000 typing.py:1149(cast)\n", - " 8 0.000 0.000 0.000 0.000 base.py:7168(unpack_nested_dtype)\n", - " 6 0.000 0.000 0.000 0.000 blocks.py:2030(new_block_2d)\n", - " 1 0.000 0.000 0.000 0.000 base.py:6947(ensure_index_from_sequences)\n", - " 96 0.000 0.000 0.000 0.000 {method 'split' of 'str' objects}\n", - " 4 0.000 0.000 0.000 0.000 missing.py:571(na_value_for_dtype)\n", - " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(in1d)\n", - " 4 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(append)\n", - " 1 0.000 0.000 0.000 0.000 indexing.py:2352(check_bool_indexer)\n", - " 6 0.000 0.000 0.000 0.000 common.py:459(is_interval_dtype)\n", - " 58 0.000 0.000 0.000 0.000 common.py:1552(get_dtype)\n", - " 13 0.000 0.000 0.000 0.000 {method 'max' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.000 0.000 blocks.py:396(apply)\n", - " 11 0.000 0.000 0.000 0.000 numerictypes.py:356(issubdtype)\n", - " 5 0.000 0.000 0.000 0.000 managers.py:1016(iget)\n", - " 59 0.000 0.000 0.000 0.000 common.py:147()\n", - " 2 0.000 0.000 0.000 0.000 arraysetops.py:519(in1d)\n", - " 31 0.000 0.000 0.000 0.000 common.py:155()\n", - " 7 0.000 0.000 0.000 0.000 common.py:680(is_integer_dtype)\n", - " 101 0.000 0.000 0.000 0.000 managers.py:1792(_block)\n", - " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(moveaxis)\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:468(__init__)\n", - " 4 0.000 0.000 0.000 0.000 function_base.py:5337(append)\n", - " 8 0.000 0.000 0.000 0.000 managers.py:1665(is_consolidated)\n", - " 5 0.000 0.000 0.000 0.000 numerictypes.py:597(find_common_type)\n", - " 6 0.000 0.000 0.000 0.000 dtypes.py:1206(is_dtype)\n", - " 1 0.000 0.000 0.000 0.000 {function SeedSequence.generate_state at 0x7fb5e410ac10}\n", - " 12 0.000 0.000 0.000 0.000 common.py:786(is_unsigned_integer_dtype)\n", - " 6 0.000 0.000 0.000 0.000 base.py:845(_engine)\n", - " 63 0.000 0.000 0.000 0.000 expressions.py:72(_can_use_numexpr)\n", - " 145 0.000 0.000 0.000 0.000 generic.py:349(flags)\n", - " 12 0.000 0.000 0.000 0.000 common.py:732(is_signed_integer_dtype)\n", - " 6 0.000 0.000 0.000 0.000 base.py:5917(_maybe_promote)\n", - " 13 0.000 0.000 0.000 0.000 _methods.py:38(_amax)\n", - " 2 0.000 0.000 0.000 0.000 numeric.py:1404(moveaxis)\n", - " 2 0.000 0.000 0.000 0.000 contextlib.py:117(__exit__)\n", - " 5 0.000 0.000 0.000 0.000 managers.py:1673(_consolidate_check)\n", - " 63 0.000 0.000 0.000 0.000 _ufunc_config.py:425(__init__)\n", - " 17 0.000 0.000 0.000 0.000 common.py:1429(is_extension_array_dtype)\n", - " 8 0.000 0.000 0.000 0.000 warnings.py:181(_add_filter)\n", - " 8 0.000 0.000 0.000 0.000 re.py:250(compile)\n", - " 34 0.000 0.000 0.000 0.000 range.py:909(__len__)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:635(_extract_index)\n", - " 2 0.000 0.000 0.000 0.000 base.py:3465(_wrap_difference_result)\n", - " 2 0.000 0.000 0.000 0.000 base.py:5799(_raise_if_missing)\n", - " 1 0.000 0.000 0.000 0.000 utils.py:457(check_array_indexer)\n", - " 6 0.000 0.000 0.000 0.000 {method 'get_indexer' of 'pandas._libs.index.IndexEngine' objects}\n", - " 1 0.000 0.000 0.000 0.000 {built-in method _operator.invert}\n", - " 2 0.000 0.000 0.000 0.000 base.py:3258(_wrap_setop_result)\n", - " 10 0.000 0.000 0.000 0.000 frame.py:1413(__len__)\n", - " 95 0.000 0.000 0.000 0.000 numeric.py:331(_is_all_dates)\n", - " 72 0.000 0.000 0.000 0.000 generic.py:328(attrs)\n", - " 62 0.000 0.000 0.000 0.000 array_ops.py:510(_bool_arith_check)\n", - " 3 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(argsort)\n", - " 75 0.000 0.000 0.000 0.000 {pandas._libs.lib.item_from_zerodim}\n", - " 2 0.000 0.000 0.000 0.000 algorithms.py:308(_check_object_for_strings)\n", - " 2 0.000 0.000 0.000 0.000 algorithms.py:193(_reconstruct_data)\n", - " 8 0.000 0.000 0.000 0.000 re.py:289(_compile)\n", - " 22 0.000 0.000 0.000 0.000 numerictypes.py:282(issubclass_)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:487()\n", - " 15 0.000 0.000 0.000 0.000 :1017(_handle_fromlist)\n", - " 3 0.000 0.000 0.000 0.000 numeric.py:139(_ensure_array)\n", - " 4 0.000 0.000 0.000 0.000 numeric.py:149(ones)\n", - " 15 0.000 0.000 0.000 0.000 managers.py:156(blknos)\n", - " 5 0.000 0.000 0.000 0.000 common.py:97(_maybe_match_name)\n", - " 3 0.000 0.000 0.000 0.000 _asarray.py:22(require)\n", - " 4 0.000 0.000 0.000 0.000 base.py:118(_reset_cache)\n", - " 6 0.000 0.000 0.000 0.000 indexing.py:2318(convert_to_index_sliceable)\n", - " 2 0.000 0.000 0.000 0.000 generic.py:1991(__contains__)\n", - " 2 0.000 0.000 0.000 0.000 managers.py:1541(as_array)\n", - " 4 0.000 0.000 0.000 0.000 common.py:348(is_datetime64tz_dtype)\n", - " 12 0.000 0.000 0.000 0.000 base.py:4846(_get_engine_target)\n", - " 2 0.000 0.000 0.000 0.000 managers.py:1267(_insert_update_mgr_locs)\n", - " 13 0.000 0.000 0.000 0.000 missing.py:390(array_equivalent)\n", - " 2 0.000 0.000 0.000 0.000 groupby.py:985(_reset_group_selection)\n", - " 73 0.000 0.000 0.000 0.000 flags.py:51(allows_duplicate_labels)\n", - " 11 0.000 0.000 0.000 0.000 inference.py:184(is_array_like)\n", - " 18 0.000 0.000 0.000 0.000 {built-in method numpy.array}\n", - " 1 0.000 0.000 0.000 0.000 construction.py:483()\n", - " 4 0.000 0.000 0.000 0.000 base.py:2232(is_unique)\n", - " 21 0.000 0.000 0.000 0.000 common.py:160(cast_scalar_indexer)\n", - " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(prod)\n", - " 9 0.000 0.000 0.000 0.000 indexing.py:2481(check_deprecated_indexers)\n", - " 9 0.000 0.000 0.000 0.000 blocks.py:2119(extend_blocks)\n", - " 64 0.000 0.000 0.000 0.000 series.py:523(_constructor)\n", - " 60 0.000 0.000 0.000 0.000 fromnumeric.py:89(_take_dispatcher)\n", - " 11 0.000 0.000 0.000 0.000 {method 'get_loc' of 'pandas._libs.index.IndexEngine' objects}\n", - " 4 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(ravel)\n", - " 2 0.000 0.000 0.000 0.000 base.py:3560(_convert_can_do_setop)\n", - " 3 0.000 0.000 0.000 0.000 fromnumeric.py:1012(argsort)\n", - " 4 0.000 0.000 0.000 0.000 common.py:287(maybe_iterable_to_list)\n", - " 59 0.000 0.000 0.000 0.000 common.py:145(classes)\n", - " 4 0.000 0.000 0.000 0.000 numeric.py:1341(normalize_axis_tuple)\n", - " 9 0.000 0.000 0.000 0.000 base.py:2624(_is_multi)\n", - " 30 0.000 0.000 0.000 0.000 fromnumeric.py:1123(_argmax_dispatcher)\n", - " 3 0.000 0.000 0.000 0.000 cast.py:1960(construct_1d_object_array_from_listlike)\n", - " 74 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}\n", - " 1 0.000 0.000 0.000 0.000 {method 'delete' of 'pandas._libs.internals.BlockPlacement' objects}\n", - " 3 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(atleast_2d)\n", - " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2970(prod)\n", - " 8 0.000 0.000 0.000 0.000 warnings.py:458(__enter__)\n", - " 5 0.000 0.000 0.000 0.000 managers.py:1679()\n", - " 2 0.000 0.000 0.000 0.000 {method 'sum' of 'numpy.ndarray' objects}\n", - " 5 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(ndim)\n", - " 9 0.000 0.000 0.000 0.000 series.py:575(dtype)\n", - " 5 0.000 0.000 0.000 0.000 series.py:1238(_set_as_cached)\n", - " 11 0.000 0.000 0.000 0.000 generic.py:560(_get_axis)\n", - " 37 0.000 0.000 0.000 0.000 managers.py:222(items)\n", - " 11 0.000 0.000 0.000 0.000 abc.py:96(__instancecheck__)\n", - " 1 0.000 0.000 0.000 0.000 fromnumeric.py:69(_wrapreduction)\n", - " 9 0.000 0.000 0.000 0.000 blocks.py:332(getitem_block_columns)\n", - " 1 0.000 0.000 0.000 0.000 blocks.py:427(_split_op_result)\n", - " 2 0.000 0.000 0.000 0.000 generic.py:1752(_check_label_or_level_ambiguity)\n", - " 2 0.000 0.000 0.000 0.000 _methods.py:46(_sum)\n", - " 13 0.000 0.000 0.000 0.000 base.py:803(is_)\n", - " 46 0.000 0.000 0.000 0.000 {pandas._libs.algos.ensure_platform_int}\n", - " 19 0.000 0.000 0.000 0.000 take.py:326(_get_take_nd_function)\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:925(_convert_grouper)\n", - " 1 0.000 0.000 0.000 0.000 blocks.py:252(make_block)\n", - " 1 0.000 0.000 0.000 0.000 generic.py:767(_set_axis)\n", - " 3 0.000 0.000 0.000 0.000 blocks.py:2135(ensure_block_shape)\n", - " 3 0.000 0.000 0.000 0.000 {built-in method builtins.any}\n", - " 25 0.000 0.000 0.000 0.000 base.py:834(_reset_identity)\n", - " 2 0.000 0.000 0.000 0.000 managers.py:2128(_fast_count_smallints)\n", - " 3 0.000 0.000 0.000 0.000 shape_base.py:81(atleast_2d)\n", - " 26 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x908780}\n", - " 7 0.000 0.000 0.000 0.000 blocks.py:267(make_block_same_class)\n", - " 12 0.000 0.000 0.000 0.000 common.py:346(apply_if_callable)\n", - " 4 0.000 0.000 0.000 0.000 fromnumeric.py:1755(ravel)\n", - " 2 0.000 0.000 0.000 0.000 missing.py:625(is_valid_na_for_dtype)\n", - " 8 0.000 0.000 0.000 0.000 warnings.py:477(__exit__)\n", - " 36 0.000 0.000 0.000 0.000 base.py:1650(name)\n", - " 9 0.000 0.000 0.000 0.000 cast.py:468(maybe_promote)\n", - " 2 0.000 0.000 0.000 0.000 base.py:2844(unique)\n", - " 3 0.000 0.000 0.000 0.000 missing.py:66(isna)\n", - " 9 0.000 0.000 0.000 0.000 managers.py:1837(dtype)\n", - " 1 0.000 0.000 0.000 0.000 frame.py:821(shape)\n", - " 1 0.000 0.000 0.000 0.000 random.py:721(getrandbits)\n", - " 23 0.000 0.000 0.000 0.000 _dtype.py:24(_kind_name)\n", - " 8 0.000 0.000 0.000 0.000 {method 'remove' of 'list' objects}\n", - " 8 0.000 0.000 0.000 0.000 types.py:171(__get__)\n", - " 9 0.000 0.000 0.000 0.000 generic.py:636(_info_axis)\n", - " 2 0.000 0.000 0.000 0.000 groupby.py:1734(_obj_1d_constructor)\n", - " 9 0.000 0.000 0.000 0.000 range.py:522(equals)\n", - " 6 0.000 0.000 0.000 0.000 {method 'reshape' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.000 0.000 generic.py:1399(_indexed_same)\n", - " 10 0.000 0.000 0.000 0.000 numerictypes.py:573(_can_coerce_all)\n", - " 11 0.000 0.000 0.000 0.000 {method 'format' of 'str' objects}\n", - " 4 0.000 0.000 0.000 0.000 managers.py:1992(_grouping_func)\n", - " 3 0.000 0.000 0.000 0.000 managers.py:2102()\n", - " 31 0.000 0.000 0.000 0.000 common.py:150(classes_and_not_datetimelike)\n", - " 2 0.000 0.000 0.000 0.000 base.py:4931(_validate_fill_value)\n", - " 18 0.000 0.000 0.000 0.000 generic.py:546(_get_axis_number)\n", - " 2 0.000 0.000 0.000 0.000 base.py:785(_view)\n", - " 3 0.000 0.000 0.000 0.000 missing.py:149(_isna)\n", - " 13 0.000 0.000 0.000 0.000 {built-in method builtins.max}\n", - " 1 0.000 0.000 0.000 0.000 base.py:841(_cleanup)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:212(set_axis)\n", - " 2 0.000 0.000 0.000 0.000 base.py:4226(_wrap_reindex_result)\n", - " 2 0.000 0.000 0.000 0.000 {built-in method builtins.sum}\n", - " 2 0.000 0.000 0.000 0.000 contextlib.py:238(helper)\n", - " 3 0.000 0.000 0.000 0.000 base.py:358(size)\n", - " 6 0.000 0.000 0.000 0.000 missing.py:911(clean_reindex_fill_method)\n", - " 8 0.000 0.000 0.000 0.000 warnings.py:437(__init__)\n", - " 9 0.000 0.000 0.000 0.000 {pandas._libs.lib.maybe_indices_to_slice}\n", - " 24 0.000 0.000 0.000 0.000 {built-in method _warnings._filters_mutated}\n", - " 7 0.000 0.000 0.000 0.000 managers.py:217(is_single_block)\n", - " 3 0.000 0.000 0.000 0.000 managers.py:973(from_blocks)\n", - " 3 0.000 0.000 0.000 0.000 frame.py:3883(_ensure_valid_index)\n", - " 2 0.000 0.000 0.000 0.000 base.py:768(_shallow_copy)\n", - " 2 0.000 0.000 0.000 0.000 groupby.py:977()\n", - " 4 0.000 0.000 0.000 0.000 common.py:1747(pandas_dtype)\n", - " 11 0.000 0.000 0.000 0.000 {built-in method _abc._abc_instancecheck}\n", - " 24 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_float}\n", - " 2 0.000 0.000 0.000 0.000 generic.py:1401()\n", - " 1 0.000 0.000 0.000 0.000 api.py:322(default_index)\n", - " 4 0.000 0.000 0.000 0.000 frame.py:3920(_clear_item_cache)\n", - " 3 0.000 0.000 0.000 0.000 generic.py:3925(_set_is_copy)\n", - " 4 0.000 0.000 0.000 0.000 common.py:1148(needs_i8_conversion)\n", - " 3 0.000 0.000 0.000 0.000 _asarray.py:111()\n", - " 3 0.000 0.000 0.000 0.000 shape_base.py:218(_vhstack_dispatcher)\n", - " 19 0.000 0.000 0.000 0.000 multiarray.py:1071(copyto)\n", - " 1 0.000 0.000 0.000 0.000 base.py:57(_validate_set_axis)\n", - " 3 0.000 0.000 0.000 0.000 base.py:2642(_na_value)\n", - " 16 0.000 0.000 0.000 0.000 base.py:937(dtype)\n", - " 2 0.000 0.000 0.000 0.000 base.py:4230(_maybe_preserve_names)\n", - " 13 0.000 0.000 0.000 0.000 blocks.py:354(dtype)\n", - " 2 0.000 0.000 0.000 0.000 algorithms.py:237(_ensure_arraylike)\n", - " 2 0.000 0.000 0.000 0.000 cast.py:2147(can_hold_element)\n", - " 6 0.000 0.000 0.000 0.000 {pandas._libs.internals.get_blkno_placements}\n", - " 1 0.000 0.000 0.000 0.000 {built-in method posix.urandom}\n", - " 15 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_scalar}\n", - " 17 0.000 0.000 0.000 0.000 multiarray.py:148(concatenate)\n", - " 9 0.000 0.000 0.000 0.000 blocks.py:310(_slice)\n", - " 2 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(bincount)\n", - " 2 0.000 0.000 0.000 0.000 base.py:3554(_assert_can_do_setop)\n", - " 19 0.000 0.000 0.000 0.000 base.py:326(ndim)\n", - " 3 0.000 0.000 0.000 0.000 generic.py:566(_get_block_manager_axis)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:494()\n", - " 14 0.000 0.000 0.000 0.000 base.py:5905(_index_as_unique)\n", - " 3 0.000 0.000 0.000 0.000 {built-in method numpy.zeros}\n", - " 2 0.000 0.000 0.000 0.000 contextlib.py:82(__init__)\n", - " 14 0.000 0.000 0.000 0.000 managers.py:172(blklocs)\n", - " 5 0.000 0.000 0.000 0.000 numerictypes.py:649()\n", - " 10 0.000 0.000 0.000 0.000 {method 'insert' of 'list' objects}\n", - " 2 0.000 0.000 0.000 0.000 base.py:723(tolist)\n", - " 2 0.000 0.000 0.000 0.000 blocks.py:222(get_values)\n", - " 4 0.000 0.000 0.000 0.000 {method 'transpose' of 'numpy.ndarray' objects}\n", - " 15 0.000 0.000 0.000 0.000 {built-in method builtins.callable}\n", - " 1 0.000 0.000 0.000 0.000 range.py:167(_simple_new)\n", - " 5 0.000 0.000 0.000 0.000 blocks.py:358(iget)\n", - " 4 0.000 0.000 0.000 0.000 numeric.py:1391()\n", - " 6 0.000 0.000 0.000 0.000 base.py:518()\n", - " 14 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_integer}\n", - " 3 0.000 0.000 0.000 0.000 grouper.py:802()\n", - " 15 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_iterator}\n", - " 4 0.000 0.000 0.000 0.000 {method 'view' of 'numpy.ndarray' objects}\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:570(name)\n", - " 10 0.000 0.000 0.000 0.000 {method 'ravel' of 'numpy.ndarray' objects}\n", - " 9 0.000 0.000 0.000 0.000 base.py:540(_ensure_array)\n", - " 2 0.000 0.000 0.000 0.000 groupby.py:898(__getattr__)\n", - " 10 0.000 0.000 0.000 0.000 {method 'index' of 'list' objects}\n", - " 12 0.000 0.000 0.000 0.000 base.py:229(disallow_kwargs)\n", - " 9 0.000 0.000 0.000 0.000 blocks.py:244(mgr_locs)\n", - " 1 0.000 0.000 0.000 0.000 typing.py:868(__new__)\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:838(is_in_axis)\n", - " 3 0.000 0.000 0.000 0.000 shape_base.py:207(_arrays_for_stack_dispatcher)\n", - " 8 0.000 0.000 0.000 0.000 {built-in method numpy.core._multiarray_umath.normalize_axis_index}\n", - " 1 0.000 0.000 0.000 0.000 managers.py:1722(from_blocks)\n", - " 2 0.000 0.000 0.000 0.000 base.py:1701(_get_names)\n", - " 9 0.000 0.000 0.000 0.000 managers.py:919(__init__)\n", - " 9 0.000 0.000 0.000 0.000 managers.py:1257()\n", - " 8 0.000 0.000 0.000 0.000 enum.py:753(value)\n", - " 8 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}\n", - " 1 0.000 0.000 0.000 0.000 pipeline.py:140(get_rn_generator)\n", - " 2 0.000 0.000 0.000 0.000 range.py:347(dtype)\n", - " 5 0.000 0.000 0.000 0.000 frame.py:804(axes)\n", - " 3 0.000 0.000 0.000 0.000 {built-in method pandas._libs.missing.checknull}\n", - " 1 0.000 0.000 0.000 0.000 common.py:1042(is_numeric_v_string_like)\n", - " 5 0.000 0.000 0.000 0.000 {method 'clear' of 'dict' objects}\n", - " 1 0.000 0.000 0.000 0.000 sorting.py:148()\n", - " 1 0.000 0.000 0.000 0.000 construction.py:486()\n", - " 3 0.000 0.000 0.000 0.000 grouper.py:804()\n", - " 5 0.000 0.000 0.000 0.000 generic.py:660(ndim)\n", - " 7 0.000 0.000 0.000 0.000 ops.py:680(groupings)\n", - " 5 0.000 0.000 0.000 0.000 fromnumeric.py:3164(ndim)\n", - " 3 0.000 0.000 0.000 0.000 grouper.py:803()\n", - " 1 0.000 0.000 0.000 0.000 ops.py:660(__init__)\n", - " 6 0.000 0.000 0.000 0.000 missing.py:107(clean_fill_method)\n", - " 1 0.000 0.000 0.000 0.000 sorting.py:135(_int64_cut_off)\n", - " 5 0.000 0.000 0.000 0.000 {pandas._libs.algos.ensure_int64}\n", - " 1 0.000 0.000 0.000 0.000 generic.py:4060(_check_inplace_and_allows_duplicate_labels)\n", - " 8 0.000 0.000 0.000 0.000 base.py:6022(_is_comparable_dtype)\n", - " 2 0.000 0.000 0.000 0.000 base.py:7046(ensure_has_len)\n", - " 5 0.000 0.000 0.000 0.000 base.py:692(_constructor)\n", - " 2 0.000 0.000 0.000 0.000 common.py:315(is_datetime64_dtype)\n", - " 5 0.000 0.000 0.000 0.000 numerictypes.py:650()\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:921(_is_label_like)\n", - " 5 0.000 0.000 0.000 0.000 base.py:1124(_maybe_disallow_fill)\n", - " 1 0.000 0.000 0.000 0.000 _decorators.py:214(_format_argument_list)\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:851(is_in_obj)\n", - " 2 0.000 0.000 0.000 0.000 common.py:389(is_timedelta64_dtype)\n", - " 1 0.000 0.000 0.000 0.000 base.py:890(__array__)\n", - " 5 0.000 0.000 0.000 0.000 fromnumeric.py:3160(_ndim_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 _validators.py:218(validate_bool_kwarg)\n", - " 3 0.000 0.000 0.000 0.000 generic.py:3948(_check_setitem_copy)\n", - " 3 0.000 0.000 0.000 0.000 fromnumeric.py:432(_repeat_dispatcher)\n", - " 3 0.000 0.000 0.000 0.000 {method 'upper' of 'str' objects}\n", - " 1 0.000 0.000 0.000 0.000 construction.py:233(mgr_to_mgr)\n", - " 3 0.000 0.000 0.000 0.000 managers.py:2111()\n", - " 4 0.000 0.000 0.000 0.000 fromnumeric.py:1751(_ravel_dispatcher)\n", - " 4 0.000 0.000 0.000 0.000 {built-in method _operator.index}\n", - " 3 0.000 0.000 0.000 0.000 function.py:49(__call__)\n", - " 3 0.000 0.000 0.000 0.000 frame.py:578(_constructor)\n", - " 2 0.000 0.000 0.000 0.000 cast.py:2185(np_can_hold_element)\n", - " 4 0.000 0.000 0.000 0.000 function_base.py:5333(_append_dispatcher)\n", - " 3 0.000 0.000 0.000 0.000 {method 'pop' of 'dict' objects}\n", - " 3 0.000 0.000 0.000 0.000 numeric.py:199(_ensure_dtype)\n", - " 1 0.000 0.000 0.000 0.000 pipeline.py:72(rng)\n", - " 3 0.000 0.000 0.000 0.000 numeric.py:188(_validate_dtype)\n", - " 1 0.000 0.000 0.000 0.000 {built-in method pandas._libs.missing.is_matching_na}\n", - " 3 0.000 0.000 0.000 0.000 fromnumeric.py:1008(_argsort_dispatcher)\n", - " 2 0.000 0.000 0.000 0.000 base.py:3050(_validate_sort_keyword)\n", - " 3 0.000 0.000 0.000 0.000 shape_base.py:77(_atleast_2d_dispatcher)\n", - " 2 0.000 0.000 0.000 0.000 {method 'tolist' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.000 0.000 blocks.py:306(__len__)\n", - " 2 0.000 0.000 0.000 0.000 {method 'item' of 'numpy.ndarray' objects}\n", - " 2 0.000 0.000 0.000 0.000 shape_base.py:1243()\n", - " 2 0.000 0.000 0.000 0.000 shape_base.py:1253()\n", - " 2 0.000 0.000 0.000 0.000 grouper.py:588(_ilevel)\n", - " 2 0.000 0.000 0.000 0.000 function_base.py:1291(_diff_dispatcher)\n", - " 2 0.000 0.000 0.000 0.000 numeric.py:1466()\n", - " 1 0.000 0.000 0.000 0.000 managers.py:1118(value_getitem)\n", - " 1 0.000 0.000 0.000 0.000 range.py:241(start)\n", - " 2 0.000 0.000 0.000 0.000 generic.py:1772()\n", - " 2 0.000 0.000 0.000 0.000 base.py:7191(_maybe_try_sort)\n", - " 2 0.000 0.000 0.000 0.000 function_base.py:5139(_insert_dispatcher)\n", - " 2 0.000 0.000 0.000 0.000 fromnumeric.py:1862(_nonzero_dispatcher)\n", - " 2 0.000 0.000 0.000 0.000 multiarray.py:883(bincount)\n", - " 1 0.000 0.000 0.000 0.000 range.py:287(step)\n", - " 2 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_bool_list}\n", - " 2 0.000 0.000 0.000 0.000 arraysetops.py:515(_in1d_dispatcher)\n", - " 2 0.000 0.000 0.000 0.000 arraysetops.py:780(_setdiff1d_dispatcher)\n", - " 2 0.000 0.000 0.000 0.000 function_base.py:4954(_delete_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 fromnumeric.py:70()\n", - " 2 0.000 0.000 0.000 0.000 {method 'add' of 'set' objects}\n", - " 1 0.000 0.000 0.000 0.000 {built-in method from_bytes}\n", - " 1 0.000 0.000 0.000 0.000 blocks.py:248(mgr_locs)\n", - " 2 0.000 0.000 0.000 0.000 base.py:1898(nlevels)\n", - " 2 0.000 0.000 0.000 0.000 numeric.py:1400(_moveaxis_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 multiarray.py:1106(putmask)\n", - " 1 0.000 0.000 0.000 0.000 {method 'keys' of 'dict' objects}\n", - " 1 0.000 0.000 0.000 0.000 contextlib.py:59(_recreate_cm)\n", - " 1 0.000 0.000 0.000 0.000 {method 'items' of 'dict' objects}\n", - " 1 0.000 0.000 0.000 0.000 {method 'append' of 'pandas._libs.internals.BlockPlacement' objects}\n", - " 1 0.000 0.000 0.000 0.000 range.py:183(_constructor)\n", - " 1 0.000 0.000 0.000 0.000 range.py:264(stop)\n", - " 1 0.000 0.000 0.000 0.000 shape_base.py:1167(_tile_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 fromnumeric.py:2965(_prod_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 base.py:4786(values)\n", - " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", - " 1 0.000 0.000 0.000 0.000 managers.py:282()\n", - " 1 0.000 0.000 0.000 0.000 {method 'clear_mapping' of 'pandas._libs.index.IndexEngine' objects}\n", - " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_bool}" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%prun -s cumulative run_previous()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35a9df01-2b32-41c3-b9e2-259b21f214c6", - "metadata": {}, - "outputs": [], - "source": [ - "37642 function calls (37233 primitive calls) in 3.187 seconds\n", - "406159 function calls (406079 primitive calls) in 60.697 seconds\n", - "\n", - " Ordered by: cumulative time\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 1 0.000 0.000 60.697 60.697 {built-in method builtins.exec}\n", - " 1 0.000 0.000 60.697 60.697 :1()\n", - " 1 0.033 0.033 60.697 60.697 2292568830.py:1(run_fru)\n", - " 1 37.147 37.147 60.664 60.664 3636660809.py:1(make_sample_choices_dev)\n", - " 30 0.745 0.025 22.155 0.738 random.py:562(random_for_df)\n", - " 30 0.192 0.006 13.381 0.446 random.py:598()\n", - " 404130 13.189 0.000 13.189 0.000 {method 'rand' of 'numpy.random.mtrand.RandomState' objects}\n", - " 32 8.025 0.251 8.025 0.251 {built-in method numpy.asanyarray}\n", - " 70/66 0.128 0.002 1.361 0.021 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", - " 32 0.000 0.000 1.232 0.039 fromnumeric.py:51(_wrapfunc)\n", - " 30 0.000 0.000 1.104 0.037 <__array_function__ internals>:177(argmax)\n", - " 30 0.000 0.000 1.103 0.037 fromnumeric.py:1127(argmax)\n", - " 30 1.103 0.037 1.103 0.037 {method 'argmax' of 'numpy.ndarray' objects}\n", - " \n", - "w/o random calls (test where we simply take max(obs_utlils) but everything else is identical)\n", - " 1039 function calls (1019 primitive calls) in 1.403 seconds\n", - " Ordered by: cumulative time\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 1 0.000 0.000 1.403 1.403 {built-in method builtins.exec}\n", - " 1 0.000 0.000 1.403 1.403 :1()\n", - " 1 0.000 0.000 1.403 1.403 2292568830.py:1(run_fru)\n", - " 1 0.021 0.021 1.403 1.403 691721383.py:1(make_sample_choices_dev)\n", - " 40/36 0.120 0.003 1.380 0.038 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", - " 32 0.000 0.000 1.260 0.039 fromnumeric.py:51(_wrapfunc)\n", - " 30 0.000 0.000 1.134 0.038 <__array_function__ internals>:177(argmax)\n", - " 30 0.000 0.000 1.133 0.038 fromnumeric.py:1127(argmax)\n", - " 30 1.133 0.038 1.133 0.038 {method 'argmax' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.127 0.127 <__array_function__ internals>:177(nonzero)\n", - " 1 0.000 0.000 0.127 0.127 fromnumeric.py:1866(nonzero)\n", - " 1 0.127 0.127 0.127 0.127 {method 'nonzero' of 'numpy.ndarray' objects}" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "082ecf8f-bf23-4a61-be4f-a0d914469f18", - "metadata": { - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " " - ] - }, - { - "data": { - "text/plain": [ - " 406159 function calls (406079 primitive calls) in 61.192 seconds\n", - "\n", - " Ordered by: cumulative time\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 1 0.000 0.000 61.192 61.192 {built-in method builtins.exec}\n", - " 1 0.000 0.000 61.192 61.192 :1()\n", - " 1 0.027 0.027 61.192 61.192 2292568830.py:1(run_fru)\n", - " 1 37.470 37.470 61.165 61.165 1111942506.py:1(make_sample_choices_dev)\n", - " 30 0.778 0.026 22.305 0.744 random.py:562(random_for_df)\n", - " 30 0.195 0.006 13.419 0.447 random.py:598()\n", - " 404130 13.225 0.000 13.225 0.000 {method 'rand' of 'numpy.random.mtrand.RandomState' objects}\n", - " 32 8.104 0.253 8.104 0.253 {built-in method numpy.asanyarray}\n", - " 70/66 0.122 0.002 1.388 0.021 {built-in method numpy.core._multiarray_umath.implement_array_function}\n", - " 32 0.000 0.000 1.265 0.040 fromnumeric.py:51(_wrapfunc)\n", - " 30 0.000 0.000 1.129 0.038 <__array_function__ internals>:177(argmax)\n", - " 30 0.000 0.000 1.128 0.038 fromnumeric.py:1127(argmax)\n", - " 30 1.128 0.038 1.128 0.038 {method 'argmax' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.137 0.137 <__array_function__ internals>:177(nonzero)\n", - " 1 0.000 0.000 0.137 0.137 fromnumeric.py:1866(nonzero)\n", - " 1 0.137 0.137 0.137 0.137 {method 'nonzero' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.122 0.122 <__array_function__ internals>:177(zeros_like)\n", - " 1 0.000 0.000 0.122 0.122 numeric.py:76(zeros_like)\n", - " 2 0.000 0.000 0.122 0.061 <__array_function__ internals>:177(copyto)\n", - " 30 0.000 0.000 0.003 0.000 contextlib.py:72(inner)\n", - " 30 0.002 0.000 0.002 0.000 {function SeedSequence.generate_state at 0x7fb5e410ac10}\n", - " 1 0.000 0.000 0.001 0.001 frame.py:587(__init__)\n", - " 1 0.000 0.000 0.001 0.001 construction.py:425(dict_to_mgr)\n", - " 1 0.000 0.000 0.001 0.001 construction.py:102(arrays_to_mgr)\n", - " 60 0.000 0.000 0.001 0.000 _ufunc_config.py:32(seterr)\n", - " 30 0.000 0.000 0.000 0.000 random.py:721(getrandbits)\n", - " 30 0.000 0.000 0.000 0.000 _ufunc_config.py:429(__enter__)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:1937(create_block_manager_from_column_arrays)\n", - " 158/83 0.000 0.000 0.000 0.000 {built-in method builtins.len}\n", - " 32 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(concatenate)\n", - " 30 0.000 0.000 0.000 0.000 {built-in method posix.urandom}\n", - " 2/1 0.000 0.000 0.000 0.000 base.py:397(__new__)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:1683(_consolidate_inplace)\n", - " 32 0.000 0.000 0.000 0.000 frame.py:1413(__len__)\n", - " 30 0.000 0.000 0.000 0.000 _ufunc_config.py:434(__exit__)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:2074(_consolidate)\n", - " 60 0.000 0.000 0.000 0.000 _ufunc_config.py:131(geterr)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:2008(_form_blocks)\n", - " 190 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}\n", - " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(empty_like)\n", - " 5 0.000 0.000 0.000 0.000 construction.py:470(sanitize_array)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:596(_homogenize)\n", - " 38 0.000 0.000 0.000 0.000 abc.py:96(__instancecheck__)\n", - " 30 0.000 0.000 0.000 0.000 pipeline.py:140(get_rn_generator)\n", - " 3 0.000 0.000 0.000 0.000 managers.py:2091(_merge_blocks)\n", - " 60 0.000 0.000 0.000 0.000 {built-in method numpy.seterrobj}\n", - " 2 0.000 0.000 0.000 0.000 frame.py:1693(to_numpy)\n", - " 36 0.000 0.000 0.000 0.000 base.py:884(__len__)\n", - " 1 0.000 0.000 0.000 0.000 {built-in method builtins.sorted}\n", - " 8 0.000 0.000 0.000 0.000 managers.py:2079()\n", - " 38 0.000 0.000 0.000 0.000 {built-in method _abc._abc_instancecheck}\n", - " 120 0.000 0.000 0.000 0.000 {built-in method numpy.geterrobj}\n", - " 1 0.000 0.000 0.000 0.000 base.py:7106(_maybe_cast_data_without_dtype)\n", - " 4 0.000 0.000 0.000 0.000 blocks.py:166(_consolidate_key)\n", - " 88 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}\n", - " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.maybe_convert_objects}\n", - " 4 0.000 0.000 0.000 0.000 managers.py:2060(_stack_arrays)\n", - " 3 0.000 0.000 0.000 0.000 generic.py:5646(_consolidate_inplace)\n", - " 4 0.000 0.000 0.000 0.000 _dtype.py:328(_name_get)\n", - " 3 0.000 0.000 0.000 0.000 generic.py:5632(_protect_consolidate)\n", - " 41 0.000 0.000 0.000 0.000 generic.py:43(_check)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:635(_extract_index)\n", - " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(vstack)\n", - " 2 0.000 0.000 0.000 0.000 chunk.py:599(log_df)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:487()\n", - " 2 0.000 0.000 0.000 0.000 chunk.py:140(chunk_training_mode)\n", - " 1 0.000 0.000 0.000 0.000 generic.py:2063(__array__)\n", - " 4 0.000 0.000 0.000 0.000 common.py:348(is_datetime64tz_dtype)\n", - " 2 0.000 0.000 0.000 0.000 frame.py:821(shape)\n", - " 1 0.000 0.000 0.000 0.000 shape_base.py:222(vstack)\n", - " 5 0.000 0.000 0.000 0.000 construction.py:379(extract_array)\n", - " 2 0.000 0.000 0.000 0.000 managers.py:1541(as_array)\n", - " 4 0.000 0.000 0.000 0.000 _asarray.py:22(require)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:483()\n", - " 2 0.000 0.000 0.000 0.000 common.py:229(asarray_tuplesafe)\n", - " 1 0.000 0.000 0.000 0.000 frame.py:898(_values)\n", - " 5 0.000 0.000 0.000 0.000 construction.py:695(_try_cast)\n", - " 4 0.000 0.000 0.000 0.000 _dtype.py:314(_name_includes_bit_suffix)\n", - " 2 0.000 0.000 0.000 0.000 config.py:108(setting)\n", - " 4 0.000 0.000 0.000 0.000 base.py:286(is_dtype)\n", - " 4 0.000 0.000 0.000 0.000 common.py:287(maybe_iterable_to_list)\n", - " 1 0.000 0.000 0.000 0.000 config.py:255(__call__)\n", - " 3 0.000 0.000 0.000 0.000 generic.py:5650(f)\n", - " 1 0.000 0.000 0.000 0.000 config.py:127(_get_option)\n", - " 30 0.000 0.000 0.000 0.000 {built-in method from_bytes}\n", - " 30 0.000 0.000 0.000 0.000 fromnumeric.py:1123(_argmax_dispatcher)\n", - " 30 0.000 0.000 0.000 0.000 pipeline.py:72(rng)\n", - " 4 0.000 0.000 0.000 0.000 managers.py:1665(is_consolidated)\n", - " 4 0.000 0.000 0.000 0.000 numerictypes.py:356(issubdtype)\n", - " 1 0.000 0.000 0.000 0.000 {built-in method numpy.arange}\n", - " 1 0.000 0.000 0.000 0.000 {method '_rebuild_blknos_and_blklocs' of 'pandas._libs.internals.BlockManager' objects}\n", - " 1 0.000 0.000 0.000 0.000 base.py:554(_dtype_to_subclass)\n", - " 5 0.000 0.000 0.000 0.000 blocks.py:1989(get_block_type)\n", - " 32 0.000 0.000 0.000 0.000 multiarray.py:148(concatenate)\n", - " 1 0.000 0.000 0.000 0.000 numeric.py:289(full)\n", - " 2 0.000 0.000 0.000 0.000 inject.py:121(get_injectable)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:1673(_consolidate_check)\n", - " 2 0.000 0.000 0.000 0.000 base.py:7082(maybe_extract_name)\n", - " 5 0.000 0.000 0.000 0.000 common.py:1587(_is_dtype_type)\n", - " 1 0.000 0.000 0.000 0.000 config.py:109(_get_single_key)\n", - " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(argsort)\n", - " 4 0.000 0.000 0.000 0.000 common.py:552(require_length_match)\n", - " 1 0.000 0.000 0.000 0.000 base.py:654(_simple_new)\n", - " 6 0.000 0.000 0.000 0.000 {built-in method numpy.empty}\n", - " 5 0.000 0.000 0.000 0.000 construction.py:627(_sanitize_ndim)\n", - " 2 0.000 0.000 0.000 0.000 common.py:1240(is_float_dtype)\n", - " 7 0.000 0.000 0.000 0.000 range.py:909(__len__)\n", - " 32 0.000 0.000 0.000 0.000 {built-in method builtins.issubclass}\n", - " 1 0.000 0.000 0.000 0.000 cast.py:1960(construct_1d_object_array_from_listlike)\n", - " 30 0.000 0.000 0.000 0.000 contextlib.py:59(_recreate_cm)\n", - " 4 0.000 0.000 0.000 0.000 managers.py:1992(_grouping_func)\n", - " 1 0.000 0.000 0.000 0.000 blocks.py:2030(new_block_2d)\n", - " 8 0.000 0.000 0.000 0.000 numerictypes.py:282(issubclass_)\n", - " 1 0.000 0.000 0.000 0.000 fromnumeric.py:1012(argsort)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:1679()\n", - " 1 0.000 0.000 0.000 0.000 base.py:53(shape)\n", - " 1 0.000 0.000 0.000 0.000 <__array_function__ internals>:177(atleast_2d)\n", - " 6 0.000 0.000 0.000 0.000 {built-in method numpy.array}\n", - " 2 0.000 0.000 0.000 0.000 orca.py:1610(get_injectable)\n", - " 3 0.000 0.000 0.000 0.000 managers.py:618(consolidate)\n", - " 3 0.000 0.000 0.000 0.000 blocks.py:2119(extend_blocks)\n", - " 1 0.000 0.000 0.000 0.000 api.py:322(default_index)\n", - " 1 0.000 0.000 0.000 0.000 generic.py:239(__init__)\n", - " 4 0.000 0.000 0.000 0.000 common.py:1483(is_ea_or_datetimelike_dtype)\n", - " 4 0.000 0.000 0.000 0.000 _asarray.py:111()\n", - " 3 0.000 0.000 0.000 0.000 generic.py:5577(__setattr__)\n", - " 3 0.000 0.000 0.000 0.000 base.py:55()\n", - " 1 0.000 0.000 0.000 0.000 construction.py:494()\n", - " 2 0.000 0.000 0.000 0.000 :1017(_handle_fromlist)\n", - " 2 0.000 0.000 0.000 0.000 blocks.py:222(get_values)\n", - " 5 0.000 0.000 0.000 0.000 construction.py:664(_sanitize_str_dtypes)\n", - " 5 0.000 0.000 0.000 0.000 {built-in method numpy.asarray}\n", - " 17 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}\n", - " 1 0.000 0.000 0.000 0.000 config.py:589(_get_root)\n", - " 1 0.000 0.000 0.000 0.000 common.py:786(is_unsigned_integer_dtype)\n", - " 3 0.000 0.000 0.000 0.000 construction.py:438(ensure_wrapped_if_datetimelike)\n", - " 1 0.000 0.000 0.000 0.000 blocks.py:1960(maybe_coerce_values)\n", - " 1 0.000 0.000 0.000 0.000 shape_base.py:81(atleast_2d)\n", - " 1 0.000 0.000 0.000 0.000 {method 'argsort' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.000 0.000 range.py:167(_simple_new)\n", - " 5 0.000 0.000 0.000 0.000 construction.py:684(_maybe_repeat)\n", - " 14 0.000 0.000 0.000 0.000 typing.py:1149(cast)\n", - " 1 0.000 0.000 0.000 0.000 common.py:161(is_object_dtype)\n", - " 1 0.000 0.000 0.000 0.000 config.py:642(_warn_if_deprecated)\n", - " 1 0.000 0.000 0.000 0.000 common.py:732(is_signed_integer_dtype)\n", - " 3 0.000 0.000 0.000 0.000 common.py:147()\n", - " 10 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_list_like}\n", - " 4 0.000 0.000 0.000 0.000 common.py:1416(is_1d_only_ea_dtype)\n", - " 2 0.000 0.000 0.000 0.000 inject.py:116(is_injectable)\n", - " 9 0.000 0.000 0.000 0.000 {built-in method builtins.hasattr}\n", - " 2 0.000 0.000 0.000 0.000 inference.py:321(is_hashable)\n", - " 1 0.000 0.000 0.000 0.000 {built-in method numpy.zeros}\n", - " 1 0.000 0.000 0.000 0.000 managers.py:2102()\n", - " 1 0.000 0.000 0.000 0.000 construction.py:486()\n", - " 2 0.000 0.000 0.000 0.000 generic.py:5561(__getattr__)\n", - " 2 0.000 0.000 0.000 0.000 config.py:603(_get_deprecated_option)\n", - " 4 0.000 0.000 0.000 0.000 {method 'format' of 'str' objects}\n", - " 2 0.000 0.000 0.000 0.000 orca.py:1568(get_raw_injectable)\n", - " 1 0.000 0.000 0.000 0.000 shape_base.py:218(_vhstack_dispatcher)\n", - " 4 0.000 0.000 0.000 0.000 cast.py:1711(sanitize_to_nanoseconds)\n", - " 2 0.000 0.000 0.000 0.000 {method 'transpose' of 'numpy.ndarray' objects}\n", - " 2 0.000 0.000 0.000 0.000 managers.py:217(is_single_block)\n", - " 1 0.000 0.000 0.000 0.000 common.py:1747(pandas_dtype)\n", - " 2 0.000 0.000 0.000 0.000 base.py:6987(ensure_index)\n", - " 1 0.000 0.000 0.000 0.000 {built-in method builtins.all}\n", - " 4 0.000 0.000 0.000 0.000 orca.py:1560(is_injectable)\n", - " 1 0.000 0.000 0.000 0.000 flags.py:47(__init__)\n", - " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_scalar}\n", - " 2 0.000 0.000 0.000 0.000 common.py:155()\n", - " 1 0.000 0.000 0.000 0.000 config.py:571(_select_options)\n", - " 4 0.000 0.000 0.000 0.000 blocks.py:354(dtype)\n", - " 4 0.000 0.000 0.000 0.000 _dtype.py:24(_kind_name)\n", - " 4 0.000 0.000 0.000 0.000 {method 'upper' of 'str' objects}\n", - " 2 0.000 0.000 0.000 0.000 base.py:4786(values)\n", - " 1 0.000 0.000 0.000 0.000 construction.py:233(mgr_to_mgr)\n", - " 2 0.000 0.000 0.000 0.000 base.py:518()\n", - " 2 0.000 0.000 0.000 0.000 base.py:834(_reset_identity)\n", - " 1 0.000 0.000 0.000 0.000 config.py:630(_translate_key)\n", - " 1 0.000 0.000 0.000 0.000 shape_base.py:207(_arrays_for_stack_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 base.py:1650(name)\n", - " 2 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}\n", - " 3 0.000 0.000 0.000 0.000 common.py:145(classes)\n", - " 2 0.000 0.000 0.000 0.000 common.py:150(classes_and_not_datetimelike)\n", - " 2 0.000 0.000 0.000 0.000 blocks.py:244(mgr_locs)\n", - " 2 0.000 0.000 0.000 0.000 {built-in method __new__ of type object at 0x908780}\n", - " 2 0.000 0.000 0.000 0.000 {built-in method builtins.hash}\n", - " 2 0.000 0.000 0.000 0.000 multiarray.py:1071(copyto)\n", - " 1 0.000 0.000 0.000 0.000 {method 'split' of 'str' objects}\n", - " 2 0.000 0.000 0.000 0.000 {method 'setdefault' of 'dict' objects}\n", - " 1 0.000 0.000 0.000 0.000 fromnumeric.py:1862(_nonzero_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 {method 'astype' of 'numpy.ndarray' objects}\n", - " 1 0.000 0.000 0.000 0.000 {pandas._libs.lib.is_iterator}\n", - " 1 0.000 0.000 0.000 0.000 base.py:540(_ensure_array)\n", - " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", - " 1 0.000 0.000 0.000 0.000 {method 'keys' of 'dict' objects}\n", - " 1 0.000 0.000 0.000 0.000 managers.py:2111()\n", - " 1 0.000 0.000 0.000 0.000 multiarray.py:80(empty_like)\n", - " 1 0.000 0.000 0.000 0.000 managers.py:919(__init__)\n", - " 1 0.000 0.000 0.000 0.000 base.py:229(disallow_kwargs)\n", - " 1 0.000 0.000 0.000 0.000 numeric.py:72(_zeros_like_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 shape_base.py:77(_atleast_2d_dispatcher)\n", - " 1 0.000 0.000 0.000 0.000 fromnumeric.py:1008(_argsort_dispatcher)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%prun -s cumulative run_fru()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "66c833e5-2ad3-4052-ae11-2aa887ad9a4f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5e0f3f75-1150-4712-bfb4-435e85af8b52", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1fc21256-564e-4842-82fb-4ba5358fb644", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ab332607-5f2e-4c7f-bfe4-5407c32644fb", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/notebooks/frozen_rand_mode_choice_dev.ipynb b/notebooks/frozen_rand_mode_choice_dev.ipynb deleted file mode 100644 index 101953e516..0000000000 --- a/notebooks/frozen_rand_mode_choice_dev.ipynb +++ /dev/null @@ -1,3418 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", - "metadata": {}, - "source": [ - "# Start work here, clean up as you go\n", - "\n", - "Problem below: scale of error term on lower levels needs to be given by nest, not the case atm\n", - "\n", - "Do I remember this correctly and probabilities are calculated as products of marginal and conditional probabilities?\n", - "if so, the corresponding utilities at leaf and node levels would need to be calculated, and I would be able to use\n", - "these directly, right? CHECK, would make it much easier!" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.385153Z", - "iopub.status.busy": "2022-05-01T10:17:57.384881Z", - "iopub.status.idle": "2022-05-01T10:17:57.534433Z", - "shell.execute_reply": "2022-05-01T10:17:57.533096Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%load_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "c3dba451-1e10-403e-8614-35d57e6577f4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.536623Z", - "iopub.status.busy": "2022-05-01T10:17:57.536012Z", - "iopub.status.idle": "2022-05-01T10:17:57.542755Z", - "shell.execute_reply": "2022-05-01T10:17:57.541685Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:00.350944Z", - "iopub.status.busy": "2022-05-01T10:18:00.350730Z", - "iopub.status.idle": "2022-05-01T10:18:12.760977Z", - "shell.execute_reply": "2022-05-01T10:18:12.760013Z", - "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "import os\n", - "import argparse\n", - "from datetime import datetime\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "from numpy.random import default_rng\n", - "\n", - "\n", - "from activitysim.cli import run\n", - "from activitysim.core import inject\n", - "from activitysim.core import tracing\n", - "from activitysim.core import config\n", - "from activitysim.core import pipeline\n", - "from activitysim.core import mem\n", - "from activitysim.core import chunk\n", - "from activitysim.core import simulate\n", - "from activitysim.core import logit\n", - "from activitysim.abm.models.util.mode import mode_choice_simulate\n", - "from activitysim.abm.models.util import estimation\n", - "from activitysim.core import expressions\n", - "from activitysim.core.util import assign_in_place" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:16.944537Z", - "iopub.status.busy": "2022-05-01T10:18:16.944291Z", - "iopub.status.idle": "2022-05-01T10:18:17.124764Z", - "shell.execute_reply": "2022-05-01T10:18:17.123725Z", - "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "pd.set_option(\"max_columns\", 500)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:17.839947Z", - "iopub.status.busy": "2022-05-01T10:18:17.839070Z", - "iopub.status.idle": "2022-05-01T10:18:18.019676Z", - "shell.execute_reply": "2022-05-01T10:18:18.018689Z", - "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_example_mtc\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:18.894533Z", - "iopub.status.busy": "2022-05-01T10:18:18.894303Z", - "iopub.status.idle": "2022-05-01T10:18:19.078807Z", - "shell.execute_reply": "2022-05-01T10:18:19.077951Z", - "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "os.chdir(example_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:22.111723Z", - "iopub.status.busy": "2022-05-01T10:18:22.111490Z", - "iopub.status.idle": "2022-05-01T10:18:22.297437Z", - "shell.execute_reply": "2022-05-01T10:18:22.296501Z", - "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "parser = argparse.ArgumentParser()\n", - "run.add_run_args(parser)\n", - "# args = parser.parse_args()\n", - "# parser.parse_args(['--sum', '7', '-1', '42'])\n", - "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", - "#run.run(args) # 2mins full example run\n", - "\n", - "\n", - "if not inject.is_injectable('preload_injectables'):\n", - " from activitysim import abm # register abm steps and other abm-specific injectables\n", - "run.handle_standard_args(args) # possibly update injectables" - ] - }, - { - "cell_type": "markdown", - "id": "6045438f-d1eb-4b5c-9737-185798b7f97f", - "metadata": {}, - "source": [ - "## trip mode choice by hand" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "600ae244-3e6c-4b66-8d39-aa9f6f60b378", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:26.399145Z", - "iopub.status.busy": "2022-05-01T10:18:26.398687Z", - "iopub.status.idle": "2022-05-01T10:18:26.583256Z", - "shell.execute_reply": "2022-05-01T10:18:26.582233Z", - "shell.execute_reply.started": "2022-05-01T10:18:26.399105Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#inject.get_table('trips').to_frame() #pipeline.orca.get_raw_table('trips').to_frame()\n", - "#inject.get_table('tours_merged').to_frame() #pipeline.orca.get_raw_table('tours_merged').to_frame()\n", - "#inject.get_injectable('network_los')" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "outputs": [], - "source": [ - "def run_trip_mode_choice(do_these_purposes=None, simulate_function=simulate.simple_simulate):\n", - " #do_these_purposes=['escort']\n", - " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", - " resume_after = \"trip_scheduling\"\n", - " model_name = \"trip_mode_choice\"\n", - " chunk_size = 0 # test_mtc means no chunking\n", - "\n", - " pipeline.open_pipeline(resume_after)\n", - " # preload any bulky injectables (e.g. skims) not in pipeline\n", - " inject.get_injectable('preload_injectables', None)\n", - " pipeline._PIPELINE.rng().begin_step(model_name)\n", - " step_name = model_name\n", - " args = {}\n", - " checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - " inject.set_step_args(args)\n", - "\n", - " trips = inject.get_table('trips')\n", - " tours_merged = inject.get_table('tours_merged')\n", - " network_los = inject.get_injectable('network_los')\n", - "\n", - " trace_label = 'trip_mode_choice'\n", - " model_settings_file_name = 'trip_mode_choice.yaml'\n", - " model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - " mode_column_name = 'trip_mode'\n", - "\n", - " trips_df = trips.to_frame()\n", - " print(\"Running with %d trips\", trips_df.shape[0])\n", - "\n", - " tours_merged = tours_merged.to_frame()\n", - " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "\n", - " # - trips_merged - merge trips and tours_merged\n", - " trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - " assert trips_merged.index.equals(trips.index)\n", - "\n", - " # setup skim keys\n", - " assert ('trip_period' not in trips_merged)\n", - " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - " orig_col = 'origin'\n", - " dest_col = 'destination'\n", - "\n", - " constants = {}\n", - " constants.update(config.get_model_constants(model_settings))\n", - " constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - " })\n", - "\n", - " skim_dict = network_los.get_default_skim_dict()\n", - "\n", - " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "\n", - " skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - " }\n", - "\n", - " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - " nest_specs = config.get_logit_model_settings(model_settings)\n", - "\n", - " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - "\n", - " if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", - " continue\n", - "\n", - " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " (primary_purpose, len(trips_segment.index), ))\n", - "\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " ################ Replace wrapper function\n", - " # choices = mode_choice_simulate(...)\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", - " choices = simulate_function(\n", - " choosers=trips_segment,\n", - " spec=spec,\n", - " nest_spec=nest_spec,\n", - " skims=skims,\n", - " locals_d=locals_dict,\n", - " chunk_size=chunk_size,\n", - " want_logsums=logsum_column_name is not None,\n", - " trace_label=segment_trace_label,\n", - " trace_choice_name='trip_mode_choice',\n", - " estimator=estimator,\n", - " trace_column_names=None)\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " alts = spec.columns\n", - " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", - " ################\n", - " choices_list.append(choices)\n", - " choices_df_asim = pd.concat(choices_list)\n", - "\n", - " # update trips table with choices (and potionally logssums)\n", - " trips_df = trips.to_frame()\n", - "\n", - " if (do_these_purposes is not None):\n", - " trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", - "\n", - " assign_in_place(trips_df, choices_df_asim)\n", - " assert not trips_df[mode_column_name].isnull().any()\n", - "\n", - " finalise = True\n", - " if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - " print(\"Done\")\n", - "\n", - " return trips_df" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:28:53.643404Z", - "iopub.status.busy": "2021-09-01T02:28:53.643055Z", - "iopub.status.idle": "2021-09-01T02:29:01.223951Z", - "shell.execute_reply": "2021-09-01T02:29:01.222865Z", - "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'atwork' (27 trips)\n", - "trip_mode_choice tour_type 'eatout' (33 trips)\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n", - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", - "trip_mode_choice tour_type 'othmaint' (46 trips)\n", - "trip_mode_choice tour_type 'school' (37 trips)\n", - "trip_mode_choice tour_type 'shopping' (77 trips)\n", - "trip_mode_choice tour_type 'social' (19 trips)\n", - "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n" - ] - } - ], - "source": [ - "trips_df = run_trip_mode_choice()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "7eae2854-38bd-4da0-a161-3a15bbd17177", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:30:57.562848Z", - "iopub.status.busy": "2021-08-31T10:30:57.562615Z", - "iopub.status.idle": "2021-08-31T10:30:57.806655Z", - "shell.execute_reply": "2021-08-31T10:30:57.805135Z", - "shell.execute_reply.started": "2021-08-31T10:30:57.562824Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n8684833 26478 26478 eatout 1 True \n8684837 26478 26478 eatout 1 False \n8685009 26478 26478 othmaint 1 True \n8685013 26478 26478 othmaint 1 False \n8753057 26686 26686 eatout 1 True \n... ... ... ... ... ... \n2472945113 7539466 2848131 shopping 1 True \n2472945117 7539466 2848131 shopping 1 False \n2472945118 7539466 2848131 shopping 2 False \n2473024473 7539708 2848373 univ 1 True \n2473024477 7539708 2848373 univ 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n8684833 1 13 8 1085604 eatout \n8684837 1 8 13 1085604 home \n8685009 1 10 8 1085626 othmaint \n8685013 1 8 10 1085626 home \n8753057 1 5 8 1094132 eatout \n... ... ... ... ... ... \n2472945113 1 8 3 309118139 shopping \n2472945117 2 25 8 309118139 shopping \n2472945118 2 3 25 309118139 home \n2473024473 1 13 18 309128059 univ \n2473024477 1 18 13 309128059 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n8684833 NaN 11.0 WALK -1.171760 \n8684837 NaN 11.0 WALK -1.238719 \n8685009 NaN 12.0 BIKE 6.198626 \n8685013 NaN 13.0 BIKE 6.175681 \n8753057 NaN 19.0 WALK 4.457539 \n... ... ... ... ... \n2472945113 NaN 18.0 WALK_LOC 12.537675 \n2472945117 56.842247 21.0 WALK_LOC 11.880804 \n2472945118 NaN 22.0 WALK 13.710030 \n2473024473 NaN 16.0 WALK_LOC -0.530696 \n2473024477 NaN 23.0 WALK_LRF 0.624304 \n\n[482 rows x 14 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
86848332647826478eatout1True11381085604eatoutNaN11.0WALK-1.171760
86848372647826478eatout1False18131085604homeNaN11.0WALK-1.238719
86850092647826478othmaint1True11081085626othmaintNaN12.0BIKE6.198626
86850132647826478othmaint1False18101085626homeNaN13.0BIKE6.175681
87530572668626686eatout1True1581094132eatoutNaN19.0WALK4.457539
.............................................
247294511375394662848131shopping1True183309118139shoppingNaN18.0WALK_LOC12.537675
247294511775394662848131shopping1False2258309118139shopping56.84224721.0WALK_LOC11.880804
247294511875394662848131shopping2False2325309118139homeNaN22.0WALK13.710030
247302447375397082848373univ1True11318309128059univNaN16.0WALK_LOC-0.530696
247302447775397082848373univ1False11813309128059homeNaN23.0WALK_LRF0.624304
\n

482 rows × 14 columns

\n
" - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trips_df" - ] - }, - { - "cell_type": "markdown", - "id": "8eebea31-499c-41ca-8411-883a88ca800a", - "metadata": {}, - "source": [ - "## nested dev" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "3e2513e9-c03f-4891-9a1c-4053a4440a10", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:31:01.113060Z", - "iopub.status.busy": "2021-08-31T10:31:01.112829Z", - "iopub.status.idle": "2021-08-31T10:31:01.359912Z", - "shell.execute_reply": "2021-08-31T10:31:01.358948Z", - "shell.execute_reply.started": "2021-08-31T10:31:01.113034Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# see fct above - return if necessary\n", - "#spec = simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - "#nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "36c100b3-3e39-4950-a586-4d42be695eaa", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T10:31:01.775068Z", - "iopub.status.busy": "2021-08-31T10:31:01.774846Z", - "iopub.status.idle": "2021-08-31T10:31:01.975816Z", - "shell.execute_reply": "2021-08-31T10:31:01.974499Z", - "shell.execute_reply.started": "2021-08-31T10:31:01.775044Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#print(nest_spec)\n", - "#for nest in logit.each_nest(nest_spec):\n", - "# nest.print()" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "e8d11f6a-f82a-40bd-8eef-fc28bcca8252", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:59:35.109079Z", - "iopub.status.busy": "2021-09-01T01:59:35.108781Z", - "iopub.status.idle": "2021-09-01T01:59:35.319301Z", - "shell.execute_reply": "2021-09-01T01:59:35.318402Z", - "shell.execute_reply.started": "2021-09-01T01:59:35.109050Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, estimator,\n", - " log_alt_losers=False,\n", - " want_logsums=False, trace_label=None,\n", - " trace_choice_name=None, trace_column_names=None):\n", - "\n", - " trace_label = tracing.extend_trace_label(trace_label, 'eval_nl')\n", - " assert trace_label\n", - " have_trace_targets = tracing.has_trace_targets(choosers)\n", - "\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - "\n", - " if have_trace_targets:\n", - " tracing.trace_df(choosers, '%s.choosers' % trace_label)\n", - "\n", - " raw_utilities = simulate.eval_utilities(spec, choosers, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=have_trace_targets,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - " chunk.log_df(trace_label, \"raw_utilities\", raw_utilities)\n", - "\n", - " if have_trace_targets:\n", - " tracing.trace_df(raw_utilities, '%s.raw_utilities' % trace_label,\n", - " column_labels=['alternative', 'utility'])\n", - "\n", - " # exponentiated utilities of leaves and nests\n", - " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - " chunk.log_df(trace_label, \"nested_exp_utilities\", nested_exp_utilities)\n", - "\n", - " del raw_utilities\n", - " chunk.log_df(trace_label, 'raw_utilities', None)\n", - "\n", - " if have_trace_targets:\n", - " tracing.trace_df(nested_exp_utilities, '%s.nested_exp_utilities' % trace_label,\n", - " column_labels=['alternative', 'utility'])\n", - "\n", - " # probabilities of alternatives relative to siblings sharing the same nest\n", - " nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec,\n", - " trace_label=trace_label)\n", - " chunk.log_df(trace_label, \"nested_probabilities\", nested_probabilities)\n", - "\n", - " if want_logsums:\n", - " # logsum of nest root\n", - " logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index)\n", - " chunk.log_df(trace_label, \"logsums\", logsums)\n", - "\n", - " del nested_exp_utilities\n", - " chunk.log_df(trace_label, 'nested_exp_utilities', None)\n", - "\n", - " if have_trace_targets:\n", - " tracing.trace_df(nested_probabilities, '%s.nested_probabilities' % trace_label,\n", - " column_labels=['alternative', 'probability'])\n", - "\n", - " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", - " chunk.log_df(trace_label, \"base_probabilities\", base_probabilities)\n", - "\n", - " del nested_probabilities\n", - " chunk.log_df(trace_label, 'nested_probabilities', None)\n", - "\n", - " if have_trace_targets:\n", - " tracing.trace_df(base_probabilities, '%s.base_probabilities' % trace_label,\n", - " column_labels=['alternative', 'probability'])\n", - "\n", - " # note base_probabilities could all be zero since we allowed all probs for nests to be zero\n", - " # check here to print a clear message but make_choices will raise error if probs don't sum to 1\n", - " BAD_PROB_THRESHOLD = 0.001\n", - " no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD\n", - "\n", - " if no_choices.any():\n", - "\n", - " logit.report_bad_choices(\n", - " no_choices, base_probabilities,\n", - " trace_label=tracing.extend_trace_label(trace_label, 'bad_probs'),\n", - " trace_choosers=choosers,\n", - " msg=\"base_probabilities do not sum to one\")\n", - "\n", - " if custom_chooser:\n", - " choices, rands = custom_chooser(probs=base_probabilities, choosers=choosers, spec=spec,\n", - " trace_label=trace_label)\n", - " else:\n", - " choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)\n", - "\n", - " del base_probabilities\n", - " chunk.log_df(trace_label, 'base_probabilities', None)\n", - "\n", - " if have_trace_targets:\n", - " tracing.trace_df(choices, '%s.choices' % trace_label,\n", - " columns=[None, trace_choice_name])\n", - " tracing.trace_df(rands, '%s.rands' % trace_label,\n", - " columns=[None, 'rand'])\n", - " if want_logsums:\n", - " tracing.trace_df(logsums, '%s.logsums' % trace_label,\n", - " columns=[None, 'logsum'])\n", - "\n", - " if want_logsums:\n", - " choices = choices.to_frame('choice')\n", - " choices['logsum'] = logsums\n", - "\n", - " return choices\n", - "\n", - "\n", - "def simple_simulate_dev(choosers, spec, nest_spec,\n", - " skims=None, locals_d=None,\n", - " chunk_size=0, custom_chooser=None,\n", - " log_alt_losers=False,\n", - " want_logsums=False,\n", - " estimator=None,\n", - " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", - " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", - " assert len(choosers) > 0\n", - " result_list = []\n", - " # segment by person type and pick the right spec for each person type\n", - " for i, chooser_chunk, chunk_trace_label \\\n", - " in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", - " # the following replaces choices = _simple_simulate(...)\n", - " if skims is not None:\n", - " simulate.set_skim_wrapper_targets(choosers, skims)\n", - "\n", - " # only do this for nested, logit is straight forward\n", - " assert nest_spec is not None\n", - " choices = eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser,\n", - " log_alt_losers=log_alt_losers,\n", - " want_logsums=want_logsums,\n", - " estimator=estimator,\n", - " trace_label=trace_label,\n", - " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", - "\n", - "\n", - " result_list.append(choices)\n", - " chunk.log_df(trace_label, f'result_list', result_list)\n", - "\n", - " if len(result_list) > 1:\n", - " choices = pd.concat(result_list)\n", - " assert len(choices.index == len(choosers.index))\n", - " return choices\n" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "ec4eb1ff-2f35-4919-96b4-5acffa01f597", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n", - "Done\n" - ] - }, - { - "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n137248721 418441 304036 escort 1 True \n137248725 418441 304036 escort 1 False \n211388201 644476 386761 escort 1 True \n211388205 644476 386761 escort 1 False \n806388401 2458501 1173905 escort 1 True \n806388405 2458501 1173905 escort 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n137248721 1 7 10 17156090 escort \n137248725 1 10 7 17156090 home \n211388201 1 11 16 26423525 escort \n211388205 1 16 11 26423525 home \n806388401 1 16 8 100798550 escort \n806388405 1 8 16 100798550 home \n\n destination_logsum depart trip_mode mode_choice_logsum \ntrip_id \n137248721 NaN 7.0 WALK 11.435800 \n137248725 NaN 7.0 WALK 11.480440 \n211388201 NaN 5.0 WALK_LOC 4.789158 \n211388205 NaN 6.0 WALK_LOC 5.050171 \n806388401 NaN 15.0 WALK_LOC 6.451457 \n806388405 NaN 16.0 WALK_LOC 6.446188 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdeparttrip_modemode_choice_logsum
trip_id
137248721418441304036escort1True171017156090escortNaN7.0WALK11.435800
137248725418441304036escort1False110717156090homeNaN7.0WALK11.480440
211388201644476386761escort1True1111626423525escortNaN5.0WALK_LOC4.789158
211388205644476386761escort1False1161126423525homeNaN6.0WALK_LOC5.050171
80638840124585011173905escort1True1168100798550escortNaN15.0WALK_LOC6.451457
80638840524585011173905escort1False1816100798550homeNaN16.0WALK_LOC6.446188
\n
" - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "test_trips = run_trip_mode_choice(do_these_purposes=[\"escort\"], simulate_function=simple_simulate_dev)\n", - "test_trips" - ] - }, - { - "cell_type": "markdown", - "source": [ - "# Get raw utilities, etc" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 61, - "outputs": [], - "source": [ - "def eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, estimator,\n", - " log_alt_losers=False,\n", - " want_logsums=False, trace_label=None,\n", - " trace_choice_name=None, trace_column_names=None):\n", - "\n", - " trace_label = tracing.extend_trace_label(trace_label, 'eval_nl')\n", - " assert trace_label\n", - " have_trace_targets = tracing.has_trace_targets(choosers)\n", - "\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, choosers, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=have_trace_targets,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - " # exponentiated utilities of leaves and nests\n", - " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - " nested_utils = simulate.compute_nested_utilities(raw_utilities, nest_spec)\n", - " # probabilities of alternatives relative to siblings sharing the same nest\n", - " nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec,\n", - " trace_label=trace_label)\n", - " if want_logsums:\n", - " # logsum of nest root\n", - " logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index)\n", - " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec)\n", - " # note base_probabilities could all be zero since we allowed all probs for nests to be zero\n", - " # check here to print a clear message but make_choices will raise error if probs don't sum to 1\n", - " BAD_PROB_THRESHOLD = 0.001\n", - " no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD\n", - " if no_choices.any():\n", - " print(\"BAD\")\n", - " choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label)\n", - " if want_logsums:\n", - " choices = choices.to_frame('choice')\n", - " choices['logsum'] = logsums\n", - " return choices, raw_utilities, nested_exp_utilities, nested_utils\n", - "\n", - "\n", - "def simple_simulate_dev(choosers, spec, nest_spec,\n", - " skims=None, locals_d=None,\n", - " chunk_size=0, custom_chooser=None,\n", - " log_alt_losers=False,\n", - " want_logsums=False,\n", - " estimator=None,\n", - " trace_label=None, trace_choice_name=None, trace_column_names=None):\n", - " trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate')\n", - " assert len(choosers) > 0\n", - " result_list = []\n", - " # segment by person type and pick the right spec for each person type\n", - " for i, chooser_chunk, chunk_trace_label in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label):\n", - " # the following replaces choices = _simple_simulate(...)\n", - " if skims is not None:\n", - " simulate.set_skim_wrapper_targets(choosers, skims)\n", - "\n", - " # only do this for nested, logit is straight forward\n", - " assert nest_spec is not None\n", - " choices, raw_utilities, nested_exp_utilities, nested_utils = eval_nl_dev(choosers, spec, nest_spec, locals_d,\n", - " custom_chooser,\n", - " log_alt_losers=log_alt_losers,\n", - " want_logsums=want_logsums,\n", - " estimator=estimator,\n", - " trace_label=trace_label,\n", - " trace_choice_name=trace_choice_name, trace_column_names=trace_column_names)\n", - "\n", - "\n", - " result_list.append(choices)\n", - " chunk.log_df(trace_label, f'result_list', result_list)\n", - "\n", - " if len(result_list) > 1:\n", - " choices = pd.concat(result_list)\n", - " assert len(choices.index == len(choosers.index))\n", - " return choices, raw_utilities, nested_exp_utilities, nested_utils\n", - "\n", - "\n", - "def get_stuff(do_these_purposes=None):\n", - " #do_these_purposes=['escort']\n", - " \"\"\"open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists\"\"\"\n", - " resume_after = \"trip_scheduling\"\n", - " model_name = \"trip_mode_choice\"\n", - " chunk_size = 0 # test_mtc means no chunking\n", - "\n", - " pipeline.open_pipeline(resume_after)\n", - " # preload any bulky injectables (e.g. skims) not in pipeline\n", - " inject.get_injectable('preload_injectables', None)\n", - " pipeline._PIPELINE.rng().begin_step(model_name)\n", - " step_name = model_name\n", - " args = {}\n", - " checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - " inject.set_step_args(args)\n", - "\n", - " trips = inject.get_table('trips')\n", - " tours_merged = inject.get_table('tours_merged')\n", - " network_los = inject.get_injectable('network_los')\n", - "\n", - " trace_label = 'trip_mode_choice'\n", - " model_settings_file_name = 'trip_mode_choice.yaml'\n", - " model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - " logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - " mode_column_name = 'trip_mode'\n", - "\n", - " trips_df = trips.to_frame()\n", - " print(\"Running with %d trips\", trips_df.shape[0])\n", - "\n", - " tours_merged = tours_merged.to_frame()\n", - " tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "\n", - " # - trips_merged - merge trips and tours_merged\n", - " trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - " assert trips_merged.index.equals(trips.index)\n", - "\n", - " # setup skim keys\n", - " assert ('trip_period' not in trips_merged)\n", - " trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - " orig_col = 'origin'\n", - " dest_col = 'destination'\n", - "\n", - " constants = {}\n", - " constants.update(config.get_model_constants(model_settings))\n", - " constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - " })\n", - "\n", - " skim_dict = network_los.get_default_skim_dict()\n", - "\n", - " odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - " dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - " od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "\n", - " skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - " }\n", - "\n", - " model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - " nest_specs = config.get_logit_model_settings(model_settings)\n", - "\n", - " estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - " choices_list = []\n", - " raw_util_list = []\n", - " nest_list = []\n", - " nu_list = []\n", - " nest_spec_list = []\n", - "\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - "\n", - " if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes):\n", - " continue\n", - "\n", - " print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " (primary_purpose, len(trips_segment.index), ))\n", - "\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " ################ Replace wrapper function\n", - " # choices = mode_choice_simulate(...)\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label)\n", - " choices, raw_utilities, nested_exp_utilities, nested_utils = simple_simulate_dev(\n", - " choosers=trips_segment,\n", - " spec=spec,\n", - " nest_spec=nest_spec,\n", - " skims=skims,\n", - " locals_d=locals_dict,\n", - " chunk_size=chunk_size,\n", - " want_logsums=logsum_column_name is not None,\n", - " trace_label=segment_trace_label,\n", - " trace_choice_name='trip_mode_choice',\n", - " estimator=estimator,\n", - " trace_column_names=None)\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " alts = spec.columns\n", - " choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts))))\n", - " ################\n", - " choices_list.append(choices)\n", - " raw_util_list.append(raw_utilities)\n", - " nest_list.append(nested_exp_utilities)\n", - " nu_list.append(nested_utils)\n", - " nest_spec_list.append(nest_spec)\n", - "\n", - " choices_df_asim = pd.concat(choices_list)\n", - "\n", - " # update trips table with choices (and potionally logssums)\n", - " trips_df = trips.to_frame()\n", - "\n", - " if (do_these_purposes is not None):\n", - " trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)]\n", - "\n", - " assign_in_place(trips_df, choices_df_asim)\n", - " assert not trips_df[mode_column_name].isnull().any()\n", - "\n", - " finalise = True\n", - " if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - " print(\"Done\")\n", - "\n", - " return trips_df, raw_util_list, nest_list, nu_list, nest_spec_list" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 62, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n", - "Done\n" - ] - } - ], - "source": [ - "t, ru, neu, nu, ns = get_stuff(do_these_purposes=[\"escort\"])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 63, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n137248721 -1.294906 -2855.580620 -0.453217 -0.280520 \n137248725 -1.247739 -2855.533453 -0.436709 -0.233697 \n211388201 -2855.520162 -5709.805876 -inf -17.366498 \n211388205 -2856.810200 -5711.095914 -inf -18.093309 \n806388401 -2855.874866 -5710.160580 -inf -9.085232 \n806388405 -2858.281791 -5712.567505 -inf -10.451460 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n137248721 -2854.566235 -0.098182 -2855.573680 -5709.859395 -inf \n137248725 -2854.519411 -0.081794 -2855.526994 -5709.812708 -inf \n211388201 -2871.652212 -6.078274 -21.226117 -2875.511831 -7.429141 \n211388205 -2872.379023 -6.332658 -21.727638 -2876.013352 -7.604673 \n806388401 -2863.370946 -3.179831 -10.734064 -2865.019778 -3.756922 \n806388405 -2864.737174 -3.658011 -11.684013 -2865.969727 -4.089405 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n137248721 0.311848 15.883036 -1368.200102 11.435786 -1970.783011 \n137248725 0.323683 15.945036 -1368.169103 11.480426 -1970.850529 \n211388201 -4.210520 -4.216264 -1381.397292 -3.035710 9.572919 \n211388205 -4.381625 -4.216264 -1381.397292 -3.035710 10.096155 \n806388401 -1.968599 6.192499 -1378.480723 4.458599 12.609633 \n806388405 -2.273383 5.998749 -1378.519473 4.319099 12.638426 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n137248721 -3968.783011 -3968.783011 -3968.783011 -3968.783011 -inf \n137248725 -3968.850529 -3968.850529 -3968.850529 -3968.850529 -inf \n211388201 -1988.028653 -1988.028653 -1988.028653 -1988.028653 4.786459 \n211388205 -1987.903845 -1987.903845 -1987.903845 -1987.903845 5.048078 \n806388401 -1984.209027 -1983.750027 -1984.124827 -1984.171027 6.304817 \n806388405 -1984.311174 -1983.852174 -1984.226974 -1984.273174 6.319213 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n137248721 -1970.903118 -3973.401006 -3973.401006 -3974.669500 -3975.688637 \n137248725 -1971.203369 -3975.938495 -3975.938495 -3971.574832 -3978.057757 \n211388201 -1988.387109 -3990.994654 -3990.994654 -1994.178373 -1997.184706 \n211388205 -1988.132266 -3993.215462 -3993.215462 -1990.677481 -1997.375747 \n806388401 -1985.442854 -3989.293913 -3989.293913 -3990.843560 -3993.682148 \n806388405 -1985.451447 -3990.724818 -3990.724818 -3987.215444 -3994.211225 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\ntrip_id \n137248721 -inf -inf -24.503675 -25.143041 -24.249471 -8.448473 \n137248725 -inf -inf -24.341318 -25.104291 -24.140831 -8.406870 \n211388201 -inf 3.446251 -15.861515 -3.864497 -5.068360 -1.296757 \n211388205 -inf 3.634616 -15.776424 -3.845077 -5.005482 -1.286094 \n806388401 -inf 4.539468 -19.143696 -19.495908 -16.361074 -5.853917 \n806388405 -inf 4.549833 -18.886576 -19.495133 -16.300395 -5.828540 \n\n root \ntrip_id \n137248721 11.435801 \n137248725 11.480441 \n211388201 3.456910 \n211388205 3.643469 \n806388401 5.193789 \n806388405 5.134877 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.294906-2855.580620-0.453217-0.280520-2854.566235-0.098182-2855.573680-5709.859395-inf0.31184815.883036-1368.20010211.435786-1970.783011-3968.783011-3968.783011-3968.783011-3968.783011-inf-1970.903118-3973.401006-3973.401006-3974.669500-3975.688637-inf-inf-24.503675-25.143041-24.249471-8.44847311.435801
137248725-1.247739-2855.533453-0.436709-0.233697-2854.519411-0.081794-2855.526994-5709.812708-inf0.32368315.945036-1368.16910311.480426-1970.850529-3968.850529-3968.850529-3968.850529-3968.850529-inf-1971.203369-3975.938495-3975.938495-3971.574832-3978.057757-inf-inf-24.341318-25.104291-24.140831-8.40687011.480441
211388201-2855.520162-5709.805876-inf-17.366498-2871.652212-6.078274-21.226117-2875.511831-7.429141-4.210520-4.216264-1381.397292-3.0357109.572919-1988.028653-1988.028653-1988.028653-1988.0286534.786459-1988.387109-3990.994654-3990.994654-1994.178373-1997.184706-inf3.446251-15.861515-3.864497-5.068360-1.2967573.456910
211388205-2856.810200-5711.095914-inf-18.093309-2872.379023-6.332658-21.727638-2876.013352-7.604673-4.381625-4.216264-1381.397292-3.03571010.096155-1987.903845-1987.903845-1987.903845-1987.9038455.048078-1988.132266-3993.215462-3993.215462-1990.677481-1997.375747-inf3.634616-15.776424-3.845077-5.005482-1.2860943.643469
806388401-2855.874866-5710.160580-inf-9.085232-2863.370946-3.179831-10.734064-2865.019778-3.756922-1.9685996.192499-1378.4807234.45859912.609633-1984.209027-1983.750027-1984.124827-1984.1710276.304817-1985.442854-3989.293913-3989.293913-3990.843560-3993.682148-inf4.539468-19.143696-19.495908-16.361074-5.8539175.193789
806388405-2858.281791-5712.567505-inf-10.451460-2864.737174-3.658011-11.684013-2865.969727-4.089405-2.2733835.998749-1378.5194734.31909912.638426-1984.311174-1983.852174-1984.226974-1984.2731746.319213-1985.451447-3990.724818-3990.724818-3987.215444-3994.211225-inf4.549833-18.886576-19.495133-16.300395-5.8285405.134877
\n
" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "display(nu[0])#, ns[0])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 64, - "outputs": [], - "source": [ - "# next: add error terms to alternatives and nodes - can be done with this right here by iterating over nests and\n", - "# adding at each level.\n", - "# will probably want to vectorise, but that's for later.\n", - "# add_random returns a random number per row - this seems like the right thing to use while iterating over nest nodes\n", - "# and leafs (which gives us single columns per alternative)\n", - "# for destination choice, we might need to rethink this pattern though, but we'll cross that bridge when we come to it\n", - "\n", - "def inverse_ev1_cdf(x, location=0.0, scale=1.0):\n", - " #quantile function of EV1\n", - " # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution where the scale is proportional to variance (not variance^{-1})\n", - " # this means nested scales are between 0 and 1\n", - " # x can be number or np array or pd df for vecops\n", - " return location - scale * np.log(-np.log(x))" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 112, - "outputs": [], - "source": [ - "# fake random channel for prototyping as per Asim tests\n", - "from activitysim.core.random import Random\n", - "rng = Random()\n", - "\n", - "def add_ev1_random(df, nest_spec):\n", - " # TODO: generalise to logit for nest_spec==None by adding one rand (scale=1) to all columns\n", - " nest_utils_for_choice = df.copy() # we'll add random parts to this such that we can recursively choose from\n", - " for n in logit.each_nest(nest_spec):\n", - " if n.level == 1:\n", - " continue # skip the root level\n", - " # TODO: check parent nest level scale is what we want this is right\n", - " rands = inverse_ev1_cdf(rng.random_for_df(nest_utils_for_choice, n=1), scale=n.parent_nest_scale)\n", - " # this will be cleaner wtith xarrays\n", - " nest_utils_for_choice.loc[:,n.name] += rands[:,0]\n", - " return nest_utils_for_choice" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 115, - "outputs": [], - "source": [ - "utils_df = nu[0]\n", - "nest_spec = ns[0]\n", - "nest_utils_for_choice = add_ev1_random(utils_df, nest_spec)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 116, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n137248721 -1.116115 -2855.401829 -0.085418 -0.101729 \n137248725 -0.865187 -2855.150902 0.350254 0.148855 \n211388201 -2855.281895 -5709.567609 -inf -17.128230 \n211388205 -2856.635577 -5710.921291 -inf -17.918686 \n806388401 -2855.821604 -5710.107318 -inf -9.031970 \n806388405 -2857.992149 -5712.277863 -inf -10.161818 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n137248721 -2854.387444 0.269616 -2855.394889 -5709.680604 -inf \n137248725 -2854.136859 0.705169 -2855.144442 -5709.430157 -inf \n211388201 -2871.413945 -5.588125 -20.987850 -2875.273564 -6.938992 \n211388205 -2872.204400 -5.973433 -21.553014 -2875.838729 -7.245448 \n806388401 -2863.317684 -3.070264 -10.680802 -2864.966516 -3.647355 \n806388405 -2864.447532 -3.062176 -11.394371 -2865.680085 -3.493570 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n137248721 0.822679 16.250835 -1367.832304 11.946618 -1970.527596 \n137248725 1.416687 16.732000 -1367.382139 12.573430 -1970.304027 \n211388201 -3.529757 -3.726114 -1380.907142 -2.354947 9.913300 \n211388205 -3.882701 -3.857039 -1381.038067 -2.536786 10.345617 \n806388401 -1.816422 6.302066 -1378.371156 4.610776 12.685721 \n806388405 -1.445835 6.594584 -1377.923639 5.146648 13.052200 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n137248721 -3968.527596 -3968.527596 -3968.527596 -3968.527596 -inf \n137248725 -3968.304027 -3968.304027 -3968.304027 -3968.304027 -inf \n211388201 -1987.688271 -1987.688271 -1987.688271 -1987.688271 5.276609 \n211388205 -1987.654383 -1987.654383 -1987.654383 -1987.654383 5.407303 \n806388401 -1984.132938 -1983.673938 -1984.048738 -1984.094938 6.414384 \n806388405 -1983.897400 -1983.438400 -1983.813200 -1983.859400 6.915048 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n137248721 -1970.647702 -3973.145591 -3973.145591 -3974.414085 -3975.433221 \n137248725 -1970.656867 -3975.391993 -3975.391993 -3971.028330 -3977.511255 \n211388201 -1988.046728 -3990.654273 -3990.654273 -1993.837992 -1996.844325 \n211388205 -1987.882804 -3992.966000 -3992.966000 -1990.428019 -1997.126285 \n806388401 -1985.366765 -3989.217824 -3989.217824 -3990.767471 -3993.606059 \n806388405 -1985.037673 -3990.311044 -3990.311044 -3986.801670 -3993.797451 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\ntrip_id \n137248721 -inf -inf -24.319775 -24.959142 -24.065572 -7.937642 \n137248725 -inf -inf -23.947836 -24.710809 -23.747349 -7.313866 \n211388201 -inf 4.127014 -15.616441 -3.619423 -4.823285 -0.615994 \n211388205 -inf 4.133540 -15.596812 -3.665464 -4.825869 -0.787171 \n806388401 -inf 4.691645 -19.088912 -19.441124 -16.306290 -5.701741 \n806388405 -inf 5.377382 -18.588659 -19.197216 -16.002477 -5.000992 \n\n root \ntrip_id \n137248721 11.435801 \n137248725 11.480441 \n211388201 3.456910 \n211388205 3.643469 \n806388401 5.193789 \n806388405 5.134877 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILroot
trip_id
137248721-1.116115-2855.401829-0.085418-0.101729-2854.3874440.269616-2855.394889-5709.680604-inf0.82267916.250835-1367.83230411.946618-1970.527596-3968.527596-3968.527596-3968.527596-3968.527596-inf-1970.647702-3973.145591-3973.145591-3974.414085-3975.433221-inf-inf-24.319775-24.959142-24.065572-7.93764211.435801
137248725-0.865187-2855.1509020.3502540.148855-2854.1368590.705169-2855.144442-5709.430157-inf1.41668716.732000-1367.38213912.573430-1970.304027-3968.304027-3968.304027-3968.304027-3968.304027-inf-1970.656867-3975.391993-3975.391993-3971.028330-3977.511255-inf-inf-23.947836-24.710809-23.747349-7.31386611.480441
211388201-2855.281895-5709.567609-inf-17.128230-2871.413945-5.588125-20.987850-2875.273564-6.938992-3.529757-3.726114-1380.907142-2.3549479.913300-1987.688271-1987.688271-1987.688271-1987.6882715.276609-1988.046728-3990.654273-3990.654273-1993.837992-1996.844325-inf4.127014-15.616441-3.619423-4.823285-0.6159943.456910
211388205-2856.635577-5710.921291-inf-17.918686-2872.204400-5.973433-21.553014-2875.838729-7.245448-3.882701-3.857039-1381.038067-2.53678610.345617-1987.654383-1987.654383-1987.654383-1987.6543835.407303-1987.882804-3992.966000-3992.966000-1990.428019-1997.126285-inf4.133540-15.596812-3.665464-4.825869-0.7871713.643469
806388401-2855.821604-5710.107318-inf-9.031970-2863.317684-3.070264-10.680802-2864.966516-3.647355-1.8164226.302066-1378.3711564.61077612.685721-1984.132938-1983.673938-1984.048738-1984.0949386.414384-1985.366765-3989.217824-3989.217824-3990.767471-3993.606059-inf4.691645-19.088912-19.441124-16.306290-5.7017415.193789
806388405-2857.992149-5712.277863-inf-10.161818-2864.447532-3.062176-11.394371-2865.680085-3.493570-1.4458356.594584-1377.9236395.14664813.052200-1983.897400-1983.438400-1983.813200-1983.8594006.915048-1985.037673-3990.311044-3990.311044-3986.801670-3993.797451-inf5.377382-18.588659-19.197216-16.002477-5.0009925.134877
\n
" - }, - "execution_count": 116, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nest_utils_for_choice" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 106, - "outputs": [], - "source": [ - "all_alternatives = list(map(lambda x: x.name, filter(lambda x: x.is_leaf, logit.each_nest(nest_spec))))\n", - "def is_alternative(name):\n", - " return name in all_alternatives\n", - "\n", - "# NOW IN logit.group_nests_by_level(nest_spec)\n", - "# def group_nests_by_level(nest_spec):\n", - "# # group nests by level:\n", - "# depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", - "# #print(f\"Nesting depth is {depth}\")\n", - "# nest_levels = {x: [] for x in range(1, depth+1)}\n", - "# for n in logit.each_nest(nest_spec):\n", - "# nest_levels[n.level].append(n.name)\n", - "# assert len(nest_levels[1]) == 1\n", - "# # assert nest_levels[1][0] == 'root'\n", - "# return nest_levels\n", - "\n", - "nest_utils_for_choice[\"choice\"] = None\n", - "\n", - "for level, alts in logit.group_nests_by_level(nest_spec).items():\n", - " if level == 1:\n", - " continue\n", - " no_choices_made_yet = nest_utils_for_choice[\"choice\"].isnull()\n", - " choice_this_level = nest_utils_for_choice.loc[no_choices_made_yet][alts].idxmax(1)\n", - " nest_utils_for_choice.loc[no_choices_made_yet, \"choice\"] = \\\n", - " np.where(choice_this_level.apply(is_alternative), choice_this_level, None)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 107, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY DRIVEALONE SHARED2FREE \\\ntrip_id \n137248721 -1.116115 -2855.401829 -0.085418 -0.101729 \n137248725 -0.865187 -2855.150902 0.350254 0.148855 \n211388201 -2855.281895 -5709.567609 -inf -17.128230 \n211388205 -2856.635577 -5710.921291 -inf -17.918686 \n806388401 -2855.821604 -5710.107318 -inf -9.031970 \n806388405 -2857.992149 -5712.277863 -inf -10.161818 \n\n SHARED2PAY SHAREDRIDE2 SHARED3FREE SHARED3PAY SHAREDRIDE3 \\\ntrip_id \n137248721 -2854.387444 0.269616 -2855.394889 -5709.680604 -inf \n137248725 -2854.136859 0.705169 -2855.144442 -5709.430157 -inf \n211388201 -2871.413945 -5.588125 -20.987850 -2875.273564 -6.938992 \n211388205 -2872.204400 -5.973433 -21.553014 -2875.838729 -7.245448 \n806388401 -2863.317684 -3.070264 -10.680802 -2864.966516 -3.647355 \n806388405 -2864.447532 -3.062176 -11.394371 -2865.680085 -3.493570 \n\n AUTO WALK BIKE NONMOTORIZED WALK_LOC \\\ntrip_id \n137248721 0.822679 16.250835 -1367.832304 11.946618 -1970.527596 \n137248725 1.416687 16.732000 -1367.382139 12.573430 -1970.304027 \n211388201 -3.529757 -3.726114 -1380.907142 -2.354947 9.913300 \n211388205 -3.882701 -3.857039 -1381.038067 -2.536786 10.345617 \n806388401 -1.816422 6.302066 -1378.371156 4.610776 12.685721 \n806388405 -1.445835 6.594584 -1377.923639 5.146648 13.052200 \n\n WALK_LRF WALK_EXP WALK_HVY WALK_COM WALKACCESS \\\ntrip_id \n137248721 -3968.527596 -3968.527596 -3968.527596 -3968.527596 -inf \n137248725 -3968.304027 -3968.304027 -3968.304027 -3968.304027 -inf \n211388201 -1987.688271 -1987.688271 -1987.688271 -1987.688271 5.276609 \n211388205 -1987.654383 -1987.654383 -1987.654383 -1987.654383 5.407303 \n806388401 -1984.132938 -1983.673938 -1984.048738 -1984.094938 6.414384 \n806388405 -1983.897400 -1983.438400 -1983.813200 -1983.859400 6.915048 \n\n DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY DRIVE_COM \\\ntrip_id \n137248721 -1970.647702 -3973.145591 -3973.145591 -3974.414085 -3975.433221 \n137248725 -1970.656867 -3975.391993 -3975.391993 -3971.028330 -3977.511255 \n211388201 -1988.046728 -3990.654273 -3990.654273 -1993.837992 -1996.844325 \n211388205 -1987.882804 -3992.966000 -3992.966000 -1990.428019 -1997.126285 \n806388401 -1985.366765 -3989.217824 -3989.217824 -3990.767471 -3993.606059 \n806388405 -1985.037673 -3990.311044 -3990.311044 -3986.801670 -3993.797451 \n\n DRIVEACCESS TRANSIT TAXI TNC_SINGLE TNC_SHARED RIDEHAIL \\\ntrip_id \n137248721 -inf -inf -24.319775 -24.959142 -24.065572 -7.937642 \n137248725 -inf -inf -23.947836 -24.710809 -23.747349 -7.313866 \n211388201 -inf 4.127014 -15.616441 -3.619423 -4.823285 -0.615994 \n211388205 -inf 4.133540 -15.596812 -3.665464 -4.825869 -0.787171 \n806388401 -inf 4.691645 -19.088912 -19.441124 -16.306290 -5.701741 \n806388405 -inf 5.377382 -18.588659 -19.197216 -16.002477 -5.000992 \n\n root choice \ntrip_id \n137248721 11.435801 WALK \n137248725 11.480441 WALK \n211388201 3.456910 WALK_LOC \n211388205 3.643469 WALK_LOC \n806388401 5.193789 WALK_LOC \n806388405 5.134877 WALK_LOC ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYDRIVEALONESHARED2FREESHARED2PAYSHAREDRIDE2SHARED3FREESHARED3PAYSHAREDRIDE3AUTOWALKBIKENONMOTORIZEDWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMWALKACCESSDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMDRIVEACCESSTRANSITTAXITNC_SINGLETNC_SHAREDRIDEHAILrootchoice
trip_id
137248721-1.116115-2855.401829-0.085418-0.101729-2854.3874440.269616-2855.394889-5709.680604-inf0.82267916.250835-1367.83230411.946618-1970.527596-3968.527596-3968.527596-3968.527596-3968.527596-inf-1970.647702-3973.145591-3973.145591-3974.414085-3975.433221-inf-inf-24.319775-24.959142-24.065572-7.93764211.435801WALK
137248725-0.865187-2855.1509020.3502540.148855-2854.1368590.705169-2855.144442-5709.430157-inf1.41668716.732000-1367.38213912.573430-1970.304027-3968.304027-3968.304027-3968.304027-3968.304027-inf-1970.656867-3975.391993-3975.391993-3971.028330-3977.511255-inf-inf-23.947836-24.710809-23.747349-7.31386611.480441WALK
211388201-2855.281895-5709.567609-inf-17.128230-2871.413945-5.588125-20.987850-2875.273564-6.938992-3.529757-3.726114-1380.907142-2.3549479.913300-1987.688271-1987.688271-1987.688271-1987.6882715.276609-1988.046728-3990.654273-3990.654273-1993.837992-1996.844325-inf4.127014-15.616441-3.619423-4.823285-0.6159943.456910WALK_LOC
211388205-2856.635577-5710.921291-inf-17.918686-2872.204400-5.973433-21.553014-2875.838729-7.245448-3.882701-3.857039-1381.038067-2.53678610.345617-1987.654383-1987.654383-1987.654383-1987.6543835.407303-1987.882804-3992.966000-3992.966000-1990.428019-1997.126285-inf4.133540-15.596812-3.665464-4.825869-0.7871713.643469WALK_LOC
806388401-2855.821604-5710.107318-inf-9.031970-2863.317684-3.070264-10.680802-2864.966516-3.647355-1.8164226.302066-1378.3711564.61077612.685721-1984.132938-1983.673938-1984.048738-1984.0949386.414384-1985.366765-3989.217824-3989.217824-3990.767471-3993.606059-inf4.691645-19.088912-19.441124-16.306290-5.7017415.193789WALK_LOC
806388405-2857.992149-5712.277863-inf-10.161818-2864.447532-3.062176-11.394371-2865.680085-3.493570-1.4458356.594584-1377.9236395.14664813.052200-1983.897400-1983.438400-1983.813200-1983.8594006.915048-1985.037673-3990.311044-3990.311044-3986.801670-3993.797451-inf5.377382-18.588659-19.197216-16.002477-5.0009925.134877WALK_LOC
\n
" - }, - "execution_count": 107, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nest_utils_for_choice" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "nest_utils_for_choice = utils_df.copy()\n", - "\n", - "logit.make_choices_ru_frozen(nest_utils_for_choice, nest_spec, trace_label=\"blah\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# OLD\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "id": "2f080150-c15a-4059-8a26-42a8c0072606", - "metadata": { - "execution": { - "iopub.execute_input": "2021-08-31T23:16:40.014024Z", - "iopub.status.busy": "2021-08-31T23:16:40.013770Z", - "iopub.status.idle": "2021-08-31T23:16:40.236326Z", - "shell.execute_reply": "2021-08-31T23:16:40.235553Z", - "shell.execute_reply.started": "2021-08-31T23:16:40.013973Z" - }, - "tags": [] - }, - "source": [ - "### make choice at each level" - ] - }, - { - "cell_type": "code", - "execution_count": 315, - "id": "2ed71b54-67e4-4087-957a-75f4fa184144", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:43.778081Z", - "iopub.status.busy": "2021-09-01T01:11:43.777757Z", - "iopub.status.idle": "2021-09-01T01:11:43.975332Z", - "shell.execute_reply": "2021-09-01T01:11:43.974588Z", - "shell.execute_reply.started": "2021-09-01T01:11:43.778055Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def get_alternatives(nests, name):\n", - " alts = list(filter(lambda x: x.name == name, nests))\n", - " assert len(alts) == 1, f\"{len(alts)} not one\"\n", - " alts = alts[0].alternatives\n", - " return alts\n", - "\n", - "def recursive_choice(row, columns, nest_levels, nests):\n", - " choices = row[columns].idxmax() #axis=1).values[0]\n", - " next_level_columns = get_alternatives(nests, choices)\n", - " #print(f\"{choices} leads to columns {next_level_columns}\")\n", - " if next_level_columns is None:\n", - " return choices \n", - " new_choice = recursive_choice(row, next_level_columns, nest_levels, nests)\n", - " return new_choice\n", - "\n", - "lower_bound = np.finfo(np.float64).eps # chance is very small but let's make it zero. could also check and replace if it ever happened\n", - "\n", - "def make_choice(utils_df, nests, nest_levels, seed=None):\n", - " rng = default_rng(seed=seed)\n", - " rands = rng.uniform(low=lower_bound, high=1.0, size=utils_df.shape[1])\n", - " probs_arr = utils_df - np.log(-np.log(rands))\n", - " choices = probs_arr.apply(lambda x: recursive_choice(x, nest_levels[1], nest_levels, nests), axis=1)\n", - " return choices" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "#rands = pipeline.get_rn_generator().random_for_df(utils_df, n=utils_df.shape[1])\n", - "seed = 9326543345\n", - "make_choice(utils_df, nests_, nest_levels, seed)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 316, - "id": "30424562-2627-446b-abd5-7c763c52060e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T01:11:49.444289Z", - "iopub.status.busy": "2021-09-01T01:11:49.444002Z", - "iopub.status.idle": "2021-09-01T01:11:49.664214Z", - "shell.execute_reply": "2021-09-01T01:11:49.663088Z", - "shell.execute_reply.started": "2021-09-01T01:11:49.444266Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "4\n" - ] - } - ], - "source": [ - "# group nests by level:\n", - "depth = np.max([x.level for x in nests_])\n", - "print(depth)\n", - "nest_levels = {x: [] for x in range(1, depth+1)}\n", - "for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - "assert len(nest_levels[1]) == 1\n", - "assert nest_levels[1][0] == 'root'" - ] - }, - { - "cell_type": "code", - "execution_count": 453, - "id": "806ce752-0927-4d5b-a6cc-68d6c9b8a05e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:55:59.542669Z", - "iopub.status.busy": "2021-09-01T04:55:59.542360Z", - "iopub.status.idle": "2021-09-01T04:55:59.771722Z", - "shell.execute_reply": "2021-09-01T04:55:59.770801Z", - "shell.execute_reply.started": "2021-09-01T04:55:59.542615Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - " nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec)\n", - " nested_probabilities = \\\n", - " simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, trace_label=trace_label)\n", - " # global (flattened) leaf probabilities based on relative nest coefficients (in spec order)\n", - " base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) \n", - " return base_probabilities\n", - "#simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names)" - ] - }, - { - "cell_type": "code", - "execution_count": 451, - "id": "054fd9b1-72fc-49cc-a790-48ef75bcbaed", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:55:08.260140Z", - "iopub.status.busy": "2021-09-01T04:55:08.259914Z", - "iopub.status.idle": "2021-09-01T04:55:09.140095Z", - "shell.execute_reply": "2021-09-01T04:55:09.139119Z", - "shell.execute_reply.started": "2021-09-01T04:55:08.260116Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "trip_id\n", - "86627409 WALK_LRF\n", - "86627413 WALK_LRF\n", - "86673657 WALK_LOC\n", - "86673658 WALK\n", - "86673659 WALK\n", - " ... \n", - "2464446025 WALK\n", - "2464446029 WALK\n", - "2464449633 WALK\n", - "2464449634 WALK\n", - "2464449637 WALK\n", - "Length: 168, dtype: object" - ] - }, - "execution_count": 451, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def simple_simulate_rum(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " utils_df = compute_nested_utilities(raw_utilities, nest_spec)\n", - "\n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices\n", - "\n", - "simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" - ] - }, - { - "cell_type": "code", - "execution_count": 393, - "id": "f3c5c1ba-e9c3-4511-98bf-9ac98c987da7", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:39:52.539983Z", - "iopub.status.busy": "2021-09-01T02:39:52.539668Z", - "iopub.status.idle": "2021-09-01T02:39:52.796170Z", - "shell.execute_reply": "2021-09-01T02:39:52.795498Z", - "shell.execute_reply.started": "2021-09-01T02:39:52.539944Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " segment_trace_label, log_alt_losers=log_alt_losers, \n", - " trace_column_names=trace_column_names, seed=seed)\n", - "\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " choices_list.append(choices)\n", - "\n", - " choices_df = pd.concat(choices_list)\n", - " return choices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 454, - "id": "74eeedd3-4ade-4729-8170-79fa9dcf11f8", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:58:52.825042Z", - "iopub.status.busy": "2021-09-01T04:58:52.824768Z", - "iopub.status.idle": "2021-09-01T04:58:53.042884Z", - "shell.execute_reply": "2021-09-01T04:58:53.042061Z", - "shell.execute_reply.started": "2021-09-01T04:58:52.825004Z" - } - }, - "outputs": [], - "source": [ - "## caculate probabilities with Asim methodology, should be correct\n", - "def gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None):\n", - " full_probs = [] # analytical probs\n", - "\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " #choices = simple_simulate_rum(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " # segment_trace_label, log_alt_losers=log_alt_losers, \n", - " # trace_column_names=trace_column_names, seed=seed)\n", - " probs = simple_simulate_probabilities(trips_segment, spec, nest_spec, locals_dict, \n", - " estimator, segment_trace_label, log_alt_losers, \n", - " trace_column_names)\n", - " full_probs.append(probs)\n", - " probs_df = pd.concat(full_probs)\n", - " return probs_df\n" - ] - }, - { - "cell_type": "code", - "execution_count": 455, - "id": "295a4fd1-8e85-47e2-af82-9f1beac31d63", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T04:59:46.126555Z", - "iopub.status.busy": "2021-09-01T04:59:46.126262Z", - "iopub.status.idle": "2021-09-01T04:59:52.979908Z", - "shell.execute_reply": "2021-09-01T04:59:52.979043Z", - "shell.execute_reply.started": "2021-09-01T04:59:46.126522Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2021-09-01 14:59:46.353007 Start\n", - "2021-09-01 14:59:52.976736 End\n" - ] - } - ], - "source": [ - "print(f\"{datetime.now()} Start\")\n", - "pipeline.open_pipeline(resume_after)\n", - "inject.get_injectable('preload_injectables', None)\n", - "model_name = \"trip_mode_choice\"\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "\n", - "step_name = model_name\n", - "args = {}\n", - "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "chunk_size = 0\n", - "\n", - "trace_label = 'trip_mode_choice'\n", - "model_settings_file_name = 'trip_mode_choice.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - "mode_column_name = 'trip_mode'\n", - "trips_df = trips.to_frame()\n", - "#print(\"Running with %d trips\", trips_df.shape[0])\n", - "tours_merged = tours_merged.to_frame()\n", - "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "# - trips_merged - merge trips and tours_merged\n", - "trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - "assert trips_merged.index.equals(trips.index)\n", - "\n", - "# setup skim keys\n", - "assert ('trip_period' not in trips_merged)\n", - "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - "orig_col = 'origin'\n", - "dest_col = 'destination'\n", - "\n", - "constants = {}\n", - "constants.update(config.get_model_constants(model_settings))\n", - "constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - "})\n", - "\n", - "skim_dict = network_los.get_default_skim_dict()\n", - "\n", - "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "\n", - "skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - "}\n", - "\n", - "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - "nest_spec = config.get_logit_model_settings(model_settings)\n", - "\n", - "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - "\n", - "all_choices = []\n", - "for i in range(100):\n", - " if i % 10 == 0:\n", - " print(f\"{datetime.now()} iteration {i}\")\n", - " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=None)\n", - " all_choices.append(choices_df)\n", - "all_choices = pd.concat(all_choices, axis=1)\n", - "\n", - "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", - " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", - " log_alt_losers=None, trace_column_names=None)\n", - "\n", - "# update trips table with choices (and potionally logssums)\n", - "#trips_df = trips.to_frame()\n", - "#\n", - "#assign_in_place(trips_df, choices_df)\n", - "#assert not trips_df[mode_column_name].isnull().any()\n", - "\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - "print(f\"{datetime.now()} End\")" - ] - }, - { - "cell_type": "code", - "execution_count": 463, - "id": "dc87e904-16d1-44d4-81b7-cafbe4e223c3", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:01:40.901982Z", - "iopub.status.busy": "2021-09-01T05:01:40.901759Z", - "iopub.status.idle": "2021-09-01T05:01:41.337758Z", - "shell.execute_reply": "2021-09-01T05:01:41.336757Z", - "shell.execute_reply.started": "2021-09-01T05:01:40.901946Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#all_choices.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", - "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts = val_counts / all_choices.shape[1]\n", - "#val_counts = val_counts.merge(choices_df_asim[['trip_mode']].rename(columns={'trip_mode': 'asim'}), left_index=True, right_index=True)\n", - "#val_counts['prob_of_asim_choice'] = val_counts.apply(lambda x: x[x.asim], axis=1) # this is what our simulation says w.r.t. to asim choice\n", - "# for 100% and many samples should mostly agree" - ] - }, - { - "cell_type": "code", - "execution_count": 464, - "id": "09bf151c-a39a-4823-acfd-9c8ae747c338", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:01:46.119117Z", - "iopub.status.busy": "2021-09-01T05:01:46.118849Z", - "iopub.status.idle": "2021-09-01T05:01:46.354114Z", - "shell.execute_reply": "2021-09-01T05:01:46.353248Z", - "shell.execute_reply.started": "2021-09-01T05:01:46.119092Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "#val_counts['prob_of_asim_choice'].hist(bins=100);" - ] - }, - { - "cell_type": "code", - "execution_count": 490, - "id": "f3b6ade9-d875-4104-b438-b53ef42e342c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T05:10:08.653227Z", - "iopub.status.busy": "2021-09-01T05:10:08.652978Z", - "iopub.status.idle": "2021-09-01T05:10:08.915620Z", - "shell.execute_reply": "2021-09-01T05:10:08.914513Z", - "shell.execute_reply.started": "2021-09-01T05:10:08.653189Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", - "analytical 3.152 0.852 0.0 0.0 0.0 \n", - "experiment 3.243 0.880 0.0 0.0 0.0 \n", - "\n", - " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", - "analytical 0.0 0.0 0.0 0.685 0.0 \n", - "experiment 0.0 0.0 0.0 0.772 0.0 \n", - "\n", - " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK \\\n", - "analytical 0.129 0.0 0.182 0.260 1.334 63.708 \n", - "experiment 0.156 0.0 0.174 0.259 1.434 63.243 \n", - "\n", - " WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", - "analytical 0.0 0.0 0.455 18.355 10.887 \n", - "experiment 0.0 0.0 0.394 18.639 10.807 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mode_share_analytical = (probs_nl.sum(axis=0) / probs_nl.shape[0]).to_frame('analytical')\n", - "assert np.allclose(mode_share_analytical.sum(), 1)\n", - "mode_share_rum = (val_counts.sum(axis=0) / val_counts.shape[0]).to_frame('experiment')\n", - "assert np.allclose(mode_share_rum.sum(), 1)\n", - "full_share = mode_share_analytical.join(mode_share_rum, how='outer').fillna(0)\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * full_share).T)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e48a56e-d613-4dfd-bef6-62bee524f12f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50914e1d-750d-440c-9617-f96bd2a46c56", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f2d70e0e-520e-49a6-8024-ae31345f6ead", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "f9e8ef02-1d57-4b21-8aef-25a6e1095c02", - "metadata": {}, - "source": [ - "### try zenith normalisation of simple_simulate_rum" - ] - }, - { - "cell_type": "code", - "execution_count": 629, - "id": "db235e90-d2e5-4e90-b41c-6d90a670e41d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:24.045881Z", - "iopub.status.busy": "2021-09-01T11:37:24.045637Z", - "iopub.status.idle": "2021-09-01T11:37:24.276488Z", - "shell.execute_reply": "2021-09-01T11:37:24.274898Z", - "shell.execute_reply.started": "2021-09-01T11:37:24.045844Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation belowi s equivalent.\n", - "def compute_nested_utilities_zenith(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) # / nest.product_of_coefficients #coefficient\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", - " \n", - " # now go over all leaves and correct for scale\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] /= nest.coefficient\n", - " \n", - " return nested_utilities\n", - "\n", - "\n", - "def simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " utils_df = compute_nested_utilities_zenith(raw_utilities, nest_spec)\n", - "\n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices\n", - "\n", - "#simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=1233974)" - ] - }, - { - "cell_type": "code", - "execution_count": 630, - "id": "f69bc3dc-84e4-4dd5-9a57-c053cadba4b5", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:24.828926Z", - "iopub.status.busy": "2021-09-01T11:37:24.828707Z", - "iopub.status.idle": "2021-09-01T11:37:25.046409Z", - "shell.execute_reply": "2021-09-01T11:37:25.045399Z", - "shell.execute_reply.started": "2021-09-01T11:37:24.828902Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=None, log_alt_losers=None, trace_column_names=None, seed=None):\n", - " choices_list = []\n", - " for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'):\n", - " #print(\"trip_mode_choice tour_type '%s' (%s trips)\" %\n", - " # (primary_purpose, len(trips_segment.index), ))\n", - " # name index so tracing knows how to slice\n", - " assert trips_segment.index.name == 'trip_id'\n", - "\n", - " coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose)\n", - "\n", - " locals_dict = {}\n", - " locals_dict.update(constants)\n", - " locals_dict.update(coefficients)\n", - "\n", - " segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose)\n", - "\n", - " expressions.annotate_preprocessors(\n", - " trips_segment, locals_dict, skims,\n", - " model_settings, segment_trace_label)\n", - "\n", - " locals_dict.update(skims)\n", - "\n", - " spec=simulate.eval_coefficients(model_spec, coefficients, estimator)\n", - " nest_spec = simulate.eval_nest_coefficients(nest_spec, coefficients, segment_trace_label)\n", - " choices = simple_simulate_rum_zenith(trips_segment, spec, nest_spec, locals_dict, estimator, \n", - " segment_trace_label, log_alt_losers=log_alt_losers, \n", - " trace_column_names=trace_column_names, seed=seed)\n", - "\n", - " # for consistency, always return dataframe, whether or not logsums were requested\n", - " if isinstance(choices, pd.Series):\n", - " choices = choices.to_frame('choice')\n", - " choices.rename(columns={'logsum': logsum_column_name,\n", - " 'choice': mode_column_name},\n", - " inplace=True)\n", - " choices_list.append(choices)\n", - "\n", - " choices_df = pd.concat(choices_list)\n", - " return choices_df" - ] - }, - { - "cell_type": "code", - "execution_count": 631, - "id": "8c4f4fb1-c4fb-4a05-9be4-8b9d7f25d1e6", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:37:32.250808Z", - "iopub.status.busy": "2021-09-01T11:37:32.250548Z", - "iopub.status.idle": "2021-09-01T20:20:35.257463Z", - "shell.execute_reply": "2021-09-01T20:20:35.255964Z", - "shell.execute_reply.started": "2021-09-01T11:37:32.250782Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "num_samples = 10\n", - "\n", - "rng_test = default_rng(23423)\n", - "\n", - "print(f\"{datetime.now()} Start\")\n", - "pipeline.open_pipeline(resume_after)\n", - "inject.get_injectable('preload_injectables', None)\n", - "model_name = \"trip_mode_choice\"\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "\n", - "step_name = model_name\n", - "args = {}\n", - "checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "chunk_size = 0\n", - "\n", - "trace_label = 'trip_mode_choice'\n", - "model_settings_file_name = 'trip_mode_choice.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - "mode_column_name = 'trip_mode'\n", - "trips_df = trips.to_frame()\n", - "#print(\"Running with %d trips\", trips_df.shape[0])\n", - "tours_merged = tours_merged.to_frame()\n", - "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "# - trips_merged - merge trips and tours_merged\n", - "trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - "assert trips_merged.index.equals(trips.index)\n", - "\n", - "# setup skim keys\n", - "assert ('trip_period' not in trips_merged)\n", - "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "\n", - "orig_col = 'origin'\n", - "dest_col = 'destination'\n", - "\n", - "constants = {}\n", - "constants.update(config.get_model_constants(model_settings))\n", - "constants.update({\n", - " 'ORIGIN': orig_col,\n", - " 'DESTINATION': dest_col\n", - "})\n", - "\n", - "skim_dict = network_los.get_default_skim_dict()\n", - "odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col,\n", - " dim3_key='trip_period')\n", - "dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col,\n", - " dim3_key='trip_period')\n", - "od_skim_wrapper = skim_dict.wrap('origin', 'destination')\n", - "skims = {\n", - " \"odt_skims\": odt_skim_stack_wrapper,\n", - " \"dot_skims\": dot_skim_stack_wrapper,\n", - " \"od_skims\": od_skim_wrapper,\n", - "}\n", - "model_spec = simulate.read_model_spec(file_name=model_settings['SPEC'])\n", - "nest_spec = config.get_logit_model_settings(model_settings)\n", - "estimator = estimation.manager.begin_estimation('trip_mode_choice')\n", - "\n", - "all_choices_zenith = []\n", - "all_choices = []\n", - "for i in range(num_samples):\n", - " \n", - " seed = rng_test.integers(0, 100000) #int(9.3 * (i+1)**3) # why not\n", - " \n", - " if i % 50 == 0:\n", - " print(f\"{datetime.now()} iteration {i}\")\n", - " choices_df_zenith = stuff_zenith(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", - " all_choices_zenith.append(choices_df_zenith)\n", - "\n", - " choices_df = stuff(trips_merged, model_settings, constants, skims, model_spec, nest_spec, estimator, logsum_column_name, mode_column_name, \n", - " trace_label=trace_label, log_alt_losers=None, trace_column_names=None, seed=seed)\n", - " all_choices.append(choices_df)\n", - " \n", - " t_ = choices_df_zenith.merge(choices_df, left_index=True, right_index=True, suffixes=['_zenith', '_asim'])\n", - " diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", - " \n", - " #print(f\"seed {seed} leads to {diffs.shape[0]} differences. tripids {diffs.index}\")\n", - "\n", - "all_choices_zenith = pd.concat(all_choices_zenith, axis=1)\n", - "all_choices = pd.concat(all_choices, axis=1)\n", - "\n", - "probs_nl = gimme_probabilities(trips_merged, model_settings, constants, skims, model_spec, nest_spec,\n", - " estimator, logsum_column_name, mode_column_name,trace_label=trace_label, \n", - " log_alt_losers=None, trace_column_names=None)\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()\n", - "\n", - "print(f\"{datetime.now()} End\")" - ] - }, - { - "cell_type": "code", - "execution_count": 643, - "id": "3abd7949-dc9c-469b-845e-26c17741f70e", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:33:21.533110Z", - "iopub.status.busy": "2021-09-01T20:33:21.531915Z", - "iopub.status.idle": "2021-09-01T20:33:21.740946Z", - "shell.execute_reply": "2021-09-01T20:33:21.739713Z", - "shell.execute_reply.started": "2021-09-01T20:33:21.533074Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Have 2038 samples\n" - ] - } - ], - "source": [ - "print(f\"Have {all_choices_zenith.shape[1]} samples\")" - ] - }, - { - "cell_type": "code", - "execution_count": 633, - "id": "8762bd6d-ac04-458c-869f-be86d8297351", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:21:27.778803Z", - "iopub.status.busy": "2021-09-01T20:21:27.778531Z", - "iopub.status.idle": "2021-09-01T20:21:28.675645Z", - "shell.execute_reply": "2021-09-01T20:21:28.674734Z", - "shell.execute_reply.started": "2021-09-01T20:21:27.778754Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "val_counts_zenith = all_choices_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts_zenith = val_counts_zenith / all_choices_zenith.shape[1]\n", - "\n", - "val_counts = all_choices.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "val_counts = val_counts / all_choices.shape[1]" - ] - }, - { - "cell_type": "code", - "execution_count": 634, - "id": "0f566105-9424-457b-86f5-d60c1d63aac5", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:21:30.954894Z", - "iopub.status.busy": "2021-09-01T20:21:30.954654Z", - "iopub.status.idle": "2021-09-01T20:21:31.179689Z", - "shell.execute_reply": "2021-09-01T20:21:31.178304Z", - "shell.execute_reply.started": "2021-09-01T20:21:30.954867Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
analytical3.1520.8520.00.00.00.00.00.00.6850.00.1290.00.1820.2601.33463.7080.00.00.45518.35510.887
experiment3.2430.8800.00.00.00.00.00.00.7720.00.1560.00.1740.2591.43463.2430.00.00.39418.63910.807
experiment_zenith3.1500.8740.00.00.00.00.00.00.6800.00.1360.00.1540.2361.28563.7670.00.00.51518.82410.379
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP \\\n", - "analytical 3.152 0.852 0.0 0.0 0.0 \n", - "experiment 3.243 0.880 0.0 0.0 0.0 \n", - "experiment_zenith 3.150 0.874 0.0 0.0 0.0 \n", - "\n", - " DRIVE_HVY DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY \\\n", - "analytical 0.0 0.0 0.0 0.685 0.0 \n", - "experiment 0.0 0.0 0.0 0.772 0.0 \n", - "experiment_zenith 0.0 0.0 0.0 0.680 0.0 \n", - "\n", - " SHARED3FREE SHARED3PAY TAXI TNC_SHARED TNC_SINGLE \\\n", - "analytical 0.129 0.0 0.182 0.260 1.334 \n", - "experiment 0.156 0.0 0.174 0.259 1.434 \n", - "experiment_zenith 0.136 0.0 0.154 0.236 1.285 \n", - "\n", - " WALK WALK_COM WALK_EXP WALK_HVY WALK_LOC WALK_LRF \n", - "analytical 63.708 0.0 0.0 0.455 18.355 10.887 \n", - "experiment 63.243 0.0 0.0 0.394 18.639 10.807 \n", - "experiment_zenith 63.767 0.0 0.0 0.515 18.824 10.379 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mode_share_rum_zenith = (val_counts_zenith.sum(axis=0) / val_counts_zenith.shape[0]).to_frame('experiment_zenith')\n", - "assert np.allclose(mode_share_rum_zenith.sum(), 1)\n", - "full_share_incl_zenith = full_share.merge(mode_share_rum_zenith, left_index=True, right_index=True, how='outer').fillna(0)\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * full_share_incl_zenith).T)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c0d3081-b45b-4573-9246-e4a9d39591dd", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "612b2f04-45e2-4eb2-b52a-9f753427877f", - "metadata": {}, - "source": [ - "## investigate diverging seed and look at diff in formulation" - ] - }, - { - "cell_type": "code", - "execution_count": 619, - "id": "ac09dc75-a92f-43fb-a0cd-fe7de1f8b66d", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:12:02.988141Z", - "iopub.status.busy": "2021-09-01T11:12:02.987900Z", - "iopub.status.idle": "2021-09-01T11:12:03.212179Z", - "shell.execute_reply": "2021-09-01T11:12:03.210993Z", - "shell.execute_reply.started": "2021-09-01T11:12:02.988102Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# TODO: work out if our formulation and formulation based on asim probabilities is equivalent\n", - "def compute_nested_utilities_asim(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) / nest.product_of_coefficients\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1))\n", - " return nested_utilities\n", - "\n", - "def compute_nested_utilities_zenith_check(raw_utilities, nest_spec):\n", - " nested_utilities = pd.DataFrame(index=raw_utilities.index)\n", - " for nest in logit.each_nest(nest_spec, post_order=True):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] = \\\n", - " raw_utilities[name].astype(float) # scale correction is below\n", - " else:\n", - " with np.errstate(divide='ignore'):\n", - " nested_utilities[name] = \\\n", - " nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives] / nest.coefficient).sum(axis=1))\n", - " \n", - " # now go over all leaves and correct for scale\n", - " for nest in logit.each_nest(nest_spec):\n", - " name = nest.name\n", - " if nest.is_leaf:\n", - " nested_utilities[name] /= nest.coefficient\n", - " \n", - " return nested_utilities" - ] - }, - { - "cell_type": "code", - "execution_count": 688, - "id": "4b7c6368-21ca-4442-969e-f2f8ff868842", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T20:49:56.958862Z", - "iopub.status.busy": "2021-09-01T20:49:56.958623Z", - "iopub.status.idle": "2021-09-01T20:49:57.191884Z", - "shell.execute_reply": "2021-09-01T20:49:57.190975Z", - "shell.execute_reply.started": "2021-09-01T20:49:56.958827Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "def simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_d, estimator, tr_label, log_alt_losers, trace_column_names, custom_chooser=None, seed=None, use_zenith=True, raw_utilities=None):\n", - " trace_label = tracing.extend_trace_label(tr_label, 'eval_nl')\n", - " logit.validate_nest_spec(nest_spec, trace_label)\n", - " \n", - " if raw_utilities is None:\n", - " raw_utilities = simulate.eval_utilities(spec, trips_segment, locals_d,\n", - " log_alt_losers=log_alt_losers,\n", - " trace_label=trace_label, have_trace_targets=False,\n", - " estimator=estimator, trace_column_names=trace_column_names)\n", - "\n", - " if use_zenith:\n", - " utils_df = compute_nested_utilities_zenith_check(raw_utilities, nest_spec)\n", - " else:\n", - " utils_df = compute_nested_utilities_asim(raw_utilities, nest_spec)\n", - "\n", - " # test\n", - " #return compute_nested_utilities_zenith_check(raw_utilities, nest_spec), compute_nested_utilities_asim(raw_utilities, nest_spec)\n", - " \n", - " nests_ = list(logit.each_nest(nest_spec))\n", - " # group nests by level:\n", - " depth = np.max([x.level for x in nests_])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in nests_:\n", - " nest_levels[n.level].append(n.name)\n", - " assert len(nest_levels[1]) == 1\n", - " assert nest_levels[1][0] == 'root'\n", - " # make choices\n", - " choices = make_choice(utils_df, nests_, nest_levels, seed)\n", - "\n", - " return choices" - ] - }, - { - "cell_type": "code", - "execution_count": 626, - "id": "bd6cadc3-79f5-4824-b55e-639c39d5ab18", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:13:12.077886Z", - "iopub.status.busy": "2021-09-01T11:13:12.077652Z", - "iopub.status.idle": "2021-09-01T11:13:12.290222Z", - "shell.execute_reply": "2021-09-01T11:13:12.288861Z", - "shell.execute_reply.started": "2021-09-01T11:13:12.077863Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# t_z, t_a = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed)\n", - "# tr_id = 86673661\n", - "# display(t_z.loc[t_z.index==tr_id])\n", - "# display(t_a.loc[t_a.index==tr_id])" - ] - }, - { - "cell_type": "code", - "execution_count": 625, - "id": "42e6da62-58e8-4b16-9169-9faa1fc8a162", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T11:13:06.847583Z", - "iopub.status.busy": "2021-09-01T11:13:06.847204Z", - "iopub.status.idle": "2021-09-01T11:13:08.401700Z", - "shell.execute_reply": "2021-09-01T11:13:08.400711Z", - "shell.execute_reply.started": "2021-09-01T11:13:06.847554Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "seed 51006 leads to 34 differences out of 168. tripids Int64Index([ 86673657, 86673661, 106741681, 106741682, 106741685,\n", - " 106741686, 106741687, 106741688, 211327433, 211327437,\n", - " 444793574, 484173905, 484173909, 535170694, 535620053,\n", - " 708171014, 943749470, 943749471, 1060575853, 1091770617,\n", - " 1146472489, 1146472493, 1276281769, 1276281773, 1658748793,\n", - " 1658748797, 1767013726, 1767186577, 1767186578, 1768237161,\n", - " 1768237165, 1768237166, 2463663417, 2463663421],\n", - " dtype='int64', name='trip_id')\n" - ] - } - ], - "source": [ - "seed = 51006\n", - "x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True)\n", - "y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False)\n", - "t_ = x_.to_frame('trip_mode_zenith').merge(y_.to_frame('trip_mode_asim'), left_index=True, right_index=True)\n", - "diffs = t_.loc[t_.trip_mode_zenith != t_.trip_mode_asim]\n", - "print(f\"seed {seed} leads to {diffs.shape[0]} differences out of {t_.shape[0]}. tripids {diffs.index}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2f3c0506-a79d-4a31-9d56-71b9966d0a92", - "metadata": {}, - "outputs": [], - "source": [ - "seed 51006 leads to 3 differences. tripids Int64Index([86673661, 535170689, 1060575849], dtype='int64', name='trip_id')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1a5f332a-b1d7-4a80-bb21-d37a1611cf9f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "24c5e4a1-6142-49d4-8da8-a1e266b3cb9b", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 753, - "id": "2098907f-28af-4f53-af74-6aaee8a6c53c", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T23:04:23.861048Z", - "iopub.status.busy": "2021-09-01T23:04:23.860748Z", - "iopub.status.idle": "2021-09-01T23:04:24.069307Z", - "shell.execute_reply": "2021-09-01T23:04:24.068434Z", - "shell.execute_reply.started": "2021-09-01T23:04:23.861024Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "rng_ = default_rng(seed=100)\n", - "new_utils = pd.DataFrame(0.1 * -np.log(-np.log(rng_.uniform(0,1,raw_utilities.shape))), columns=raw_utilities.columns)\n", - "new_utils.index = raw_utilities.index" - ] - }, - { - "cell_type": "code", - "execution_count": 767, - "id": "da48a178-82b2-44a5-b45a-e9de3bb5e5c2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T23:17:20.369675Z", - "iopub.status.busy": "2021-09-01T23:17:20.369424Z", - "iopub.status.idle": "2021-09-01T23:17:20.666506Z", - "shell.execute_reply": "2021-09-01T23:17:20.665626Z", - "shell.execute_reply.started": "2021-09-01T23:17:20.369637Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
1372487210.0566420.0373050.0427800.0295760.0861540.0265860.0883130.1003310.0280210.0185160.0643910.0200090.0263990.0226220.0230070.0217540.0871260.0211710.0364750.0276500.135171
1372487250.1019020.0228760.0312270.0699900.0569130.0466850.0884110.0877850.0254140.0381620.0239880.0288000.0393180.0201600.0483640.0230030.0339590.0345970.0377080.0995230.041213
2113882010.0375620.0700330.0559510.0403890.0200410.0959740.0946780.0943680.0360140.0250510.0348600.0349530.0354990.0280400.0436500.0247130.0313880.0259310.0669990.0526780.051228
2113882050.0354800.0562560.0439440.0480850.0420780.0622210.1068190.0872650.0351170.0303990.0351540.0264690.0506460.0357270.0242840.0329050.0394270.0410920.0547140.0484590.063458
8063884010.0549180.0404880.0331490.0542940.0434770.0679820.1524650.0741520.0334040.0287350.0592690.0243390.0369680.0277600.0235220.0609320.0234450.0239310.0446430.0337730.058353
8063884050.0327640.0573010.0184370.0770990.0469630.0635210.0949510.0890720.0308260.0378960.0357520.0198780.0495360.0796100.0269220.0248330.0248400.0266950.0799360.0401460.043022
\n", - "
" - ], - "text/plain": [ - " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n", - "trip_id \n", - "137248721 0.056642 0.037305 0.042780 0.029576 \n", - "137248725 0.101902 0.022876 0.031227 0.069990 \n", - "211388201 0.037562 0.070033 0.055951 0.040389 \n", - "211388205 0.035480 0.056256 0.043944 0.048085 \n", - "806388401 0.054918 0.040488 0.033149 0.054294 \n", - "806388405 0.032764 0.057301 0.018437 0.077099 \n", - "\n", - " SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\n", - "trip_id \n", - "137248721 0.086154 0.026586 0.088313 0.100331 0.028021 0.018516 \n", - "137248725 0.056913 0.046685 0.088411 0.087785 0.025414 0.038162 \n", - "211388201 0.020041 0.095974 0.094678 0.094368 0.036014 0.025051 \n", - "211388205 0.042078 0.062221 0.106819 0.087265 0.035117 0.030399 \n", - "806388401 0.043477 0.067982 0.152465 0.074152 0.033404 0.028735 \n", - "806388405 0.046963 0.063521 0.094951 0.089072 0.030826 0.037896 \n", - "\n", - " WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\n", - "trip_id \n", - "137248721 0.064391 0.020009 0.026399 0.022622 0.023007 0.021754 \n", - "137248725 0.023988 0.028800 0.039318 0.020160 0.048364 0.023003 \n", - "211388201 0.034860 0.034953 0.035499 0.028040 0.043650 0.024713 \n", - "211388205 0.035154 0.026469 0.050646 0.035727 0.024284 0.032905 \n", - "806388401 0.059269 0.024339 0.036968 0.027760 0.023522 0.060932 \n", - "806388405 0.035752 0.019878 0.049536 0.079610 0.026922 0.024833 \n", - "\n", - " DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n", - "trip_id \n", - "137248721 0.087126 0.021171 0.036475 0.027650 0.135171 \n", - "137248725 0.033959 0.034597 0.037708 0.099523 0.041213 \n", - "211388201 0.031388 0.025931 0.066999 0.052678 0.051228 \n", - "211388205 0.039427 0.041092 0.054714 0.048459 0.063458 \n", - "806388401 0.023445 0.023931 0.044643 0.033773 0.058353 \n", - "806388405 0.024840 0.026695 0.079936 0.040146 0.043022 " - ] - }, - "execution_count": 767, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nested_exp_utilities_cf = simulate.compute_nested_exp_utilities(new_utils, nest_spec)\n", - "nested_probabilities_cf = simulate.compute_nested_probabilities(nested_exp_utilities_cf, nest_spec, trace_label=None)\n", - "base_probabilities_cf = simulate.compute_base_probabilities(nested_probabilities_cf, nest_spec, spec)\n", - "base_probabilities_cf" - ] - }, - { - "cell_type": "code", - "execution_count": 799, - "id": "50dfd1ae-10c3-475c-94be-de783c2fa5c2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:15:29.056902Z", - "iopub.status.busy": "2021-09-02T00:15:29.056683Z", - "iopub.status.idle": "2021-09-02T00:27:21.965206Z", - "shell.execute_reply": "2021-09-02T00:27:21.964340Z", - "shell.execute_reply.started": "2021-09-02T00:15:29.056877Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 11min 49s, sys: 766 ms, total: 11min 50s\n", - "Wall time: 11min 52s\n" - ] - } - ], - "source": [ - "%%time\n", - "\n", - "num_samples = 10000 # 7.5s per 100\n", - "\n", - "data_zenith = []\n", - "data_asim = []\n", - "for i in range(num_samples):\n", - " seed = rng_.integers(0, 100000)\n", - " x_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=True, raw_utilities=new_utils)\n", - " y_ = simple_simulate_rum_zenith_check(trips_segment, spec, nest_spec, locals_dict, estimator, tr_label, log_alt_losers, trace_column_names, seed=seed, use_zenith=False, raw_utilities=new_utils)\n", - " data_zenith.append(x_)\n", - " data_asim.append(y_)\n", - " \n", - "data_asim = pd.concat(data_asim, axis=1)\n", - "data_zenith = pd.concat(data_zenith, axis=1)\n", - "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", - "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_asim = counts_asim / data_asim.shape[1]\n", - "\n", - "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", - "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", - "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", - "# assert np.allclose(mode_share_zenith.sum(), 1)\n", - "# assert np.allclose(mode_share_asim.sum(), 1)\n", - "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", - "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 796, - "id": "bb16f9d7-44f5-4316-b981-d65216a5e217", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:14:11.772592Z", - "iopub.status.busy": "2021-09-02T00:14:11.772347Z", - "iopub.status.idle": "2021-09-02T00:14:12.001077Z", - "shell.execute_reply": "2021-09-02T00:14:12.000166Z", - "shell.execute_reply.started": "2021-09-02T00:14:11.772556Z" - } - }, - "outputs": [], - "source": [ - "#temp_z = data_zenith.copy()\n", - "#temp_a = data_asim.copy()\n", - "#data_asim = data_asim.join(temp_a, lsuffix=\"_o\", rsuffix=\"_n\")\n", - "#data_zenith = data_zenith.join(temp_z, lsuffix=\"_o\", rsuffix=\"_n\")\n", - "# counts_zenith = data_zenith.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_zenith = counts_zenith / data_zenith.shape[1]\n", - "# counts_asim = data_asim.apply(lambda x: x.value_counts(), axis=1).fillna(0)\n", - "# counts_asim = counts_asim / data_asim.shape[1]\n", - "\n", - "# mode_share_zenith = (counts_zenith.sum(axis=0) / counts_zenith.shape[0]).to_frame('zenith')\n", - "# mode_share_asim = (counts_asim.sum(axis=0) / counts_asim.shape[0]).to_frame('asim')\n", - "# mode_share_base_prob = (base_probabilities_cf.sum(axis=0) / base_probabilities_cf.shape[0]).to_frame('probs')\n", - "# assert np.allclose(mode_share_zenith.sum(), 1)\n", - "# assert np.allclose(mode_share_asim.sum(), 1)\n", - "# assert np.allclose(mode_share_base_prob.sum(), 1)\n", - "# mode_share_comp = mode_share_zenith.join(mode_share_asim, how='outer').join(mode_share_base_prob, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 805, - "id": "031d8340-0af0-4608-8a83-ed2af4aaa3b2", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:41:01.948669Z", - "iopub.status.busy": "2021-09-02T00:41:01.948422Z", - "iopub.status.idle": "2021-09-02T00:41:02.175972Z", - "shell.execute_reply": "2021-09-02T00:41:02.174759Z", - "shell.execute_reply.started": "2021-09-02T00:41:01.948633Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BIKEDRIVEALONEFREEDRIVEALONEPAYDRIVE_COMDRIVE_EXPDRIVE_HVYDRIVE_LOCDRIVE_LRFSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYTAXITNC_SHAREDTNC_SINGLEWALKWALK_COMWALK_EXPWALK_HVYWALK_LOCWALK_LRF
zenith8.0555.1674.6313.5023.6064.2663.9633.4983.8955.1764.7995.8514.9996.1214.6689.4864.2284.1252.9773.6713.317
asim6.0966.6984.9773.2023.5224.3344.1293.1843.2245.1496.2838.2964.7936.5844.5327.4844.1524.2732.6293.4333.023
probs8.8835.3214.7382.8903.1364.0033.5653.1623.7585.3244.9276.0495.3416.5415.03710.4273.9734.2242.5743.1472.979
\n", - "
" - ], - "text/plain": [ - " BIKE DRIVEALONEFREE DRIVEALONEPAY DRIVE_COM DRIVE_EXP DRIVE_HVY \\\n", - "zenith 8.055 5.167 4.631 3.502 3.606 4.266 \n", - "asim 6.096 6.698 4.977 3.202 3.522 4.334 \n", - "probs 8.883 5.321 4.738 2.890 3.136 4.003 \n", - "\n", - " DRIVE_LOC DRIVE_LRF SHARED2FREE SHARED2PAY SHARED3FREE \\\n", - "zenith 3.963 3.498 3.895 5.176 4.799 \n", - "asim 4.129 3.184 3.224 5.149 6.283 \n", - "probs 3.565 3.162 3.758 5.324 4.927 \n", - "\n", - " SHARED3PAY TAXI TNC_SHARED TNC_SINGLE WALK WALK_COM WALK_EXP \\\n", - "zenith 5.851 4.999 6.121 4.668 9.486 4.228 4.125 \n", - "asim 8.296 4.793 6.584 4.532 7.484 4.152 4.273 \n", - "probs 6.049 5.341 6.541 5.037 10.427 3.973 4.224 \n", - "\n", - " WALK_HVY WALK_LOC WALK_LRF \n", - "zenith 2.977 3.671 3.317 \n", - "asim 2.629 3.433 3.023 \n", - "probs 2.574 3.147 2.979 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# sample of 20000\n", - "with pd.option_context(\"precision\", 3):\n", - " display((100.0 * mode_share_comp).T)" - ] - }, - { - "cell_type": "code", - "execution_count": 806, - "id": "aece448f-4055-4d2a-b90d-8530caec9cb9", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-02T00:43:32.971972Z", - "iopub.status.busy": "2021-09-02T00:43:32.971684Z", - "iopub.status.idle": "2021-09-02T00:43:33.223345Z", - "shell.execute_reply": "2021-09-02T00:43:33.222488Z", - "shell.execute_reply.started": "2021-09-02T00:43:32.971932Z" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
trip_id137248721_z137248721_a137248721
BIKE0.092700.054350.100331
DRIVEALONEFREE0.053650.050050.056642
DRIVEALONEPAY0.038800.031550.037305
DRIVE_COM0.028700.024700.021171
DRIVE_EXP0.028800.026050.021754
DRIVE_HVY0.078150.099700.087126
DRIVE_LOC0.028750.027000.022622
DRIVE_LRF0.027800.024450.023007
SHARED2FREE0.043250.023000.042780
SHARED2PAY0.033600.015850.029576
SHARED3FREE0.073650.119200.086154
SHARED3PAY0.032600.039600.026586
TAXI0.035100.050300.036475
TNC_SHARED0.126200.181150.135171
TNC_SINGLE0.026250.038450.027650
WALK0.079400.048250.088313
WALK_COM0.031850.024600.026399
WALK_EXP0.057050.058950.064391
WALK_HVY0.025200.018400.020009
WALK_LOC0.034550.027400.028021
WALK_LRF0.023950.017000.018516
\n", - "
" - ], - "text/plain": [ - "trip_id 137248721_z 137248721_a 137248721\n", - "BIKE 0.09270 0.05435 0.100331\n", - "DRIVEALONEFREE 0.05365 0.05005 0.056642\n", - "DRIVEALONEPAY 0.03880 0.03155 0.037305\n", - "DRIVE_COM 0.02870 0.02470 0.021171\n", - "DRIVE_EXP 0.02880 0.02605 0.021754\n", - "DRIVE_HVY 0.07815 0.09970 0.087126\n", - "DRIVE_LOC 0.02875 0.02700 0.022622\n", - "DRIVE_LRF 0.02780 0.02445 0.023007\n", - "SHARED2FREE 0.04325 0.02300 0.042780\n", - "SHARED2PAY 0.03360 0.01585 0.029576\n", - "SHARED3FREE 0.07365 0.11920 0.086154\n", - "SHARED3PAY 0.03260 0.03960 0.026586\n", - "TAXI 0.03510 0.05030 0.036475\n", - "TNC_SHARED 0.12620 0.18115 0.135171\n", - "TNC_SINGLE 0.02625 0.03845 0.027650\n", - "WALK 0.07940 0.04825 0.088313\n", - "WALK_COM 0.03185 0.02460 0.026399\n", - "WALK_EXP 0.05705 0.05895 0.064391\n", - "WALK_HVY 0.02520 0.01840 0.020009\n", - "WALK_LOC 0.03455 0.02740 0.028021\n", - "WALK_LRF 0.02395 0.01700 0.018516" - ] - }, - "execution_count": 806, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# TODO: look at probs per trip, i.e. do not sum across trips\n", - "trip_id = 137248721\n", - "\n", - "counts_zenith.loc[counts_zenith.index == trip_id].T.merge(\n", - " counts_asim.loc[counts_asim.index == trip_id].T, suffixes=['_z', '_a'], left_index=True, right_index=True, how='outer').merge(\n", - " base_probabilities_cf.loc[base_probabilities_cf.index == trip_id].T, suffixes=['', '_probs'], left_index=True, right_index=True, how='outer').fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ef84f10-d616-40e4-87e5-425499027ff3", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "431cee49-40a9-4dc7-93a5-75308fb302dd", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1fcdc0ff-c510-4878-8672-8365e046442c", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/notebooks/fru_utils.py b/notebooks/fru_utils.py deleted file mode 100644 index 77a4e920c2..0000000000 --- a/notebooks/fru_utils.py +++ /dev/null @@ -1,582 +0,0 @@ -import os -from datetime import datetime - -import numpy as np -import pandas as pd - -from activitysim.core import inject -from activitysim.core import tracing -from activitysim.core import config -from activitysim.core import pipeline -from activitysim.core import chunk -from activitysim.core import simulate -from activitysim.core import logit -from activitysim.abm.models.util import estimation -from activitysim.core import expressions -from activitysim.core.util import assign_in_place - - - - - -def mode_choice_for_trip(choose_individual_max_utility, trip_id_to_check, num_samples, trips_merged=None): - """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" - resume_after = "trip_scheduling" - model_name = "trip_mode_choice" - chunk_size = 0 # test_mtc means no chunking - - pipeline.open_pipeline(resume_after) - # preload any bulky injectables (e.g. skims) not in pipeline - inject.get_injectable('preload_injectables', None) - pipeline._PIPELINE.rng().begin_step(model_name) - #step_name = model_name - args = {} - #checkpoint = pipeline.intermediate_checkpoint(model_name) - inject.set_step_args(args) - - network_los = inject.get_injectable('network_los') - - trace_label = 'trip_mode_choice' - model_settings_file_name = 'trip_mode_choice.yaml' - model_settings = config.read_model_settings(model_settings_file_name) - - logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME') - mode_column_name = 'trip_mode' - - if trips_merged is None: - trips = inject.get_table('trips') - tours_merged = inject.get_table('tours_merged') - trips_df = trips.to_frame() - tours_merged = tours_merged.to_frame() - tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] - trips_merged = pd.merge( - trips_df, - tours_merged, - left_on='tour_id', - right_index=True, - how="left") - assert trips_merged.index.equals(trips.index) - assert ('trip_period' not in trips_merged) - trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) - - orig_col = 'origin' - dest_col = 'destination' - - constants = {} - constants.update(config.get_model_constants(model_settings)) - constants.update({ - 'ORIGIN': orig_col, - 'DESTINATION': dest_col - }) - - skim_dict = network_los.get_default_skim_dict() - - odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col, - dim3_key='trip_period') - dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col, - dim3_key='trip_period') - od_skim_wrapper = skim_dict.wrap('origin', 'destination') - - skims = { - "odt_skims": odt_skim_stack_wrapper, - "dot_skims": dot_skim_stack_wrapper, - "od_skims": od_skim_wrapper, - } - - model_spec = simulate.read_model_spec(file_name=model_settings['SPEC']) - nest_specs = config.get_logit_model_settings(model_settings) - - estimator = estimation.manager.begin_estimation('trip_mode_choice') - - # grab one, duplicate num_samples times - trips_segment = trips_merged.loc[trips_merged.index == trip_id_to_check].copy() - primary_purpose = trips_segment['primary_purpose'].values[0] - trips_segment = trips_segment.loc[trips_segment.index.repeat(num_samples)] - - # need to add new row_states for rng here, need to ensure there are no collisions with existing keys - existing_indexes = pipeline._PIPELINE.rng().get_channel_for_df(trips_merged).row_states.index.values - num_new_indexes = trips_segment.shape[0] - new_indexes = np.arange(existing_indexes.max()+1, existing_indexes.max() + num_new_indexes + 1) - trips_segment.index = new_indexes - # name index so tracing knows how to slice - trips_segment.index.name = 'trip_id' - - pipeline._PIPELINE.rng().add_channel("trips", trips_segment) - - print("trip_mode_choice tour_type '%s' (%s trips)" % - (primary_purpose, len(trips_segment.index), )) - - coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose) - - locals_dict = {} - locals_dict.update(constants) - locals_dict.update(coefficients) - - segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose) - - expressions.annotate_preprocessors( - trips_segment, locals_dict, skims, - model_settings, segment_trace_label) - - locals_dict.update(skims) - - ################ Replace wrapper function - # choices = mode_choice_simulate(...) - spec=simulate.eval_coefficients(model_spec, coefficients, estimator) - nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label) - choices = simulate.simple_simulate( - choosers=trips_segment, - spec=spec, - nest_spec=nest_spec, - skims=skims, - locals_d=locals_dict, - chunk_size=chunk_size, - want_logsums=logsum_column_name is not None, - trace_label=segment_trace_label, - trace_choice_name='trip_mode_choice', - estimator=estimator, - trace_column_names=None, - choose_individual_max_utility=choose_individual_max_utility) - # for consistency, always return dataframe, whether or not logsums were requested - if isinstance(choices, pd.Series): - choices = choices.to_frame('choice') - choices.rename(columns={'logsum': logsum_column_name, - 'choice': mode_column_name}, - inplace=True) - - # THIS SHOULD NOW BE CONSISTENT - #if not choose_individual_max_utility: - alts = spec.columns - choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) - - finalise = True - if finalise: - inject.set_step_args(None) - # - pipeline._PIPELINE.rng().end_step(model_name) - pipeline.add_checkpoint(model_name) - if not pipeline.intermediate_checkpoint(): - pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME) - - pipeline.close_pipeline() - - print("Done") - return trips_merged, choices, nest_spec - - -# def comp_mode_shares(base_probs, choose_individual_max_utility, num_samples, trip_id_to_check): -# t_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility, -# trip_id_to_check=trip_id_to_check, num_samples=num_samples) -# sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0] -# return sim_mode_shares - -def comp_mode_shares(base_probs, sim_mode_shares, trip_id_to_check): - obs_probs = base_probs.loc[base_probs.index == trip_id_to_check].T - obs_probs.columns = ["mode_share_obs"] - ms_comp = obs_probs.merge(sim_mode_shares, left_index=True, right_index=True, how="outer").fillna(0) - ms_comp["diff"] = ms_comp["trip_mode"] - ms_comp["mode_share_obs"] - ms_comp["rel_diff"] = ms_comp["diff"] / ms_comp["mode_share_obs"] - #ms_comp.style.format('{:.2}') - #with pd.option_context("precision", 3): - # display(ms_comp) - return ms_comp - - -def run_trip_mode_choice(do_these_purposes=None, choose_individual_max_utility=True): - - """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" - resume_after = "trip_scheduling" - model_name = "trip_mode_choice" - chunk_size = 0 # test_mtc means no chunking - - pipeline.open_pipeline(resume_after) - # preload any bulky injectables (e.g. skims) not in pipeline - inject.get_injectable('preload_injectables', None) - pipeline._PIPELINE.rng().begin_step(model_name) - step_name = model_name - args = {} - checkpoint = pipeline.intermediate_checkpoint(model_name) - inject.set_step_args(args) - - trips = inject.get_table('trips') - tours_merged = inject.get_table('tours_merged') - network_los = inject.get_injectable('network_los') - - trace_label = 'trip_mode_choice' - model_settings_file_name = 'trip_mode_choice.yaml' - model_settings = config.read_model_settings(model_settings_file_name) - - logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME') - mode_column_name = 'trip_mode' - - trips_df = trips.to_frame() - print("Running with %d trips", trips_df.shape[0]) - - tours_merged = tours_merged.to_frame() - tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] - - # - trips_merged - merge trips and tours_merged - trips_merged = pd.merge( - trips_df, - tours_merged, - left_on='tour_id', - right_index=True, - how="left") - assert trips_merged.index.equals(trips.index) - - # setup skim keys - assert ('trip_period' not in trips_merged) - trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) - - orig_col = 'origin' - dest_col = 'destination' - - constants = {} - constants.update(config.get_model_constants(model_settings)) - constants.update({ - 'ORIGIN': orig_col, - 'DESTINATION': dest_col - }) - - skim_dict = network_los.get_default_skim_dict() - - odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col, - dim3_key='trip_period') - dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col, - dim3_key='trip_period') - od_skim_wrapper = skim_dict.wrap('origin', 'destination') - - skims = { - "odt_skims": odt_skim_stack_wrapper, - "dot_skims": dot_skim_stack_wrapper, - "od_skims": od_skim_wrapper, - } - - model_spec = simulate.read_model_spec(file_name=model_settings['SPEC']) - nest_specs = config.get_logit_model_settings(model_settings) - - estimator = estimation.manager.begin_estimation('trip_mode_choice') - - choices_list = [] - for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'): - - if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes): - continue - - print("trip_mode_choice tour_type '%s' (%s trips)" % - (primary_purpose, len(trips_segment.index), )) - - # name index so tracing knows how to slice - assert trips_segment.index.name == 'trip_id' - - coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose) - - locals_dict = {} - locals_dict.update(constants) - locals_dict.update(coefficients) - - segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose) - - expressions.annotate_preprocessors( - trips_segment, locals_dict, skims, - model_settings, segment_trace_label) - - locals_dict.update(skims) - - ################ Replace wrapper function - # choices = mode_choice_simulate(...) - spec=simulate.eval_coefficients(model_spec, coefficients, estimator) - nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label) - choices = simulate.simple_simulate( - choosers=trips_segment, - spec=spec, - nest_spec=nest_spec, - skims=skims, - locals_d=locals_dict, - chunk_size=chunk_size, - want_logsums=logsum_column_name is not None, - trace_label=segment_trace_label, - trace_choice_name='trip_mode_choice', - estimator=estimator, - trace_column_names=None, - choose_individual_max_utility=choose_individual_max_utility) - # for consistency, always return dataframe, whether or not logsums were requested - if isinstance(choices, pd.Series): - choices = choices.to_frame('choice') - choices.rename(columns={'logsum': logsum_column_name, - 'choice': mode_column_name}, - inplace=True) - - # SHOULD NOW BE CONSISTENT - #if not choose_individual_max_utility: - alts = spec.columns - choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) - ################ - choices_list.append(choices) - choices_df_asim = pd.concat(choices_list) - - # update trips table with choices (and potionally logssums) - trips_df = trips.to_frame() - - if (do_these_purposes is not None): - trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)] - - assign_in_place(trips_df, choices_df_asim) - assert not trips_df[mode_column_name].isnull().any() - - finalise = True - if finalise: - inject.set_step_args(None) - # - pipeline._PIPELINE.rng().end_step(model_name) - pipeline.add_checkpoint(model_name) - if not pipeline.intermediate_checkpoint(): - pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME) - - pipeline.close_pipeline() - - print("Done") - - return trips_df - - - - -def eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, estimator, - log_alt_losers=False, - want_logsums=False, trace_label=None, - trace_choice_name=None, trace_column_names=None): - - trace_label = tracing.extend_trace_label(trace_label, 'eval_nl') - assert trace_label - have_trace_targets = tracing.has_trace_targets(choosers) - - logit.validate_nest_spec(nest_spec, trace_label) - raw_utilities = simulate.eval_utilities(spec, choosers, locals_d, - log_alt_losers=log_alt_losers, - trace_label=trace_label, have_trace_targets=have_trace_targets, - estimator=estimator, trace_column_names=trace_column_names) - # exponentiated utilities of leaves and nests - nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_utilities, nest_spec) - nested_utils = simulate.compute_nested_utilities(raw_utilities, nest_spec) - # probabilities of alternatives relative to siblings sharing the same nest - nested_probabilities = simulate.compute_nested_probabilities(nested_exp_utilities, nest_spec, - trace_label=trace_label) - if want_logsums: - # logsum of nest root - logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) - # global (flattened) leaf probabilities based on relative nest coefficients (in spec order) - base_probabilities = simulate.compute_base_probabilities(nested_probabilities, nest_spec, spec) - # note base_probabilities could all be zero since we allowed all probs for nests to be zero - # check here to print a clear message but make_choices will raise error if probs don't sum to 1 - BAD_PROB_THRESHOLD = 0.001 - no_choices = (base_probabilities.sum(axis=1) - 1).abs() > BAD_PROB_THRESHOLD - if no_choices.any(): - print("BAD") - choices, rands = logit.make_choices(base_probabilities, trace_label=trace_label) - if want_logsums: - choices = choices.to_frame('choice') - choices['logsum'] = logsums - return choices, raw_utilities, nested_exp_utilities, nested_utils, nested_probabilities, base_probabilities - - -def simple_simulate_dev(choosers, spec, nest_spec, - skims=None, locals_d=None, - chunk_size=0, custom_chooser=None, - log_alt_losers=False, - want_logsums=False, - estimator=None, - trace_label=None, trace_choice_name=None, trace_column_names=None): - trace_label = tracing.extend_trace_label(trace_label, 'simple_simulate') - assert len(choosers) > 0 - result_list = [] - # segment by person type and pick the right spec for each person type - for i, chooser_chunk, chunk_trace_label in chunk.adaptive_chunked_choosers(choosers, chunk_size, trace_label): - # the following replaces choices = _simple_simulate(...) - if skims is not None: - simulate.set_skim_wrapper_targets(choosers, skims) - - # only do this for nested, logit is straight forward - assert nest_spec is not None - choices, raw_utilities, nested_exp_utilities, nested_utils, \ - nested_probs, base_probs = eval_nl_dev(choosers, spec, nest_spec, locals_d, custom_chooser, - log_alt_losers=log_alt_losers, - want_logsums=want_logsums, estimator=estimator, trace_label=trace_label, - trace_choice_name=trace_choice_name, trace_column_names=trace_column_names) - - result_list.append(choices) - chunk.log_df(trace_label, f'result_list', result_list) - - if len(result_list) > 1: - choices = pd.concat(result_list) - assert len(choices.index == len(choosers.index)) - return choices, raw_utilities, nested_exp_utilities, nested_utils, nested_probs, base_probs - - -def get_stuff(do_these_purposes=None): - #do_these_purposes=['escort'] - """open pipeline and load stuff for mode choice dev assuming model has been run and pipeline.h5 exists""" - resume_after = "trip_scheduling" - model_name = "trip_mode_choice" - chunk_size = 0 # test_mtc means no chunking - - pipeline.open_pipeline(resume_after) - # preload any bulky injectables (e.g. skims) not in pipeline - inject.get_injectable('preload_injectables', None) - pipeline._PIPELINE.rng().begin_step(model_name) - step_name = model_name - args = {} - checkpoint = pipeline.intermediate_checkpoint(model_name) - inject.set_step_args(args) - - trips = inject.get_table('trips') - tours_merged = inject.get_table('tours_merged') - network_los = inject.get_injectable('network_los') - - trace_label = 'trip_mode_choice' - model_settings_file_name = 'trip_mode_choice.yaml' - model_settings = config.read_model_settings(model_settings_file_name) - - logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME') - mode_column_name = 'trip_mode' - - trips_df = trips.to_frame() - print("Running with %d trips", trips_df.shape[0]) - - tours_merged = tours_merged.to_frame() - tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']] - - # - trips_merged - merge trips and tours_merged - trips_merged = pd.merge( - trips_df, - tours_merged, - left_on='tour_id', - right_index=True, - how="left") - assert trips_merged.index.equals(trips.index) - - # setup skim keys - assert ('trip_period' not in trips_merged) - trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart) - - orig_col = 'origin' - dest_col = 'destination' - - constants = {} - constants.update(config.get_model_constants(model_settings)) - constants.update({ - 'ORIGIN': orig_col, - 'DESTINATION': dest_col - }) - - skim_dict = network_los.get_default_skim_dict() - - odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col, dest_key=dest_col, - dim3_key='trip_period') - dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col, dest_key=orig_col, - dim3_key='trip_period') - od_skim_wrapper = skim_dict.wrap('origin', 'destination') - - skims = { - "odt_skims": odt_skim_stack_wrapper, - "dot_skims": dot_skim_stack_wrapper, - "od_skims": od_skim_wrapper, - } - - model_spec = simulate.read_model_spec(file_name=model_settings['SPEC']) - nest_specs = config.get_logit_model_settings(model_settings) - - estimator = estimation.manager.begin_estimation('trip_mode_choice') - - choices_list = [] - raw_util_list = [] - nest_list = [] - nu_list = [] - nest_spec_list = [] - nested_probs_list = [] - base_probs_list = [] - - for primary_purpose, trips_segment in trips_merged.groupby('primary_purpose'): - - if (do_these_purposes is not None) and (primary_purpose not in do_these_purposes): - continue - - print("trip_mode_choice tour_type '%s' (%s trips)" % - (primary_purpose, len(trips_segment.index), )) - - # name index so tracing knows how to slice - assert trips_segment.index.name == 'trip_id' - - coefficients = simulate.get_segment_coefficients(model_settings, primary_purpose) - - locals_dict = {} - locals_dict.update(constants) - locals_dict.update(coefficients) - - segment_trace_label = tracing.extend_trace_label(trace_label, primary_purpose) - - expressions.annotate_preprocessors( - trips_segment, locals_dict, skims, - model_settings, segment_trace_label) - - locals_dict.update(skims) - - ################ Replace wrapper function - # choices = mode_choice_simulate(...) - spec=simulate.eval_coefficients(model_spec, coefficients, estimator) - nest_spec = simulate.eval_nest_coefficients(nest_specs, coefficients, segment_trace_label) - choices, raw_utilities, nested_exp_utilities, nested_utils, nested_probs, base_probs = simple_simulate_dev( - choosers=trips_segment, - spec=spec, - nest_spec=nest_spec, - skims=skims, - locals_d=locals_dict, - chunk_size=chunk_size, - want_logsums=logsum_column_name is not None, - trace_label=segment_trace_label, - trace_choice_name='trip_mode_choice', - estimator=estimator, - trace_column_names=None) - # for consistency, always return dataframe, whether or not logsums were requested - if isinstance(choices, pd.Series): - choices = choices.to_frame('choice') - choices.rename(columns={'logsum': logsum_column_name, - 'choice': mode_column_name}, - inplace=True) - alts = spec.columns - choices[mode_column_name] = choices[mode_column_name].map(dict(list(zip(list(range(len(alts))), alts)))) - ################ - choices_list.append(choices) - raw_util_list.append(raw_utilities) - nest_list.append(nested_exp_utilities) - nu_list.append(nested_utils) - nest_spec_list.append(nest_spec) - nested_probs_list.append(nested_probs) - base_probs_list.append(base_probs) - - choices_df_asim = pd.concat(choices_list) - - # update trips table with choices (and potionally logssums) - trips_df = trips.to_frame() - - if (do_these_purposes is not None): - trips_df = trips_df.loc[trips_df.primary_purpose.isin(do_these_purposes)] - - assign_in_place(trips_df, choices_df_asim) - assert not trips_df[mode_column_name].isnull().any() - - finalise = True - if finalise: - inject.set_step_args(None) - # - pipeline._PIPELINE.rng().end_step(model_name) - pipeline.add_checkpoint(model_name) - if not pipeline.intermediate_checkpoint(): - pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME) - - pipeline.close_pipeline() - - print("Done") - - return trips_df, raw_util_list, nest_list, nu_list, nest_spec_list, nested_probs_list, base_probs_list, \ - dict(list(zip(list(range(len(alts))), alts))) \ No newline at end of file diff --git a/notebooks/larch_probabilities_via_estimation.ipynb b/notebooks/larch_probabilities_via_estimation.ipynb deleted file mode 100644 index 1dc495ed64..0000000000 --- a/notebooks/larch_probabilities_via_estimation.ipynb +++ /dev/null @@ -1,592 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "collapsed": true, - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "# Probabilities for three level nests differ\n", - "\n", - "Asim scales nested utilties differently, for two level and RU2 it's identical to what we do (which agrees with the\n", - "larch paper and Zenith), but for three levels there are small differences of probabilities and logsums. I\n", - "quadruple-checked the scaling and leaf and note utility calculation, let's use larch to see if we get the same as\n", - "Asim or if the results agree with my implementation. The idea is to use estimation mode to load a small data set, and\n", - " then use my Asim setup in validate_frozen_impl.ipynb to calculate Asim's and my probabilities by sampling, and check\n", - " what larch has to say by using m.probabilities() (no need to estimate, use parameters as given by asim model).\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "outputs": [], - "source": [ - "%load_ext autoreload" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 2, - "outputs": [], - "source": [ - "%autoreload 2" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 3, - "outputs": [], - "source": [ - "import os\n", - "import sys\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "# I installed larch deps (which I collected in a requirements file, xmle and sharrow are not on pypi)\n", - "# python -m pip install -r ../larch/requirements.txt\n", - "\n", - "larch_path = \"/mnt/c/Users/jan.zill/code/larch\"\n", - "if larch_path not in sys.path:\n", - " sys.path.append(larch_path)\n", - "import larch" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 4, - "outputs": [], - "source": [ - "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_estimation_sf\")\n", - "os.chdir(example_dir)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 5, - "outputs": [], - "source": [ - "# create estimation test example\n", - "#!activitysim create -e example_estimation_sf -d test_estimation_sf" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 6, - "outputs": [], - "source": [ - "# run estimation mode\n", - "#!activitysim run -c configs_estimation/configs -c configs -o output_sf -d data_sf" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 7, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/mnt/c/Users/jan.zill/code/activitysim/activitysim/estimation/larch/simple_simulate.py:92: DtypeWarning: Columns (411) have mixed types.Specify dtype option on import or set low_memory=False.\n", - " chooser_data = _read_csv(chooser_data_file, index_col=values_index_col,)\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "req_data does not request avail_ca or avail_co but it is set and being provided\n", - "problem: chosen-but-not-available (2 issues)\n", - "problem: low-variance-data-co (1 issues)\n", - "problem: chosen-but-not-available (2 issues)\n", - "problem: low-variance-data-co (1 issues)\n", - "problem: chosen-but-not-available (1 issues)\n", - "problem: low-variance-data-co (1 issues)\n", - "problem: chosen-but-not-available (2 issues)\n", - "problem: low-variance-data-co (1 issues)\n", - "problem: chosen-but-not-available (1 issues)\n", - "problem: low-variance-data-co (1 issues)\n", - "problem: chosen-but-not-available (2 issues)\n", - "problem: low-variance-data-co (1 issues)\n", - "problem: chosen-but-not-available (1 issues)\n", - "problem: low-variance-data-co (1 issues)\n", - "problem: chosen-but-not-available (2 issues)\n", - "problem: low-variance-data-co (1 issues)\n" - ] - } - ], - "source": [ - "modelname = \"trip_mode_choice\"\n", - "\n", - "from activitysim.estimation.larch import component_model\n", - "model, data = component_model(modelname, return_data=True)\n", - "# data.coefficients\n", - "# data.spec\n", - "# data.chooser_data\n", - "model.load_data()\n", - "model.doctor(repair_ch_av='-')\n", - "model.loglike()\n", - "# DO NOT ESTIMATE just apply probs\n", - "# model.maximize_loglike(method='SLSQP', options={\"maxiter\": 1000})\n", - "probs = [m.probability() for m in model._k_models]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 8, - "outputs": [], - "source": [ - "probs_0 = pd.DataFrame(probs[0])\n", - "m_0 = model._k_models[0]\n", - "\n", - "probs_0.index = m_0.dataframes.data_ch.index\n", - "\n", - "probs_0 = probs_0.rename(columns={v-1: k for k,v in data.settings[\"CONSTANTS\"][\"I_MODE_MAP\"].items()})" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 9, - "outputs": [ - { - "data": { - "text/plain": "(2124,)" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": "(2124,)" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "display(m_0.dataframes.data_ch.index.unique().shape)\n", - "display(np.intersect1d(data.chooser_data.index.values, m_0.dataframes.data_ch.index.values).shape)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 11, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/mnt/c/Users/jan.zill/code/activitysim/test_estimation_sf\r\n" - ] - } - ], - "source": [ - "!pwd" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 14, - "outputs": [], - "source": [ - "#m_0.graph.to_png(filename=\"/mnt/c/Users/jan.zill/code/activitysim/test_estimation_sf/mtc_test.png\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 15, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'temp' is not defined", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m/tmp/ipykernel_24538/1635136243.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mdisplay\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtemp\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", - "\u001B[0;31mNameError\u001B[0m: name 'temp' is not defined" - ] - } - ], - "source": [ - "display(temp)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 17, - "outputs": [], - "source": [ - "subg = m_0.graph.subgraph_from(22)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 23, - "outputs": [ - { - "data": { - "text/plain": "", - "text/html": "" - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "subg.to_png(filename=\"/mnt/c/Users/jan.zill/code/activitysim/test_estimation_sf/mtc_mode_choice_subgraph.png\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 22, - "outputs": [ - { - "data": { - "text/plain": "", - "text/html": "
\n\nTree\n\n\ncluster_elemental\n\nElemental Alternatives\n\n\n\n1\n\nDRIVEALONEFREE \n(1)\n\n\n\n2\n\nDRIVEALONEPAY \n(2)\n\n\n\n24\n\nSHAREDRIDE2 \n(24)\n\n\n\n25\n\nSHAREDRIDE3 \n(25)\n\n\n\n22\n\nAUTO \n(22)\n\n\n\n22->24\n\n\n\n\n\n22->25\n\n\n\n\n\n23\n\nDRIVEALONE \n(23)\n\n\n\n22->23\n\n\n\n\n\n23->1\n\n\n\n\n\n23->2\n\n\n\n\n\n0\n\nRoot\n\n\n
" - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m_0.graph.partial_figure(source=22)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 18, - "outputs": [], - "source": [ - "#data.chooser_data[['override_choice', 'override_choice_code']].drop_duplicates().reset_index(drop=True).set_index\\\n", - "# ('override_choice_code').to_dict()['override_choice']\n", - "#mode_map = pd.DataFrame.from_dict(data.settings[\"CONSTANTS\"][\"I_MODE_MAP\"], orient='index', columns=[\"mode_code\"])\\\n", - "# .reset_index().rename(columns={\"index\": \"mode_name\"})" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 19, - "outputs": [ - { - "data": { - "text/plain": "Int64Index([ 2961920, 2970120, 2998943, 3013252, 3015794, 3021985,\n 3022354, 3025019, 3055523, 3060361,\n ...\n 308028857, 308037836, 308057680, 308057681, 308070308, 308070309,\n 308073875, 308090603, 308105896, 308122624],\n dtype='int64', name='_caseid_', length=2124)" - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "m_0.dataframes.data_ch.index.unique()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 34, - "outputs": [ - { - "data": { - "text/plain": " trip_id model_choice override_choice \\\ntour_id \n2961920 23695361 WALK_LRF WALK_LRF \n2961920 23695365 WALK WALK_LRF \n\n util_DRIVEALONEFREE_Unavailable \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_zero_auto_households \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_persons_less_than_16 \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_for_joint_tours \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_work \\\ntour_id \n2961920 0.0 \n2961920 0.0 \n\n util_DRIVEALONEFREE_In_vehicle_time \\\ntour_id \n2961920 5.36 \n2961920 4.91 \n\n util_DRIVEALONEFREE_Terminal_time ... \\\ntour_id ... \n2961920 10.45084 ... \n2961920 10.45084 ... \n\n drive_heavyrail_available_outbound \\\ntour_id \n2961920 False \n2961920 False \n\n drive_heavyrail_available_inbound drive_commuter_available_outbound \\\ntour_id \n2961920 False False \n2961920 False False \n\n drive_commuter_available_inbound walk_ferry_available \\\ntour_id \n2961920 False False \n2961920 False False \n\n drive_ferry_available distance distance_walk_od distance_bike_od \\\ntour_id \n2961920 False 1.98 1.98 1.98 \n2961920 False 1.96 1.96 1.96 \n\n override_choice_code \ntour_id \n2961920 10 \n2961920 10 \n\n[2 rows x 478 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_idmodel_choiceoverride_choiceutil_DRIVEALONEFREE_Unavailableutil_DRIVEALONEFREE_Unavailable_for_zero_auto_householdsutil_DRIVEALONEFREE_Unavailable_for_persons_less_than_16util_DRIVEALONEFREE_Unavailable_for_joint_toursutil_DRIVEALONEFREE_Unavailable_if_didnt_drive_to_workutil_DRIVEALONEFREE_In_vehicle_timeutil_DRIVEALONEFREE_Terminal_time...drive_heavyrail_available_outbounddrive_heavyrail_available_inbounddrive_commuter_available_outbounddrive_commuter_available_inboundwalk_ferry_availabledrive_ferry_availabledistancedistance_walk_oddistance_bike_odoverride_choice_code
tour_id
296192023695361WALK_LRFWALK_LRF0.00.00.00.00.05.3610.45084...FalseFalseFalseFalseFalseFalse1.981.981.9810
296192023695365WALKWALK_LRF0.00.00.00.00.04.9110.45084...FalseFalseFalseFalseFalseFalse1.961.961.9610
\n

2 rows × 478 columns

\n
" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": " 1 2 3 4 5 6 7 8 9 10 ... 12 13 \\\n_caseid_ ... \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 \n\n 14 15 16 17 18 19 20 21 \n_caseid_ \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n\n[2 rows x 21 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
12345678910...12131415161718192021
_caseid_
29619200.00.00.00.00.00.00.00.00.01.0...0.00.00.00.00.00.00.00.00.00.0
29619200.00.00.00.00.00.00.00.00.01.0...0.00.00.00.00.00.00.00.00.00.0
\n

2 rows × 21 columns

\n
" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\n_caseid_ \n2961920 0.0 0.0 2.027363e-06 0.0 \n2961920 0.0 0.0 2.943297e-09 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF ... \\\n_caseid_ ... \n2961920 1.061530e-06 0.0 0.111823 0.0 0.263130 0.625043 ... \n2961920 1.967559e-08 0.0 0.119603 0.0 0.272258 0.608139 ... \n\n WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP DRIVE_HVY \\\n_caseid_ \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 \n2961920 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \n_caseid_ \n2961920 0.0 2.489336e-08 4.965881e-08 4.072015e-07 \n2961920 0.0 2.435815e-08 4.996681e-08 4.311721e-07 \n\n[2 rows x 21 columns]", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRF...WALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
_caseid_
29619200.00.02.027363e-060.01.061530e-060.00.1118230.00.2631300.625043...0.00.00.00.00.00.00.02.489336e-084.965881e-084.072015e-07
29619200.00.02.943297e-090.01.967559e-080.00.1196030.00.2722580.608139...0.00.00.00.00.00.00.02.435815e-084.996681e-084.311721e-07
\n

2 rows × 21 columns

\n
" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "23695361\n" - ] - }, - { - "data": { - "text/plain": " SHARED2FREE SHARED3FREE WALK WALK_LOC WALK_LRF \\\n_caseid_ \n2961920 2.027363e-06 1.061530e-06 0.111823 0.263130 0.625043 \n2961920 2.943297e-09 1.967559e-08 0.119603 0.272258 0.608139 \n\n TAXI TNC_SINGLE TNC_SHARED \n_caseid_ \n2961920 2.489336e-08 4.965881e-08 4.072015e-07 \n2961920 2.435815e-08 4.996681e-08 4.311721e-07 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
SHARED2FREESHARED3FREEWALKWALK_LOCWALK_LRFTAXITNC_SINGLETNC_SHARED
_caseid_
29619202.027363e-061.061530e-060.1118230.2631300.6250432.489336e-084.965881e-084.072015e-07
29619202.943297e-091.967559e-080.1196030.2722580.6081392.435815e-084.996681e-084.311721e-07
\n
" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "tour_id_to_check = 2961920\n", - "\n", - "display(data.chooser_data.loc[data.chooser_data.index == tour_id_to_check])\n", - "display(m_0.dataframes.data_ch.loc[m_0.dataframes.data_ch.index == tour_id_to_check])\n", - "display(probs_0.loc[probs_0.index == tour_id_to_check])\n", - "\n", - "# choose the first trip to look into probabilities below\n", - "trip_id_to_check = data.chooser_data.loc[data.chooser_data.index == tour_id_to_check].trip_id.values[0]\n", - "print(trip_id_to_check)\n", - "res_ = probs_0.loc[(probs_0.index == tour_id_to_check)]\n", - "display(res_.loc[:, (res_ != 0).any(axis=0)])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 46, - "outputs": [], - "source": [ - "data.chooser_data.loc[data.chooser_data.index.isin(m_0.dataframes.data_ch.index)].to_csv(os.path.join(example_dir, \"choosers_larch.csv\"))\n", - "probs_0.to_csv(os.path.join(example_dir, \"probabilities_larch.csv\"))" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# see if larch scales nest scale" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 46, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 46, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 46, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/notebooks/nested_logit_frozen_rand_individ_util.ipynb b/notebooks/nested_logit_frozen_rand_individ_util.ipynb deleted file mode 100644 index fbf4fce6be..0000000000 --- a/notebooks/nested_logit_frozen_rand_individ_util.ipynb +++ /dev/null @@ -1,440 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Frozen randomness\n", - "\n", - "Let's try to draw from nested logit model by drawing error terms. For logit models, this is simple because error terms are independent and therefore we can uniquely invert the CDF and simply draw from that. For nested models, this is not the case. However, we know we can write the probabilities as nested logits and therefore we think we can draw repeatedly like for logit models, taking the nesting structure into account.\n", - "\n", - "Let's start with two levels and a model where we know the probabilities, i.e. we fix the utility functions and the nesting scales, like for red bus/blue bus. We then draw like ActivitySim does, and like we want to do.\n", - "\n", - "Next, we extend to three levels, where the additional nest error term has not been derived yet." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T05:31:19.564033Z", - "iopub.status.busy": "2022-05-01T05:31:19.563663Z", - "iopub.status.idle": "2022-05-01T05:31:19.824207Z", - "shell.execute_reply": "2022-05-01T05:31:19.823432Z", - "shell.execute_reply.started": "2022-05-01T05:31:19.563960Z" - } - }, - "outputs": [], - "source": [ - "import os\n", - "import sys\n", - "\n", - "import numpy as np\n", - "from numpy.random import default_rng" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T05:31:20.023960Z", - "iopub.status.busy": "2022-05-01T05:31:20.023246Z", - "iopub.status.idle": "2022-05-01T05:31:20.029538Z", - "shell.execute_reply": "2022-05-01T05:31:20.028264Z", - "shell.execute_reply.started": "2022-05-01T05:31:20.023927Z" - } - }, - "outputs": [], - "source": [ - "def logsum(utilities, nest_scale=1.0):\n", - " scaled_utils = utilities / nest_scale\n", - " max_util = np.max(scaled_utils)\n", - " return max_util + np.log(np.sum(np.exp(scaled_utils - max_util)))\n", - "\n", - "\n", - "# total probability of alternative being chosen is product of two terms:\n", - "# conditional probability of alternative given nest is chosen: exp(util / nest_scale)\n", - "# marginal probability that any alternative in nest is chosen: exp(nest_scale * logsum)\n", - " \n", - "# If you think about a single case, the probabilities are indicator variables and we take the max of each. This is what Zenith does I think.\n", - "# Given that these expressions are those of two logits, it seems natural to draw correspondingly.\n", - "# This must be related to the strict derivation of max() distributions of Hunt." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Two-level" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T09:55:14.957470Z", - "iopub.status.busy": "2022-05-01T09:55:14.957183Z", - "iopub.status.idle": "2022-05-01T09:55:14.964046Z", - "shell.execute_reply": "2022-05-01T09:55:14.963056Z", - "shell.execute_reply.started": "2022-05-01T09:55:14.957441Z" - } - }, - "outputs": [], - "source": [ - "alternatives = {1: \"car\", 2: \"blue bus\", 3: \"red bus\"}\n", - "\n", - "utility_spec = {\n", - " 1: {\"cost\": -1.0, \"asc\": 0.0},\n", - " 2: {\"cost\": -1.0, \"asc\": 0.2},\n", - " 3: {\"cost\": -1.5, \"asc\": 0.1},\n", - "}\n", - "\n", - "# blue and red bus are nested together with scale 0.5\n", - "\n", - "def utility(x, utility_spec, alternative):\n", - " assert alternative in utility_spec.keys()\n", - " return utility_spec[alternative][\"cost\"] * x + utility_spec[alternative][\"asc\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T09:55:15.467608Z", - "iopub.status.busy": "2022-05-01T09:55:15.467366Z", - "iopub.status.idle": "2022-05-01T09:55:15.478279Z", - "shell.execute_reply": "2022-05-01T09:55:15.476833Z", - "shell.execute_reply.started": "2022-05-01T09:55:15.467571Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.4402110749048506 0.5234199037442896 0.036369021350859815\n", - "1.0\n" - ] - } - ], - "source": [ - "cost = 3.0\n", - "nest_scale = 0.6\n", - "\n", - "util_3 = utility(cost, utility_spec, 3)\n", - "util_2 = utility(cost, utility_spec, 2)\n", - "logsum_bus = logsum(np.array([util_2, util_3]), nest_scale=nest_scale)\n", - "nest_util = nest_scale * logsum_bus\n", - "\n", - "util_1 = utility(cost, utility_spec, 1)\n", - "prob_1 = np.exp(util_1) / (np.exp(util_1) + np.exp(nest_util))\n", - "\n", - "nest_cond_prob = np.exp(nest_util) / (np.exp(util_1) + np.exp(nest_util))\n", - "nest_marg_prob_2 = np.exp(util_2 / nest_scale) / (np.exp(util_2 / nest_scale) + np.exp(util_3 / nest_scale))\n", - "nest_marg_prob_3 = np.exp(util_3 / nest_scale) / (np.exp(util_2 / nest_scale) + np.exp(util_3 / nest_scale))\n", - "\n", - "prob_2 = nest_cond_prob * nest_marg_prob_2\n", - "prob_3 = nest_cond_prob * nest_marg_prob_3\n", - "\n", - "print(prob_1, prob_2, prob_3)\n", - "print(sum([prob_1, prob_2, prob_3]))" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T09:55:20.523480Z", - "iopub.status.busy": "2022-05-01T09:55:20.523247Z", - "iopub.status.idle": "2022-05-01T09:55:21.341578Z", - "shell.execute_reply": "2022-05-01T09:55:21.340682Z", - "shell.execute_reply.started": "2022-05-01T09:55:20.523453Z" - } - }, - "outputs": [], - "source": [ - "# Asim does this:\n", - "# probs = [prob_1, prob_2, prob_3]\n", - "# cum_probs = [0] + list(np.cumsum(probs))\n", - "#\n", - "# num_draws = 10000000\n", - "#\n", - "# # now draw from U and put into arrays, then value count?\n", - "# rng = default_rng(999)\n", - "# rands = rng.uniform(size=num_draws)\n", - "#\n", - "# hits, bins = np.histogram(rands, bins=cum_probs)\n", - "# print(f\"closed form: {probs},\\nsimulated: {hits / num_draws}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T09:55:24.027745Z", - "iopub.status.busy": "2022-05-01T09:55:24.027421Z", - "iopub.status.idle": "2022-05-01T09:55:24.034035Z", - "shell.execute_reply": "2022-05-01T09:55:24.031703Z", - "shell.execute_reply.started": "2022-05-01T09:55:24.027708Z" - } - }, - "outputs": [], - "source": [ - "def inverse_ev1_cdf(x, location=0.0, scale=1.0):\n", - " \"quantile function of EV1\"\n", - " #return location - (1.0 / scale) * np.log(-np.log(x))\n", - " # let's follow https://en.wikipedia.org/wiki/Gumbel_distribution where the scale is proportional to variance (not variance^{-1})\n", - " return location - scale * np.log(-np.log(x))\n", - "\n", - "# for utilities with full set of ascs location=0. Do we always assume location=0 in estimation anyways?\n", - "# the scale of the error term is unidentified and therefore set to 1 in most applications, meaning the standard deviation is pi/sqrt(6)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "execution": { - "iopub.execute_input": "2022-04-17T03:38:03.583638Z", - "iopub.status.busy": "2022-04-17T03:38:03.583273Z", - "iopub.status.idle": "2022-04-17T03:38:03.588839Z", - "shell.execute_reply": "2022-04-17T03:38:03.586978Z", - "shell.execute_reply.started": "2022-04-17T03:38:03.583594Z" - } - }, - "source": [ - "## The Zenith way\n", - "\n", - "Basically, probabilities are now indicators - choose at the lowest level, then walk up. Choice is product of these. We draw error terms for each alternative, where nest roots are now alternatives too.\n", - "\n", - "OR: do we choose a" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:03:23.999857Z", - "iopub.status.busy": "2022-05-01T10:03:23.999571Z", - "iopub.status.idle": "2022-05-01T10:03:30.033848Z", - "shell.execute_reply": "2022-05-01T10:03:30.032689Z", - "shell.execute_reply.started": "2022-05-01T10:03:23.999816Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "closed form probs: 0.440211, 0.523420, 0.036369\n", - " simulated probs: 0.4403354, 0.5233336, 0.036331\n", - "CPU times: user 3.88 s, sys: 2.52 s, total: 6.39 s\n", - "Wall time: 6.73 s\n" - ] - } - ], - "source": [ - "%%time\n", - "\n", - "# conditional error term are given by logit with scale given by nest scale\n", - "num_draws_dec = 10000000\n", - "#mu = 1.0 / nest_scale\n", - "\n", - "util_3 = utility(cost, utility_spec, 3)\n", - "util_2 = utility(cost, utility_spec, 2)\n", - "logsum_bus = logsum(np.array([util_2, util_3]), nest_scale=nest_scale)\n", - "util_1 = utility(cost, utility_spec, 1)\n", - "\n", - "\n", - "rng_dec = default_rng(9)\n", - "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", - "ev1_lower = inverse_ev1_cdf(rands_dec)\n", - "\n", - "lower_utils_2 = util_2 / nest_scale + ev1_lower[num_draws_dec:]\n", - "lower_utils_3 = util_3 / nest_scale + ev1_lower[:num_draws_dec]\n", - "\n", - "ev1_upper = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec))\n", - "nest_util = nest_scale * logsum_bus + ev1_upper\n", - "\n", - "ev1_upper = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec))\n", - "full_util_1 = util_1 + ev1_upper\n", - "\n", - "choices = np.array([full_util_1, nest_util]).argmax(axis=0)\n", - "nest_indexes = np.nonzero(choices == 1)[0]\n", - "nest_choices = np.array([lower_utils_2[nest_indexes], lower_utils_3[nest_indexes]]).argmax(axis=0)\n", - "nest_choices += 1\n", - "choices = np.append(choices[choices == 0], nest_choices)\n", - "\n", - "vals, counts = np.unique(choices, return_counts=True)\n", - "probs_dec = {i+1: counts[i] / num_draws_dec for i in vals}\n", - "\n", - "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", - "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The error term decomposition way -> not working. just use the zenith way and write it as indicators for individuals.\n", - "\n", - "We can decompose the error term into one for the nest and one within nests according to Hildebrandt. However, I\n", - "cannot seem to reproduce the exact probabilities. Why?\n", - "\n", - "Looks like one of the location parameters is wrong; 0.125 added to nest makes it right (tested for one set of params)" - ] - }, - { - "cell_type": "code", - "execution_count": 79, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T09:55:25.164028Z", - "iopub.status.busy": "2022-05-01T09:55:25.163822Z", - "iopub.status.idle": "2022-05-01T09:55:25.473704Z", - "shell.execute_reply": "2022-05-01T09:55:25.472525Z", - "shell.execute_reply.started": "2022-05-01T09:55:25.164005Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "closed form probs: 0.445227, 0.533045, 0.021728\n", - " simulated probs: 0.4755317, 0.5039483, 0.02052\n", - "0.9363, 1.0577, 1.0589\n", - "CPU times: user 4.12 s, sys: 2.3 s, total: 6.42 s\n", - "Wall time: 6.99 s\n" - ] - } - ], - "source": [ - "%%time\n", - "# conditional error term are given by logit with scale given by nest scale\n", - "num_draws_dec = 10000000\n", - "\n", - "mu = 1.0 / nest_scale\n", - "\n", - "rng_dec = default_rng(9)\n", - "\n", - "rands_dec = rng_dec.uniform(size = 2 * num_draws_dec) # we need one for each alternative if num_draws_dec signifies the total number of choices we want to simulate\n", - "ev1_dec = inverse_ev1_cdf(rands_dec, scale=1.0/mu)\n", - "lower_level_utils_2 = np.repeat(util_2, num_draws_dec) + ev1_dec[num_draws_dec:]\n", - "lower_level_utils_3 = np.repeat(util_3, num_draws_dec) + ev1_dec[:num_draws_dec]\n", - "\n", - "#location_nest = - 1.0 / mu * np.log(2.0 * np.exp(mu * 0.5772))\n", - "location_nest = - np.log(2.0) / mu\n", - "#location_nest = (- np.log(2.0) / mu) - ((1.0 - 1.0 / (mu + 1.0)) * 0.57721 * mu / (mu**2 - 1.0))\n", - "#print(location_nest, - np.log(2.0) / mu)\n", - "\n", - "scale_nest = mu / np.sqrt(mu**2 - 1.0)\n", - "nest_error_terms = inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec), location=location_nest, scale=1.0/scale_nest)\n", - "\n", - "full_utils_2 = lower_level_utils_2 + nest_error_terms\n", - "full_utils_3 = lower_level_utils_3 + nest_error_terms\n", - "\n", - "# what's the distribution of error term for car? it's a degenerate nest, so bottom level is 1\n", - "# this here agrees with Bhat and Koppelman's mode choice script.\n", - "full_utils_1 = util_1 + inverse_ev1_cdf(rng_dec.uniform(size=num_draws_dec))\n", - "\n", - "choices = np.array([full_utils_1, full_utils_2, full_utils_3]).argmax(axis=0)\n", - "vals, counts = np.unique(choices, return_counts=True)\n", - "probs_dec = {i+1: counts[i] / num_draws_dec for i in vals}\n", - "\n", - "print(f\"closed form probs: {prob_1:.6f}, {prob_2:.6f}, {prob_3:.6f}\")\n", - "print(f\" simulated probs: {probs_dec[1]}, {probs_dec[2]}, {probs_dec[3]}\")\n", - "print(f\"{prob_1 / probs_dec[1]:.4f}, {prob_2 / probs_dec[2]:.4f}, {prob_3 / probs_dec[3]:.4f}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Three-level\n", - "\n", - "and recursive - maybe use Asim structure directly?\n", - "\n", - "\n", - "Could also use larch to apply models, would be great to add there too?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.4" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} \ No newline at end of file diff --git a/notebooks/validate_frozen_impl.ipynb b/notebooks/validate_frozen_impl.ipynb deleted file mode 100644 index cf89790669..0000000000 --- a/notebooks/validate_frozen_impl.ipynb +++ /dev/null @@ -1,1651 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7ece3567-e4b1-4c3f-a264-20625abb6ad7", - "metadata": {}, - "source": [ - "# validate results\n", - "\n", - "## TODO\n", - "what happened to tracing when I fixed probability calcs - it seems like all trip ids are attached when I add one by\n", - "hand below - why?" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "bd74ba44-0dfb-439a-a6ab-7ceedfc5f523", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.385153Z", - "iopub.status.busy": "2022-05-01T10:17:57.384881Z", - "iopub.status.idle": "2022-05-01T10:17:57.534433Z", - "shell.execute_reply": "2022-05-01T10:17:57.533096Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.385047Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%load_ext autoreload" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "c3dba451-1e10-403e-8614-35d57e6577f4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:17:57.536623Z", - "iopub.status.busy": "2022-05-01T10:17:57.536012Z", - "iopub.status.idle": "2022-05-01T10:17:57.542755Z", - "shell.execute_reply": "2022-05-01T10:17:57.541685Z", - "shell.execute_reply.started": "2022-05-01T10:17:57.536567Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "73deaac4-e7ac-4aff-b086-4980dc6dd903", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:00.350944Z", - "iopub.status.busy": "2022-05-01T10:18:00.350730Z", - "iopub.status.idle": "2022-05-01T10:18:12.760977Z", - "shell.execute_reply": "2022-05-01T10:18:12.760013Z", - "shell.execute_reply.started": "2022-05-01T10:18:00.350919Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "import os\n", - "import argparse\n", - "from datetime import datetime\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "from numpy.random import default_rng\n", - "from statsmodels.stats.gof import powerdiscrepancy\n", - "from statsmodels.stats.proportion import multinomial_proportions_confint\n", - "import numba\n", - "import matplotlib.pyplot as plt\n", - "\n", - "from activitysim.cli import run\n", - "from activitysim.core import inject\n", - "\n", - "from fru_utils import run_trip_mode_choice, comp_mode_shares, mode_choice_for_trip" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "7e553abd-fe0d-4cdc-aeb1-9dc80cb2757f", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:16.944537Z", - "iopub.status.busy": "2022-05-01T10:18:16.944291Z", - "iopub.status.idle": "2022-05-01T10:18:17.124764Z", - "shell.execute_reply": "2022-05-01T10:18:17.123725Z", - "shell.execute_reply.started": "2022-05-01T10:18:16.944501Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "pd.set_option(\"max_columns\", 500)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "41fec4e8-a174-4e66-87d2-1e8c7979de90", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:17.839947Z", - "iopub.status.busy": "2022-05-01T10:18:17.839070Z", - "iopub.status.idle": "2022-05-01T10:18:18.019676Z", - "shell.execute_reply": "2022-05-01T10:18:18.018689Z", - "shell.execute_reply.started": "2022-05-01T10:18:17.839911Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "root_dir = \"/mnt/c/Users/jan.zill/code/activitysim\"\n", - "example_dir = os.path.join(root_dir, \"test_example_mtc\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "2de710a2-d292-42f9-9d4a-4dcef1365506", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:18.894533Z", - "iopub.status.busy": "2022-05-01T10:18:18.894303Z", - "iopub.status.idle": "2022-05-01T10:18:19.078807Z", - "shell.execute_reply": "2022-05-01T10:18:19.077951Z", - "shell.execute_reply.started": "2022-05-01T10:18:18.894508Z" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "os.chdir(example_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "outputs": [], - "source": [ - "from activitysim.core import pipeline\n", - "pipeline.close_pipeline()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "730be239-8704-4483-bbb8-ffae0f17c5d4", - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T10:18:22.111723Z", - "iopub.status.busy": "2022-05-01T10:18:22.111490Z", - "iopub.status.idle": "2022-05-01T10:18:22.297437Z", - "shell.execute_reply": "2022-05-01T10:18:22.296501Z", - "shell.execute_reply.started": "2022-05-01T10:18:22.111697Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Configured logging using basicConfig\n", - "INFO - Configured logging using basicConfig\n", - "INFO - Read logging configuration from: configs/logging.yaml\n", - "INFO - SETTING configs_dir: ['configs']\n", - "INFO - SETTING settings_file_name: settings.yaml\n", - "INFO - SETTING data_dir: ['data']\n", - "INFO - SETTING output_dir: output\n", - "INFO - SETTING households_sample_size: 100\n", - "INFO - SETTING chunk_size: 0\n", - "INFO - SETTING chunk_method: hybrid_uss\n", - "INFO - SETTING chunk_training_mode: disabled\n", - "INFO - SETTING multiprocess: None\n", - "INFO - SETTING num_processes: None\n", - "INFO - SETTING resume_after: None\n", - "INFO - SETTING trace_hh_id: [982875]\n", - "INFO - ENV MKL_NUM_THREADS: None\n", - "INFO - ENV OMP_NUM_THREADS: None\n", - "INFO - ENV OPENBLAS_NUM_THREADS: None\n", - "INFO - NUMPY blas_opt_info libraries: ['openblas', 'openblas']\n", - "INFO - NUMPY lapack_opt_info libraries: ['openblas', 'openblas']\n", - "INFO - run single process simulation\n", - "INFO - Time to execute open_pipeline : 0.163 seconds (0.0 minutes)\n", - "INFO - Time to execute preload_injectables : 0.08 seconds (0.0 minutes)\n", - "INFO - #run_model running step initialize_landuse\n", - "Running step 'initialize_landuse'\n", - "INFO - Time to execute all models until this error : 0.393 seconds (0.0 minutes)\n", - "ERROR - activitysim run encountered an unrecoverable error\n", - "Traceback (most recent call last):\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/cli/run.py\", line 261, in run\n", - " pipeline.run(models=config.setting('models'), resume_after=resume_after)\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\", line 617, in run\n", - " run_model(model)\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\", line 476, in run_model\n", - " orca.run([step_name])\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\", line 2168, in run\n", - " step()\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\", line 973, in __call__\n", - " return self._func(**kwargs)\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/initialize.py\", line 100, in initialize_landuse\n", - " with chunk.chunk_log(trace_label, base=True):\n", - " File \"/home/jan/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py\", line 113, in __enter__\n", - " return next(self.gen)\n", - " File \"/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/chunk.py\", line 929, in chunk_log\n", - " assert base == (len(CHUNK_SIZERS) == 0)\n", - "AssertionError\n" - ] - }, - { - "ename": "AssertionError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mAssertionError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m/tmp/ipykernel_2932/4273387002.py\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[1;32m 2\u001B[0m \u001B[0mrun\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0madd_run_args\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mparser\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0margs\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mparser\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mparse_args\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m'-c'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'configs'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'-o'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'output'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'-d'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;34m'data'\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m----> 4\u001B[0;31m \u001B[0mrun\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;31m# 2mins full example run\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 5\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0minject\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mis_injectable\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'preload_injectables'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 6\u001B[0m \u001B[0;32mfrom\u001B[0m \u001B[0mactivitysim\u001B[0m \u001B[0;32mimport\u001B[0m \u001B[0mabm\u001B[0m \u001B[0;31m# register abm steps and other abm-specific injectables\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/cli/run.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(args)\u001B[0m\n\u001B[1;32m 259\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'run single process simulation'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 260\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 261\u001B[0;31m \u001B[0mpipeline\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodels\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msetting\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'models'\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mresume_after\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0mresume_after\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 262\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 263\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msetting\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'cleanup_pipeline_after_run'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(models, resume_after)\u001B[0m\n\u001B[1;32m 615\u001B[0m \u001B[0;32mfor\u001B[0m \u001B[0mmodel\u001B[0m \u001B[0;32min\u001B[0m \u001B[0mmodels\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 616\u001B[0m \u001B[0mt1\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mprint_elapsed_time\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 617\u001B[0;31m \u001B[0mrun_model\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmodel\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 618\u001B[0m \u001B[0mmem\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mtrace_memory_info\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34mf\"pipeline.run after {model}\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 619\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mrun_model\u001B[0;34m(model_name)\u001B[0m\n\u001B[1;32m 474\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minfo\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34mf\"#run_model running step {step_name}\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 475\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 476\u001B[0;31m \u001B[0morca\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mstep_name\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 477\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 478\u001B[0m \u001B[0mt0\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mprint_elapsed_time\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"#run_model completed step '%s'\"\u001B[0m \u001B[0;34m%\u001B[0m \u001B[0mmodel_name\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mt0\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mdebug\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(steps, iter_vars, data_out, out_interval, out_base_tables, out_run_tables, compress, out_base_local, out_run_local)\u001B[0m\n\u001B[1;32m 2166\u001B[0m \u001B[0mstep\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mget_step\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mstep_name\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 2167\u001B[0m \u001B[0mt2\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mtime\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mtime\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m-> 2168\u001B[0;31m \u001B[0mstep\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 2169\u001B[0m print(\"Time to execute step '{}': {:.2f} s\".format(\n\u001B[1;32m 2170\u001B[0m step_name, time.time() - t2))\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/orca/orca.py\u001B[0m in \u001B[0;36m__call__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 971\u001B[0m kwargs = _collect_variables(names=self._argspec.args,\n\u001B[1;32m 972\u001B[0m expressions=self._argspec.defaults)\n\u001B[0;32m--> 973\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_func\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m**\u001B[0m\u001B[0mkwargs\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 974\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 975\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m_tables_used\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/abm/models/initialize.py\u001B[0m in \u001B[0;36minitialize_landuse\u001B[0;34m()\u001B[0m\n\u001B[1;32m 98\u001B[0m \u001B[0mtrace_label\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;34m'initialize_landuse'\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 99\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 100\u001B[0;31m \u001B[0;32mwith\u001B[0m \u001B[0mchunk\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchunk_log\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtrace_label\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mbase\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 101\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 102\u001B[0m \u001B[0mmodel_settings\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mconfig\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mread_model_settings\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'initialize_landuse.yaml'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmandatory\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m~/.pyenv/versions/3.8.2/lib/python3.8/contextlib.py\u001B[0m in \u001B[0;36m__enter__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 111\u001B[0m \u001B[0;32mdel\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0margs\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mkwds\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfunc\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 112\u001B[0m \u001B[0;32mtry\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 113\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mnext\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mgen\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 114\u001B[0m \u001B[0;32mexcept\u001B[0m \u001B[0mStopIteration\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 115\u001B[0m \u001B[0;32mraise\u001B[0m \u001B[0mRuntimeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"generator didn't yield\"\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mfrom\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/chunk.py\u001B[0m in \u001B[0;36mchunk_log\u001B[0;34m(trace_label, chunk_tag, base)\u001B[0m\n\u001B[1;32m 927\u001B[0m \u001B[0;31m# avoids breaking the assertion below.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 928\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 929\u001B[0;31m \u001B[0;32massert\u001B[0m \u001B[0mbase\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;34m(\u001B[0m\u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mCHUNK_SIZERS\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 930\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 931\u001B[0m \u001B[0mtrace_label\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;34mf\"{trace_label}.chunk_log\"\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;31mAssertionError\u001B[0m: " - ] - } - ], - "source": [ - "parser = argparse.ArgumentParser()\n", - "run.add_run_args(parser)\n", - "args = parser.parse_args(['-c', 'configs', '-o', 'output', '-d', 'data'])\n", - "run.run(args) # 2mins full example run\n", - "if not inject.is_injectable('preload_injectables'):\n", - " from activitysim import abm # register abm steps and other abm-specific injectables\n", - "run.handle_standard_args(args) # possibly update injectables" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "outputs": [], - "source": [ - "## need to do this before loading checkpoint tables\n", - "#trace_hh_ids = 1024353\n", - "##1024353 has trip id 642446345 (at least) where logsum and probs are different\n", - "##2821179 # has trip_ids 2464104881 and 2464104885\n", - "#inject.add_injectable(\"trace_hh_id\", trace_hh_ids)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 9, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Couldn't find checkpoint 'trip_scheduling' in checkpoints\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0 init\n", - "1 initialize_landuse\n", - "2 initialize_households\n", - "3 compute_accessibility\n", - "4 school_location\n", - "Name: checkpoint_name, dtype: object\n" - ] - }, - { - "ename": "RuntimeError", - "evalue": "Couldn't find checkpoint 'trip_scheduling' in checkpoints", - "output_type": "error", - "traceback": [ - "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", - "\u001B[0;31mIndexError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mload_checkpoint\u001B[0;34m(checkpoint_name)\u001B[0m\n\u001B[1;32m 353\u001B[0m \u001B[0;31m# truncate rows after target checkpoint\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 354\u001B[0;31m \u001B[0mi\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcheckpoints\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mcheckpoints\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mCHECKPOINT_NAME\u001B[0m\u001B[0;34m]\u001B[0m \u001B[0;34m==\u001B[0m \u001B[0mcheckpoint_name\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mindex\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;36m0\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 355\u001B[0m \u001B[0mcheckpoints\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcheckpoints\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mloc\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0mi\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/.venv/lib/python3.8/site-packages/pandas/core/indexes/base.py\u001B[0m in \u001B[0;36m__getitem__\u001B[0;34m(self, key)\u001B[0m\n\u001B[1;32m 4603\u001B[0m \u001B[0mkey\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcom\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mcast_scalar_indexer\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mkey\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mwarn_float\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mTrue\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m-> 4604\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mgetitem\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mkey\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 4605\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;31mIndexError\u001B[0m: index 0 is out of bounds for axis 0 with size 0", - "\nDuring handling of the above exception, another exception occurred:\n", - "\u001B[0;31mRuntimeError\u001B[0m Traceback (most recent call last)", - "\u001B[0;32m\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/notebooks/fru_utils.py\u001B[0m in \u001B[0;36mrun_trip_mode_choice\u001B[0;34m(do_these_purposes, choose_individual_max_utility)\u001B[0m\n\u001B[1;32m 190\u001B[0m \u001B[0mchunk_size\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;36m0\u001B[0m \u001B[0;31m# test_mtc means no chunking\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 191\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 192\u001B[0;31m \u001B[0mpipeline\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mopen_pipeline\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mresume_after\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 193\u001B[0m \u001B[0;31m# preload any bulky injectables (e.g. skims) not in pipeline\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 194\u001B[0m \u001B[0minject\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_injectable\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m'preload_injectables'\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mopen_pipeline\u001B[0;34m(resume_after)\u001B[0m\n\u001B[1;32m 513\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdebug\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"open_pipeline - open existing pipeline\"\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 514\u001B[0m \u001B[0mopen_pipeline_store\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0moverwrite\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 515\u001B[0;31m \u001B[0mload_checkpoint\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mresume_after\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 516\u001B[0m \u001B[0;32melse\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 517\u001B[0m \u001B[0;31m# open new, empty pipeline\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;32m/mnt/c/Users/jan.zill/code/activitysim/activitysim/core/pipeline.py\u001B[0m in \u001B[0;36mload_checkpoint\u001B[0;34m(checkpoint_name)\u001B[0m\n\u001B[1;32m 362\u001B[0m \u001B[0mprint\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mcheckpoints\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0mCHECKPOINT_NAME\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 363\u001B[0m \u001B[0mlogger\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0merror\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmsg\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 364\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mRuntimeError\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmsg\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 365\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 366\u001B[0m \u001B[0;31m# convert pandas dataframe back to array of checkpoint dicts\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", - "\u001B[0;31mRuntimeError\u001B[0m: Couldn't find checkpoint 'trip_scheduling' in checkpoints" - ] - } - ], - "source": [ - "%%time\n", - "trips_df_ru = run_trip_mode_choice()\n", - "trips_df = run_trip_mode_choice(choose_individual_max_utility=False)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3ed520e9-75ef-4eec-ab86-6aaf1fe453fc", - "metadata": { - "execution": { - "iopub.execute_input": "2021-09-01T02:28:53.643404Z", - "iopub.status.busy": "2021-09-01T02:28:53.643055Z", - "iopub.status.idle": "2021-09-01T02:29:01.223951Z", - "shell.execute_reply": "2021-09-01T02:29:01.222865Z", - "shell.execute_reply.started": "2021-09-01T02:28:53.643367Z" - }, - "tags": [] - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "setting trace_hh_id is wrong type, should be an int, but was \n", - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'atwork' (27 trips)\n", - "trip_mode_choice tour_type 'eatout' (33 trips)\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n", - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", - "trip_mode_choice tour_type 'othmaint' (46 trips)\n", - "trip_mode_choice tour_type 'school' (37 trips)\n", - "trip_mode_choice tour_type 'shopping' (77 trips)\n", - "trip_mode_choice tour_type 'social' (19 trips)\n", - "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n", - "Running with %d trips 482\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'atwork' (27 trips)\n", - "trip_mode_choice tour_type 'eatout' (33 trips)\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n", - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", - "trip_mode_choice tour_type 'othmaint' (46 trips)\n", - "trip_mode_choice tour_type 'school' (37 trips)\n", - "trip_mode_choice tour_type 'shopping' (77 trips)\n", - "trip_mode_choice tour_type 'social' (19 trips)\n", - "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n", - "CPU times: user 18.3 s, sys: 2.3 s, total: 20.6 s\n", - "Wall time: 17.8 s\n" - ] - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 10, - "outputs": [ - { - "data": { - "text/plain": " trip_mode_fru trip_mode_asim\nWALK 309 304\nWALK_LOC 85 85\nWALK_LRF 55 53\nBIKE 15 17\nTNC_SINGLE 7 4\nDRIVEALONEFREE 5 4\nSHARED2FREE 3 4\nSHARED3FREE 2 1\nWALK_HVY 1 4", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_mode_frutrip_mode_asim
WALK309304
WALK_LOC8585
WALK_LRF5553
BIKE1517
TNC_SINGLE74
DRIVEALONEFREE54
SHARED2FREE34
SHARED3FREE21
WALK_HVY14
\n
" - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "c_ = [\"trip_mode\", \"mode_choice_logsum\"]\n", - "trips = trips_df_ru[c_].merge(trips_df[c_], left_index=True, right_index=True, how=\"outer\", suffixes=[\"_fru\", \"_asim\"])\n", - "pd.merge(trips_df_ru.trip_mode.value_counts(), trips_df.trip_mode.value_counts(), left_index=True, right_index=True,\n", - " suffixes=[\"_fru\", \"_asim\"])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 11, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Logsums agree\n" - ] - } - ], - "source": [ - "if np.allclose(trips.mode_choice_logsum_fru, trips.mode_choice_logsum_asim):\n", - " print(\"Logsums agree\")\n", - "else:\n", - " ((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru).hist(bins=50)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 12, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.000000e+00\n" - ] - } - ], - "source": [ - "x_ = np.abs((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim)).values\n", - "print(f\"{x_.max():e}\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 13, - "outputs": [], - "source": [ - "#ls_ = np.abs(((trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) / trips.mode_choice_logsum_fru))\n", - "#ls_.sort_values(ascending=False)\n", - "#trips.loc[trips.index == 642446345]\n", - "#trips.loc[np.abs(trips.mode_choice_logsum_fru - trips.mode_choice_logsum_asim) > 0]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Run MC validation\n", - "\n", - "see logit._each_nest parent_nest_scale for leaf node, and simulate.compute_nested_utilities for discussion of scales\n", - "of alternatives\n", - "\n", - "easiest way is to run a simulation" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 14, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with %d trips 482\n", - "trip_mode_choice tour_type 'atwork' (27 trips)\n", - "trip_mode_choice tour_type 'eatout' (33 trips)\n", - "trip_mode_choice tour_type 'escort' (6 trips)\n", - "trip_mode_choice tour_type 'othdiscr' (43 trips)\n", - "trip_mode_choice tour_type 'othmaint' (46 trips)\n", - "trip_mode_choice tour_type 'school' (37 trips)\n", - "trip_mode_choice tour_type 'shopping' (77 trips)\n", - "trip_mode_choice tour_type 'social' (19 trips)\n", - "trip_mode_choice tour_type 'univ' (26 trips)\n", - "trip_mode_choice tour_type 'work' (168 trips)\n", - "Done\n" - ] - } - ], - "source": [ - "from fru_utils import get_stuff\n", - "t, ru, neu, nu, ns, nested_probs, base_probs = get_stuff() #do_these_purposes=[\"social\"]) #social\n", - "bp = pd.concat(base_probs)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 107, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'social' (10000 trips)\n", - "Done\n", - "CPU times: user 8.39 s, sys: 453 ms, total: 8.84 s\n", - "Wall time: 9.28 s\n" - ] - } - ], - "source": [ - "%%time\n", - "#prun\n", - "\n", - "num_samples = 10000\n", - "trip_id_to_check = 642446345 # 1e5 takes about 25s EDIT: BEFORE APPLY, now it's more like 65s and 1e4 takes 8s\n", - "# work 615236801 also in trace log\n", - "# social 2464104885 642446345 1767182945\n", - "\n", - "\n", - "#bp = base_probs[7] # 7 for social, 9 for work. Check t[t.index == trip_id_to_check] to see which purpose, and\n", - "# output above to see which index\n", - "# choose_individual_max_utility = False\n", - "#ms_comp = comp_mode_shares(bp, choose_individual_max_utility, num_samples, trip_id_to_check)\n", - "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)\n", - "\n", - "choose_individual_max_utility = True\n", - "t_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=choose_individual_max_utility,\n", - " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", - "sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", - "ms_comp = comp_mode_shares(bp, sim_mode_shares, trip_id_to_check)\n", - "#display(ms_comp.loc[((ms_comp.mode_share_obs != 0) | (ms_comp.trip_mode != 0)), [\"mode_share_obs\", \"trip_mode\"]].T)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 64, - "outputs": [ - { - "data": { - "text/plain": "(0.3439655414093617, nan)" - }, - "execution_count": 64, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Note power tests will be inf when expected count is zero (any non-zero count will be impossible)\n", - "eps = 0 #1e-14\n", - "non_zero_shares = ms_comp.loc[(ms_comp.mode_share_obs > eps) & (ms_comp.trip_mode > eps)][[\"mode_share_obs\", \"trip_mode\"]]\n", - "ddof = non_zero_shares.shape[0] - 1 # what to use here? num alternatives - 1? What about zero expected count?\n", - "\n", - "non_zero_shares.trip_mode *= num_samples\n", - "\n", - "sum_ms = non_zero_shares.mode_share_obs.sum()\n", - "if sum_ms != 1.0:\n", - " if np.abs(1.0 - sum_ms) > 1e-1:\n", - " print(f\"NOT GOOD {sum_ms} should be one\")\n", - " #print(f\"sum ms is not one but {sum_ms}, re-normalising\")\n", - " non_zero_shares.mode_share_obs /= sum_ms\n", - "\n", - "powerdiscrepancy(non_zero_shares.trip_mode.values, non_zero_shares.mode_share_obs.values, lambd=0, ddof=ddof)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 99, - "outputs": [ - { - "data": { - "text/plain": "array([[4.17345080e-03, 5.98926219e-03, 5.00000000e-03],\n [1.45406892e-04, 6.18851051e-04, 3.00000000e-04],\n [4.13473933e-02, 4.66025328e-02, 4.39000000e-02],\n [5.64412252e-02, 6.25036058e-02, 5.94000000e-02],\n [8.87346329e-01, 8.95325012e-01, 8.91400000e-01]])" - }, - "execution_count": 99, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from statsmodels.stats.proportion import multinomial_proportions_confint\n", - "ci = multinomial_proportions_confint(non_zero_shares.trip_mode.values, alpha=0.999, method='goodman')\n", - "vals = non_zero_shares.trip_mode.values / num_samples\n", - "np.hstack((ci, vals.reshape((len(vals),1))))" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 106, - "outputs": [ - { - "data": { - "text/plain": "array([ True, True, True, True, True])" - }, - "execution_count": 106, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import numba\n", - "\n", - "@numba.jit\n", - "def within(lower, upper, x):\n", - " return (x >= lower) & (x <= upper)\n", - "\n", - "@numba.jit\n", - "def numpy_within(x):\n", - " lower = x[0]\n", - " upper = x[1]\n", - " val = x[2]\n", - " return within(lower, upper, val)\n", - "\n", - "test = np.hstack((ci, vals.reshape((len(vals),1))))\n", - "np.apply_along_axis(numpy_within, 1, test)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# All trips for paper\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "%%time\n", - "\n", - "num_samples = 10000 #about 8.5s per 10k per trip, so about 70mins for 10k sample. 1e5 is about 10 times as long, so\n", - "# about 11.5h\n", - "\n", - "trips_to_check = bp.index.values #[100:103]\n", - "\n", - "res = []\n", - "\n", - "for trip_id_to_check in trips_to_check:\n", - " _, c_, _ = mode_choice_for_trip(choose_individual_max_utility=True,\n", - " trip_id_to_check=trip_id_to_check, num_samples=num_samples)\n", - " sim_mode_shares = c_.trip_mode.value_counts() / c_.shape[0]\n", - " res.append(sim_mode_shares.rename(trip_id_to_check).to_frame().T)\n", - "\n", - "result_sim = pd.concat(res).fillna(0)\n", - "result_sim.to_csv(\"temp_res_sim_10ksamples.csv\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n", - "is_executing": true - } - } - }, - { - "cell_type": "code", - "execution_count": 158, - "outputs": [], - "source": [ - "#result_sim.merge(bp, left_index=True, right_index=True, how=\"outer\", suffixes=[\"_sim\", \"_asim\"]).fillna(0)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 155, - "outputs": [], - "source": [ - "merged_results = result_sim.reset_index().rename(columns={\"index\": \"trip_id\"}).melt(id_vars=[\"trip_id\"]).merge(\n", - " bp.reset_index().rename(columns={\"index\": \"trip_id\"}).melt(id_vars=[\"trip_id\"]),\n", - " on=[\"trip_id\", \"variable\"],\n", - " how=\"outer\",\n", - " suffixes=[\"_sim\", \"_obs\"]\n", - ").fillna(0)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 302, - "outputs": [], - "source": [ - "# should use only those without zero probability - they are really not available so not a valid choice\n", - "merged_results_wo_zeros = merged_results.loc[(merged_results.value_sim != 0) & (merged_results.value_obs != 0)]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 303, - "outputs": [ - { - "data": { - "text/plain": "" - }, - "execution_count": 303, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": "
", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "import seaborn as sns\n", - "\n", - "sns.scatterplot(data=merged_results_wo_zeros, x=\"value_sim\", y=\"value_obs\")\n", - "#sns.regplot(data=merged_results, x=\"value_sim\", y=\"value_obs\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 304, - "outputs": [ - { - "data": { - "text/plain": "LinregressResult(slope=0.9998410722635966, intercept=4.4762082968718975e-05, rvalue=0.9999904091368037, pvalue=0.0, stderr=0.00011336879523644994, intercept_stderr=5.826949896067866e-05)" - }, - "execution_count": 304, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "#from scipy.special import rel_entr\n", - "from scipy.stats import linregress\n", - "linregress(merged_results_wo_zeros.value_sim.values, merged_results_wo_zeros.value_obs.values)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 305, - "outputs": [ - { - "data": { - "text/plain": "" - }, - "execution_count": 305, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": "
", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "sns.residplot(data=merged_results_wo_zeros, x=\"value_sim\", y=\"value_obs\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 306, - "outputs": [], - "source": [ - "# # Note power tests will be inf when expected count is zero (any non-zero count will be impossible)\n", - "# eps = 0 #1e-14\n", - "# non_zero_shares = ms_comp.loc[(ms_comp.mode_share_obs > eps) & (ms_comp.trip_mode > eps)][[\"mode_share_obs\", \"trip_mode\"]]\n", - "# ddof = non_zero_shares.shape[0] - 1 # what to use here? num alternatives - 1? What about zero expected count?\n", - "#\n", - "# non_zero_shares.trip_mode *= num_samples\n", - "#\n", - "# sum_ms = non_zero_shares.mode_share_obs.sum()\n", - "# if sum_ms != 1.0:\n", - "# if np.abs(1.0 - sum_ms) > 1e-1:\n", - "# print(f\"NOT GOOD {sum_ms} should be one\")\n", - "# #print(f\"sum ms is not one but {sum_ms}, re-normalising\")\n", - "# non_zero_shares.mode_share_obs /= sum_ms\n", - "#\n", - "# powerdiscrepancy(non_zero_shares.trip_mode.values, non_zero_shares.mode_share_obs.values, lambd=0, ddof=ddof)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 314, - "outputs": [], - "source": [ - "@numba.jit\n", - "def within(lower, upper, x):\n", - " return (x >= lower) & (x <= upper)\n", - "\n", - "@numba.jit\n", - "def numpy_within(x):\n", - " lower = x[0]\n", - " upper = x[1]\n", - " val = x[2]\n", - " return within(lower, upper, val)\n", - "\n", - "def all_within_ci(trip_df, num_samples, alpha=0.9):\n", - " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=alpha, method='goodman')\n", - " vals_with_ci = np.hstack((ci, trip_df[['value_obs']].to_numpy()))\n", - " return np.apply_along_axis(numpy_within, 1, vals_with_ci).all()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 328, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Have 17 with contribution outside 95.00% CI, corresponding to 1.14% of all non-zero observations.\n" - ] - }, - { - "data": { - "text/plain": " lb ub value within\n3 0.000031 0.001291 0.000030 False\n3 0.000031 0.001291 0.000025 False\n8 0.003744 0.008367 0.003717 False\n8 0.000009 0.001113 0.000008 False\n1 0.000009 0.001113 0.000006 False\n1 0.000009 0.001113 0.000008 False\n3 0.000062 0.001460 0.000058 False", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
lbubvaluewithin
30.0000310.0012910.000030False
30.0000310.0012910.000025False
80.0037440.0083670.003717False
80.0000090.0011130.000008False
10.0000090.0011130.000006False
10.0000090.0011130.000008False
30.0000620.0014600.000058False
\n
" - }, - "execution_count": 328, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "alpha = 0.05 # default. this corresponds to a 95% CI, so 95% of all values should lie within our approximation\n", - "vals_within_ci = merged_results_wo_zeros.groupby(\"trip_id\").apply(lambda group: all_within_ci(group, num_samples, alpha))\n", - "trips_not_within = vals_within_ci.loc[~vals_within_ci]\n", - "print(f\"Have {trips_not_within.shape[0]} with contribution outside {1 - alpha:.2%} CI, corresponding to \"\n", - " f\"{trips_not_within.shape[0] / merged_results_wo_zeros.shape[0]:.2%} of all non-zero observations.\")\n", - "\n", - "bad_res = []\n", - "\n", - "for bad_trip_id in trips_not_within.index.values:\n", - " trip_df = merged_results.loc[merged_results.trip_id == bad_trip_id]\n", - " ci = multinomial_proportions_confint(num_samples * trip_df.value_sim.values, alpha=alpha, method='goodman')\n", - " vals_with_ci = np.hstack((ci, trip_df[['value_obs']].to_numpy()))\n", - " x_ = pd.DataFrame(vals_with_ci)\n", - " x_['in_range'] = np.apply_along_axis(numpy_within, 1, vals_with_ci)[:np.newaxis]\n", - " bad_res.append(x_.loc[~x_.in_range])\n", - "bad_res = pd.concat(bad_res)\n", - "bad_res.columns = [\"lb\", \"ub\", \"value\", \"within\"]\n", - "bad_res" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## CI is not great for this\n", - "\n", - "we expect about 5% outliers for 95% CI, and I can see for 1e5 samples the number of outliers actually nearly doubles for the 95CI which makes sense - it is the large sample limit where all this is exact.\n", - "\n", - "Let's plot a histogram of the std deviation, and take a single trip and plot the diff from expected value with number of draws (i.e. prob vs num_draws)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "### histogram of residuals" - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "code", - "execution_count": 440, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5.4540809129771874e-05\n" - ] - }, - { - "data": { - "text/plain": "
", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "merged_results['diff_squared'] = np.square(merged_results.value_sim - merged_results.value_obs)\n", - "print(merged_results['diff_squared'].max())\n", - "plt.hist(merged_results.diff_squared.values, bins=100);" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 448, - "outputs": [ - { - "data": { - "text/plain": "
", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# remove where both are unavailable\n", - "plt.hist(merged_results.loc[(merged_results.value_obs != 0) & (merged_results.value_sim != 0)].diff_squared.values, bins=100);\n", - "plt.yscale('log')" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "### single trip-mode convergence to expected value" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 431, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "estimation bundle trip_mode_choice not in settings file estimation.yaml\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "trip_mode_choice tour_type 'othdiscr' (10000 trips)\n", - "Done\n" - ] - }, - { - "data": { - "text/plain": " trip_mode drawn exact\n0 WALK_LRF 0.3210 0.322184\n1 WALK_HVY 0.2812 0.278911\n2 TNC_SINGLE 0.2170 0.219172\n3 WALK_LOC 0.1780 0.176455\n4 TNC_SHARED 0.0018 0.002318\n5 WALK 0.0008 0.000713\n6 SHARED2FREE 0.0001 0.000179\n7 SHARED3FREE 0.0001 0.000063\n20 TAXI 0.0000 0.000005", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
trip_modedrawnexact
0WALK_LRF0.32100.322184
1WALK_HVY0.28120.278911
2TNC_SINGLE0.21700.219172
3WALK_LOC0.17800.176455
4TNC_SHARED0.00180.002318
5WALK0.00080.000713
6SHARED2FREE0.00010.000179
7SHARED3FREE0.00010.000063
20TAXI0.00000.000005
\n
" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": "(1e-08, 1)" - }, - "execution_count": 431, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "text/plain": "
", - "image/png": "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\n" - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "num_samples_single = 10000\n", - "trip_id_to_check = 1767182865 #bp.index.values #[100:103]\n", - "\n", - "_, c_, _ = mode_choice_for_trip(choose_individual_max_utility=True,\n", - " trip_id_to_check=trip_id_to_check, num_samples=num_samples_single)\n", - "c_ = c_[['trip_mode']].reset_index(drop=True)\n", - "\n", - "temp = bp.loc[bp.index == trip_id_to_check]\n", - "temp.index = [\"exact\"]\n", - "temp = temp.T.reset_index().rename(columns={\"index\": \"trip_mode\"})\n", - "ms = (c_.value_counts() / c_.shape[0]).to_frame(\"drawn\").reset_index().merge(temp, on=\"trip_mode\", how=\"outer\").fillna(0)\n", - "ms = ms.loc[(ms.exact != 0)]\n", - "display(ms)\n", - "c_test = c_.copy()\n", - "c_test.index = np.arange(1, c_test.index.shape[0]+1)\n", - "check_mode = \"WALK_LRF\"\n", - "ms_exact = ms.loc[ms.trip_mode == check_mode].exact.values[0]\n", - "\n", - "c_test['trip_mode'] = np.where(c_test['trip_mode'] == check_mode, 1, 0)\n", - "c_test = c_test.cumsum()\n", - "c_test[\"share\"] = c_test.trip_mode / c_test.index\n", - "c_test[\"diff\"] = np.square(c_test[\"share\"] - ms_exact)\n", - "\n", - "plt.plot(c_test.index.values, c_test['diff'].values)\n", - "plt.plot(np.arange(1,num_samples_single), 1.0/np.arange(1,num_samples_single))\n", - "plt.yscale('log')\n", - "plt.xscale('log')\n", - "plt.ylim([1e-8,1])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# Dev corner" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## try to improve runtime of apply\n", - "\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 239, - "outputs": [ - { - "data": { - "text/plain": " DRIVEALONEFREE DRIVEALONEPAY SHARED2FREE SHARED2PAY \\\ntrip_id \n642446345 0.0 0.0 0.004883 0.0 \n\n SHARED3FREE SHARED3PAY WALK BIKE WALK_LOC WALK_LRF \\\ntrip_id \n642446345 4.701403e-14 0.0 0.044905 0.0 0.059962 0.889736 \n\n WALK_EXP WALK_HVY WALK_COM DRIVE_LOC DRIVE_LRF DRIVE_EXP \\\ntrip_id \n642446345 0.0 0.0 0.0 0.0 0.0 0.0 \n\n DRIVE_HVY DRIVE_COM TAXI TNC_SINGLE TNC_SHARED \ntrip_id \n642446345 0.0 0.0 0.000096 0.000131 0.000287 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
DRIVEALONEFREEDRIVEALONEPAYSHARED2FREESHARED2PAYSHARED3FREESHARED3PAYWALKBIKEWALK_LOCWALK_LRFWALK_EXPWALK_HVYWALK_COMDRIVE_LOCDRIVE_LRFDRIVE_EXPDRIVE_HVYDRIVE_COMTAXITNC_SINGLETNC_SHARED
trip_id
6424463450.00.00.0048830.04.701403e-140.00.0449050.00.0599620.8897360.00.00.00.00.00.00.00.00.0000960.0001310.000287
\n
" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "nest_spec = ns[7] # 7 is social\n", - "nest_util = nu[7].loc[nu[7].index == trip_id_to_check]\n", - "\n", - "n = 10000\n", - "\n", - "nest_util = nest_util.loc[nest_util.index.repeat(n)]\n", - "nest_util.index += np.arange(n)\n", - "\n", - "display(base_probs[7].loc[base_probs[7].index == trip_id_to_check])" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 240, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 31.2 ms, sys: 0 ns, total: 31.2 ms\n", - "Wall time: 21.1 ms\n" - ] - } - ], - "source": [ - "%%time\n", - "\n", - "from numpy.random import default_rng\n", - "from activitysim.core import logit\n", - "\n", - "rng = default_rng(999)\n", - "\n", - "def random_for_df(df):\n", - " return rng.random(size=df.shape)\n", - "\n", - "def add_ev1_random(df):\n", - " uniform_rands = random_for_df(df)\n", - " df = df + logit.inverse_ev1_cdf(uniform_rands)\n", - " return df\n", - "\n", - "nest_utils_for_choice = add_ev1_random(nest_util)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 241, - "outputs": [], - "source": [ - "def group_nests_by_name(nest_spec):\n", - " # group nests by level, returns {level: [nest.name at that level]}\n", - " depth = np.max([x.level for x in logit.each_nest(nest_spec)])\n", - " nest_levels = {x: [] for x in range(1, depth+1)}\n", - " for n in logit.each_nest(nest_spec):\n", - " nest_levels[n.level].append(n.name)\n", - " return nest_levels\n", - "\n", - "def make_choices_apply(nested_utilities, all_alternatives, logit_nest_groups, nest_alternatives_by_name):\n", - " for level, nest_names in logit_nest_groups.items():\n", - " if level == 1:\n", - " next_level_alts = nest_alternatives_by_name[nest_names[0]]\n", - " continue\n", - " choice_this_level = nested_utilities[nested_utilities.index.isin(next_level_alts)].idxmax()\n", - " if choice_this_level in all_alternatives:\n", - " return choice_this_level\n", - " next_level_alts = nest_alternatives_by_name[choice_this_level]\n", - " raise ValueError(\"This should never happen - no alternative found\")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 242, - "outputs": [], - "source": [ - "%%prun\n", - "all_alternatives = set(nest.name for nest in logit.each_nest(nest_spec, type='leaf'))\n", - "logit_nest_groups = group_nests_by_name(nest_spec)\n", - "nest_alternatives_by_name = {n.name: n.alternatives for n in logit.each_nest(nest_spec)}\n", - "\n", - "choices_apply = nest_utils_for_choice.apply(\n", - " lambda x: make_choices_apply(x, all_alternatives, logit_nest_groups, nest_alternatives_by_name),\n", - " axis=1\n", - ")" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 243, - "outputs": [ - { - "data": { - "text/plain": "WALK_LRF 0.8383\nWALK_LOC 0.1158\nWALK 0.0405\nSHARED2FREE 0.0048\nTNC_SHARED 0.0003\nTAXI 0.0002\nTNC_SINGLE 0.0001\ndtype: float64" - }, - "execution_count": 243, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "choices_apply.value_counts() / n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "# stuff\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 172, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "register joint_tour_participants: no rows with household_id in [982875].\n" - ] - } - ], - "source": [ - "from activitysim.core import pipeline\n", - "\n", - "resume_after = \"trip_scheduling\"\n", - "model_name = \"trip_mode_choice\"\n", - "chunk_size = 0 # test_mtc means no chunking\n", - "\n", - "pipeline.open_pipeline(resume_after)\n", - "# preload any bulky injectables (e.g. skims) not in pipeline\n", - "inject.get_injectable('preload_injectables', None)\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "#step_name = model_name\n", - "args = {}\n", - "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "\n", - "finalise = True\n", - "if finalise:\n", - " inject.set_step_args(None)\n", - " #\n", - " pipeline._PIPELINE.rng().end_step(model_name)\n", - " pipeline.add_checkpoint(model_name)\n", - " if not pipeline.intermediate_checkpoint():\n", - " pipeline.add_checkpoint(pipeline.FINAL_CHECKPOINT_NAME)\n", - "\n", - " pipeline.close_pipeline()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 176, - "outputs": [], - "source": [ - "t_ = trips.to_frame()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 177, - "outputs": [ - { - "data": { - "text/plain": " person_id household_id primary_purpose trip_num outbound \\\ntrip_id \n2464104885 7512514 2821179 social 1 False \n\n trip_count destination origin tour_id purpose \\\ntrip_id \n2464104885 1 8 9 308013110 home \n\n destination_logsum depart \ntrip_id \n2464104885 NaN 16.0 ", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
person_idhousehold_idprimary_purposetrip_numoutboundtrip_countdestinationorigintour_idpurposedestination_logsumdepart
trip_id
246410488575125142821179social1False189308013110homeNaN16.0
\n
" - }, - "execution_count": 177, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "t_.loc[t_.index == 2464104885]" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 52, - "outputs": [], - "source": [ - "from activitysim.core import config\n", - "resume_after = \"trip_scheduling\"\n", - "model_name = \"trip_mode_choice\"\n", - "\n", - "pipeline.open_pipeline(resume_after)\n", - "# preload any bulky injectables (e.g. skims) not in pipeline\n", - "inject.get_injectable('preload_injectables', None)\n", - "pipeline._PIPELINE.rng().begin_step(model_name)\n", - "#step_name = model_name\n", - "args = {}\n", - "#checkpoint = pipeline.intermediate_checkpoint(model_name)\n", - "inject.set_step_args(args)\n", - "\n", - "trips = inject.get_table('trips')\n", - "tours_merged = inject.get_table('tours_merged')\n", - "network_los = inject.get_injectable('network_los')\n", - "\n", - "trace_label = 'trip_mode_choice'\n", - "model_settings_file_name = 'trip_mode_choice.yaml'\n", - "model_settings = config.read_model_settings(model_settings_file_name)\n", - "\n", - "logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')\n", - "mode_column_name = 'trip_mode'\n", - "\n", - "trips_df = trips.to_frame()\n", - "\n", - "tours_merged = tours_merged.to_frame()\n", - "tours_merged = tours_merged[model_settings['TOURS_MERGED_CHOOSER_COLUMNS']]\n", - "\n", - "# - trips_merged - merge trips and tours_merged\n", - "trips_merged = pd.merge(\n", - " trips_df,\n", - " tours_merged,\n", - " left_on='tour_id',\n", - " right_index=True,\n", - " how=\"left\")\n", - "assert trips_merged.index.equals(trips.index)\n", - "\n", - "# setup skim keys\n", - "assert ('trip_period' not in trips_merged)\n", - "trips_merged['trip_period'] = network_los.skim_time_period_label(trips_merged.depart)\n", - "pipeline.close_pipeline()" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 56, - "outputs": [ - { - "data": { - "text/plain": "Index(['person_id', 'household_id', 'primary_purpose', 'trip_num', 'outbound',\n 'trip_count', 'destination', 'origin', 'tour_id', 'purpose',\n 'destination_logsum', 'depart', 'hhsize', 'age', 'auto_ownership',\n 'number_of_participants', 'tour_category', 'parent_tour_id',\n 'tour_mode', 'duration', 'value_of_time', 'tour_type',\n 'free_parking_at_work', 'trip_period'],\n dtype='object')" - }, - "execution_count": 56, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trips_merged.columns" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file From e1e9d088d706019f9c09639aded311dc31dae8ac Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 2 Apr 2025 15:48:46 +1000 Subject: [PATCH 099/298] seeding corr test --- .../2025/correlation_variance_tests.ipynb | 706 ++++++++++++++++++ 1 file changed, 706 insertions(+) create mode 100644 notebooks/2025/correlation_variance_tests.ipynb diff --git a/notebooks/2025/correlation_variance_tests.ipynb b/notebooks/2025/correlation_variance_tests.ipynb new file mode 100644 index 0000000000..3b1ae83dd9 --- /dev/null +++ b/notebooks/2025/correlation_variance_tests.ipynb @@ -0,0 +1,706 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Which correlations do we want\n", + "\n", + "simulate destination mode choice model:\n", + " 1) MC\n", + " 2) EET fixed error per level\n", + " 3) EET only fixed when identical\n", + "\n", + "Does it make a difference in analysis?" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2022-05-01T05:31:19.564033Z", + "iopub.status.busy": "2022-05-01T05:31:19.563663Z", + "iopub.status.idle": "2022-05-01T05:31:19.824207Z", + "shell.execute_reply": "2022-05-01T05:31:19.823432Z", + "shell.execute_reply.started": "2022-05-01T05:31:19.563960Z" + } + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "from numpy.random import default_rng\n", + "from copy import deepcopy" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "rng = default_rng(999)\n", + "\n", + "### Zones\n", + "num_zones = 10\n", + "destination = pd.DataFrame(\n", + " data=zip(np.arange(0, num_zones), rng.normal(1000, 10, size=num_zones), strict=True),\n", + " columns=['zone', 'attrs'],\n", + ")\n", + "\n", + "### People - assume each person makes one trip from random origin\n", + "num_ppl = 10000\n", + "vars=['id', 'income', 'orig']\n", + "ppl = pd.DataFrame(\n", + " data=zip(\n", + " np.arange(0, num_ppl),\n", + " rng.normal(50, 20, size=(num_ppl)).clip(2, 150),\n", + " rng.choice(np.arange(0, num_zones), num_ppl),\n", + " strict=True,\n", + " ),\n", + " columns=vars,\n", + ")\n", + "\n", + "\n", + "### Modes\n", + "num_modes = 3\n", + "mode_idx_to_str = {0: 'car', 1: 'pt', 2: 'walk'}\n", + "modes = np.array(['car', 'pt', 'walk'])\n", + "mode_asc = {'car': 2.5, 'pt': 0.0, 'walk': 10.0}\n", + "\n", + "### Cost and time by mode\n", + "skims = {}\n", + "skims['time'] = {}\n", + "skims['time']['car'] = rng.normal(20, 20, size=(num_zones, num_zones)).clip(4, 60)\n", + "skims['time']['pt'] = rng.normal(40, 10, size=(num_zones, num_zones)).clip(4, 80)\n", + "skims['time']['walk'] = 3.0 * skims['time']['car'].copy()\n", + "\n", + "skims['cost'] = {}\n", + "skims['cost']['car'] = skims['time']['car'].copy() / 4.0\n", + "skims['cost']['pt'] = 2.0 * np.ones_like(skims['time']['pt'])\n", + "skims['cost']['walk'] = np.zeros_like(skims['time']['walk'])\n", + "\n", + "\n", + "# scenario: decrease pt travel time to and from zone 0 by 70%\n", + "scenario_skims = deepcopy(skims)\n", + "scenario_skims['time']['pt'][0,:] *= 0.3\n", + "scenario_skims['time']['pt'] *= 0.3" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def logsum(utilities, nest_scale=1.0):\n", + " scaled_utils = utilities / nest_scale\n", + " max_util = np.max(scaled_utils, axis=1)\n", + " return max_util + np.log(np.sum(np.exp(scaled_utils - max_util.reshape((-1,1))), axis=1))\n", + "\n", + "def inverse_ev1_cdf(x, location=0.0, scale=1.0):\n", + " return location - scale * np.log(-np.log(x))\n", + "\n", + "### Utility function parameters\n", + "beta_time = {}\n", + "beta_time['car'] = -0.06\n", + "beta_time['pt'] = -0.03\n", + "beta_time['walk'] = -0.2\n", + "beta_cost = -0.01\n", + "\n", + "def mode_utility(mode, income, o, d, skims):\n", + " return (\n", + " beta_time[mode] * skims['time'][mode][o,d]\n", + " + beta_cost * skims['cost'][mode][o,d] / income\n", + " + mode_asc[mode]\n", + " )\n", + "\n", + "def mode_logsum(o: int, d: np.array, income: float, skims):\n", + " mode_utils = np.array([mode_utility(m_, income, o, d, skims) for m_ in modes]).T\n", + "\n", + " return logsum(mode_utils, 1.0)\n", + "\n", + "# mode_logsum(0, np.array([0, 1, 2]), 100)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "#inverse_ev1_cdf(default_rng(seed=np.random.SeedSequence([777, 333])).random(100))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "beta_logsum = -0.1\n", + "\n", + "dest_altneratives = destination.zone.values\n", + "num_dests = dest_altneratives.shape[0]\n", + "dest_attrs = np.log(destination['attrs'].values)\n", + "ppl['dest_utils'] = ppl.apply(\n", + " lambda x: (\n", + " beta_logsum * mode_logsum(int(x['orig']), dest_altneratives, x['income'], skims)\n", + " + dest_attrs\n", + " ),\n", + " axis=1,\n", + ")\n", + "ppl['dest_utils_scen'] = ppl.apply(\n", + " lambda x: (\n", + " beta_logsum * mode_logsum(int(x['orig']), dest_altneratives, x['income'], scenario_skims)\n", + " + dest_attrs\n", + " ),\n", + " axis=1,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "### EET\n", + "def destination_choice_model(ppl, seed=777, util_column_name=\"dest_utils\"):\n", + " dest_choice = ppl.apply(\n", + " lambda x: np.argmax(\n", + " x[util_column_name] + inverse_ev1_cdf(\n", + " default_rng(seed=np.random.SeedSequence([seed, int(x['id']), 100])).random(size=num_dests)\n", + " )\n", + " )\n", + " , axis=1\n", + " )\n", + " return dest_choice\n", + "\n", + "def mode_choice_model(ppl, skims, dest_col_name='dest', seed=777, od_seed_contrib=False):\n", + " \"\"\"\"od_seed_contrib: if True, seed depedns on destination\"\"\"\n", + " def seed_o_d(d):\n", + " if od_seed_contrib:\n", + " return d\n", + " return 0\n", + "\n", + " mode_choice = ppl.apply(\n", + " lambda x: np.argmax(\n", + " [mode_utility(m_, x['income'], int(x['orig']), int(x[dest_col_name]), skims) for m_ in modes]\n", + " + inverse_ev1_cdf(default_rng(seed=np.random.SeedSequence(\n", + " [seed, int(x['id']), 200, seed_o_d(int(x[dest_col_name]))]\n", + " )).random(size=num_modes))\n", + " ),\n", + " axis=1,\n", + " )\n", + " #mode_choice = mode_utils.apply(np.argmax)\n", + " return mode_choice.map(mode_idx_to_str)\n", + "\n", + "### MC\n", + "def destination_choice_model_mc(ppl, seed=777, util_column_name=\"dest_utils\"):\n", + " dest_choice = ppl.apply(\n", + " lambda x: default_rng(\n", + " seed=np.random.SeedSequence([seed, int(x['id']), 100])\n", + " ).choice(dest_altneratives, p=np.exp(x[util_column_name]) / np.sum(np.exp(x[util_column_name])), size=1)[0],\n", + " axis=1,\n", + " )\n", + " return dest_choice\n", + "\n", + "def mode_choice_model_mc(ppl, skims, dest_col_name='dest', seed=777, od_seed_contrib=False):\n", + " def seed_o_d(d):\n", + " if od_seed_contrib:\n", + " return d\n", + " return 0\n", + "\n", + " ppl['mode_exp_utils'] = ppl.apply(\n", + " lambda x: np.exp(\n", + " [mode_utility(m_, x['income'], int(x['orig']), int(x[dest_col_name]), skims) for m_ in modes]\n", + " ),\n", + " axis=1,\n", + " )\n", + " mode_choice = ppl.apply(\n", + " lambda x: default_rng(\n", + " seed=np.random.SeedSequence([seed, int(x['id']), 200, seed_o_d(int(x[dest_col_name]))])\n", + " ).choice(modes, p=x.mode_exp_utils / np.sum(x.mode_exp_utils), size=1)[0],\n", + " axis=1,\n", + " )\n", + " ppl.drop(columns=['mode_exp_utils'], inplace=True)\n", + " return mode_choice" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "#for i in range(0, 10):\n", + "# ppl.dest_utils.apply(lambda x: (np.exp(x) / np.sum(np.exp(x)))[i]).hist(figsize=(3,2))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# 10k ppl: per seed about 3.5s total [w/o sedd, with 12.4]\n", + "for s_ in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:\n", + " ppl[f'dest_{s_}'] = destination_choice_model(ppl, util_column_name='dest_utils', seed=s_)\n", + " ppl[f'dest_scen_{s_}'] = destination_choice_model(ppl, util_column_name='dest_utils_scen', seed=s_)\n", + " ppl[f'dest_mc_{s_}'] = destination_choice_model_mc(ppl, util_column_name='dest_utils', seed=s_)\n", + " ppl[f'dest_mc_scen_{s_}'] = destination_choice_model_mc(ppl, util_column_name='dest_utils_scen', seed=s_)\n", + " \n", + " \n", + " ppl[f'mode_choice_{s_}'] = mode_choice_model(ppl, skims, dest_col_name=f'dest_{s_}', seed=s_)\n", + " ppl[f'mode_choice_scen_{s_}'] = mode_choice_model(ppl, scenario_skims, dest_col_name=f'dest_scen_{s_}', seed=s_)\n", + " #ppl[f'mode_choice_seedd_{s_}'] = mode_choice_model(ppl, skims, dest_col_name=f'dest_{s_}', seed=s_, od_seed_contrib=True)\n", + " #ppl[f'mode_choice_seedd_scen_{s_}'] = mode_choice_model(ppl, scenario_skims, dest_col_name=f'dest_scen_{s_}', seed=s_, od_seed_contrib=True)\n", + "\n", + " ppl[f'mode_choice_mc_{s_}'] = mode_choice_model_mc(ppl, skims, dest_col_name=f'dest_mc_{s_}', seed=s_)\n", + " ppl[f'mode_choice_mc_scen_{s_}'] = mode_choice_model_mc(ppl, scenario_skims, dest_col_name=f'dest_mc_scen_{s_}', seed=s_)\n", + " #ppl[f'mode_choice_mc_seedd_{s_}'] = mode_choice_model_mc(ppl, skims, dest_col_name=f'dest_mc_{s_}', seed=s_, od_seed_contrib=True)\n", + " #ppl[f'mode_choice_mc_seedd_scen_{s_}'] = mode_choice_model_mc(ppl, scenario_skims, dest_col_name=f'dest_mc_scen_{s_}', seed=s_, od_seed_contrib=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "def display_metric(metric, ppl, seed):\n", + " return ( \n", + " ppl[f'{metric}_{seed}'].value_counts().to_frame(f'eet_base_{seed}').join(\n", + " ppl[f'{metric}_mc_{seed}'].value_counts().to_frame(f'mc_base_{seed}'), how='outer').join(\n", + " ppl[f'{metric}_scen_{seed}'].value_counts().to_frame(f'eet_scen_{seed}'), how='outer').join(\n", + " ppl[f'{metric}_mc_scen_{seed}'].value_counts().to_frame(f'mc_scen_{seed}'), how='outer').fillna(0).astype(int)\n", + " )\n", + " # ppl[f'mode_choice_seedd_{seed}'].value_counts().to_frame(f'eet_base_seedd_{seed}'), how='outer').join(\n", + " # ppl[f'mode_choice_mc_seedd_{seed}'].value_counts().to_frame(f'mc_base_seedd_{seed}'), how='outer').join(\n", + " # ppl[f'mode_choice_seedd_scen_{seed}'].value_counts().to_frame(f'eet_scen_seedd_{seed}'), how='outer').join(\n", + " # ppl[f'mode_choice_mc_seedd_scen_{seed}'].value_counts().to_frame(f'mc_scen_seedd_{seed}'), how='outer')\\" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
eet_base_0mc_base_0eet_scen_0mc_scen_0eet_base_1mc_base_1eet_scen_1mc_scen_1eet_base_2mc_base_2eet_scen_2mc_scen_2
car619162155223518861706117517251836163620151745246
pt149114732445248914691493246024291448142624252360
walk231823122332232323612390236823882389237324012394
\n", + "
" + ], + "text/plain": [ + " eet_base_0 mc_base_0 eet_scen_0 mc_scen_0 eet_base_1 mc_base_1 \\\n", + "car 6191 6215 5223 5188 6170 6117 \n", + "pt 1491 1473 2445 2489 1469 1493 \n", + "walk 2318 2312 2332 2323 2361 2390 \n", + "\n", + " eet_scen_1 mc_scen_1 eet_base_2 mc_base_2 eet_scen_2 mc_scen_2 \n", + "car 5172 5183 6163 6201 5174 5246 \n", + "pt 2460 2429 1448 1426 2425 2360 \n", + "walk 2368 2388 2389 2373 2401 2394 " + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([display_metric('mode_choice', ppl, i) for i in range(0, 3)], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
eet_base_0mc_base_0eet_scen_0mc_scen_0eet_base_1mc_base_1eet_scen_1mc_scen_1eet_base_2mc_base_2eet_scen_2mc_scen_2
0117011041159108911051131108511171138110411231097
1106110381065104410891079109810751056103710621035
2791857801865864906869915882807891814
3103411041034109710781029107010311046106810411062
4951873955881919896930908923920934924
5103210371023103210211053102010421007104610041046
610311028102910239719899729891048105010451046
7871856868862902897899895876932879934
810271026103110339701035975104110159801014985
91032107710351074108198510829871009105610071057
\n", + "
" + ], + "text/plain": [ + " eet_base_0 mc_base_0 eet_scen_0 mc_scen_0 eet_base_1 mc_base_1 \\\n", + "0 1170 1104 1159 1089 1105 1131 \n", + "1 1061 1038 1065 1044 1089 1079 \n", + "2 791 857 801 865 864 906 \n", + "3 1034 1104 1034 1097 1078 1029 \n", + "4 951 873 955 881 919 896 \n", + "5 1032 1037 1023 1032 1021 1053 \n", + "6 1031 1028 1029 1023 971 989 \n", + "7 871 856 868 862 902 897 \n", + "8 1027 1026 1031 1033 970 1035 \n", + "9 1032 1077 1035 1074 1081 985 \n", + "\n", + " eet_scen_1 mc_scen_1 eet_base_2 mc_base_2 eet_scen_2 mc_scen_2 \n", + "0 1085 1117 1138 1104 1123 1097 \n", + "1 1098 1075 1056 1037 1062 1035 \n", + "2 869 915 882 807 891 814 \n", + "3 1070 1031 1046 1068 1041 1062 \n", + "4 930 908 923 920 934 924 \n", + "5 1020 1042 1007 1046 1004 1046 \n", + "6 972 989 1048 1050 1045 1046 \n", + "7 899 895 876 932 879 934 \n", + "8 975 1041 1015 980 1014 985 \n", + "9 1082 987 1009 1056 1007 1057 " + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pd.concat([display_metric('dest', ppl, i) for i in range(0, 3)], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 14441a6281faa8ed684bf731ebf95f834ba6f43f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 2 Apr 2025 16:26:28 +1000 Subject: [PATCH 100/298] corr --- .../2025/correlation_variance_tests.ipynb | 434 +++++++++--------- 1 file changed, 206 insertions(+), 228 deletions(-) diff --git a/notebooks/2025/correlation_variance_tests.ipynb b/notebooks/2025/correlation_variance_tests.ipynb index 3b1ae83dd9..7b7991c692 100644 --- a/notebooks/2025/correlation_variance_tests.ipynb +++ b/notebooks/2025/correlation_variance_tests.ipynb @@ -16,7 +16,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 32, "metadata": { "execution": { "iopub.execute_input": "2022-05-01T05:31:19.564033Z", @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ @@ -83,15 +83,15 @@ "skims['cost']['walk'] = np.zeros_like(skims['time']['walk'])\n", "\n", "\n", - "# scenario: decrease pt travel time to and from zone 0 by 70%\n", + "# scenario: decrease pt travel time to (and within zone) 0 by 70%\n", "scenario_skims = deepcopy(skims)\n", - "scenario_skims['time']['pt'][0,:] *= 0.3\n", - "scenario_skims['time']['pt'] *= 0.3" + "#scenario_skims['time']['pt'][0,:] *= 0.3\n", + "scenario_skims['time']['pt'][:,0] *= 0.3" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ @@ -127,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -136,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -232,7 +232,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 38, "metadata": {}, "outputs": [], "source": [ @@ -242,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 39, "metadata": {}, "outputs": [], "source": [ @@ -267,7 +267,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -286,7 +286,16 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "#pd.concat([display_metric('dest', ppl, i) for i in range(0, 3)], axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, "metadata": {}, "outputs": [ { @@ -329,46 +338,46 @@ " car\n", " 6191\n", " 6215\n", - " 5223\n", - " 5188\n", + " 6048\n", + " 6060\n", " 6170\n", " 6117\n", - " 5172\n", - " 5183\n", + " 6013\n", + " 5962\n", " 6163\n", " 6201\n", - " 5174\n", - " 5246\n", - " \n", - " \n", - " pt\n", - " 1491\n", - " 1473\n", - " 2445\n", - " 2489\n", - " 1469\n", - " 1493\n", - " 2460\n", - " 2429\n", - " 1448\n", - " 1426\n", - " 2425\n", - " 2360\n", + " 6000\n", + " 6065\n", " \n", " \n", " walk\n", " 2318\n", " 2312\n", - " 2332\n", - " 2323\n", + " 2322\n", + " 2324\n", " 2361\n", " 2390\n", - " 2368\n", - " 2388\n", + " 2367\n", + " 2405\n", " 2389\n", " 2373\n", - " 2401\n", - " 2394\n", + " 2395\n", + " 2380\n", + " \n", + " \n", + " pt\n", + " 1491\n", + " 1473\n", + " 1630\n", + " 1616\n", + " 1469\n", + " 1493\n", + " 1620\n", + " 1633\n", + " 1448\n", + " 1426\n", + " 1605\n", + " 1555\n", " \n", " \n", "\n", @@ -376,17 +385,17 @@ ], "text/plain": [ " eet_base_0 mc_base_0 eet_scen_0 mc_scen_0 eet_base_1 mc_base_1 \\\n", - "car 6191 6215 5223 5188 6170 6117 \n", - "pt 1491 1473 2445 2489 1469 1493 \n", - "walk 2318 2312 2332 2323 2361 2390 \n", + "car 6191 6215 6048 6060 6170 6117 \n", + "walk 2318 2312 2322 2324 2361 2390 \n", + "pt 1491 1473 1630 1616 1469 1493 \n", "\n", " eet_scen_1 mc_scen_1 eet_base_2 mc_base_2 eet_scen_2 mc_scen_2 \n", - "car 5172 5183 6163 6201 5174 5246 \n", - "pt 2460 2429 1448 1426 2425 2360 \n", - "walk 2368 2388 2389 2373 2401 2394 " + "car 6013 5962 6163 6201 6000 6065 \n", + "walk 2367 2405 2389 2373 2395 2380 \n", + "pt 1620 1633 1448 1426 1605 1555 " ] }, - "execution_count": 18, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -397,7 +406,32 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 60, + "metadata": {}, + "outputs": [], + "source": [ + "metric = \"mode_choice\"\n", + "\n", + "dfs_ = []\n", + "\n", + "for seed in range(0, 10):\n", + " for seed_2 in range(0, 10):\n", + " df = ppl[f'{metric}_{seed}'].value_counts().to_frame(f'eet_base_{seed}').join(\n", + " ppl[f'{metric}_scen_{seed_2}'].value_counts().to_frame(f'eet_scen_{seed_2}'), how='outer').join(\n", + " ppl[f'{metric}_mc_{seed}'].value_counts().to_frame(f'mc_base_{seed}'), how='outer').join(\n", + " ppl[f'{metric}_mc_scen_{seed_2}'].value_counts().to_frame(f'mc_scen_{seed_2}'), how='outer'\n", + " ).fillna(0).astype(int)\n", + "\n", + " df[f'diff_eet_{seed}_{seed_2}'] = df[f'eet_scen_{seed_2}'] - df[f'eet_base_{seed}']\n", + " df[f'diff_mc_{seed}_{seed_2}'] = df[f'mc_scen_{seed_2}'] - df[f'mc_base_{seed}']\n", + " dfs_.append(df[[f'diff_eet_{seed}_{seed_2}', f'diff_mc_{seed}_{seed_2}']])\n", + "\n", + "diff_mc = pd.concat(dfs_, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 61, "metadata": {}, "outputs": [ { @@ -421,208 +455,140 @@ " \n", " \n", " \n", - " eet_base_0\n", - " mc_base_0\n", - " eet_scen_0\n", - " mc_scen_0\n", - " eet_base_1\n", - " mc_base_1\n", - " eet_scen_1\n", - " mc_scen_1\n", - " eet_base_2\n", - " mc_base_2\n", - " eet_scen_2\n", - " mc_scen_2\n", + " diff_eet_0_0\n", + " diff_mc_0_0\n", + " diff_eet_0_1\n", + " diff_mc_0_1\n", + " diff_eet_0_2\n", + " diff_mc_0_2\n", + " diff_eet_0_3\n", + " diff_mc_0_3\n", + " diff_eet_0_4\n", + " diff_mc_0_4\n", + " ...\n", + " diff_eet_9_5\n", + " diff_mc_9_5\n", + " diff_eet_9_6\n", + " diff_mc_9_6\n", + " diff_eet_9_7\n", + " diff_mc_9_7\n", + " diff_eet_9_8\n", + " diff_mc_9_8\n", + " diff_eet_9_9\n", + " diff_mc_9_9\n", " \n", " \n", " \n", " \n", - " 0\n", - " 1170\n", - " 1104\n", - " 1159\n", - " 1089\n", - " 1105\n", - " 1131\n", - " 1085\n", - " 1117\n", - " 1138\n", - " 1104\n", - " 1123\n", - " 1097\n", - " \n", - " \n", - " 1\n", - " 1061\n", - " 1038\n", - " 1065\n", - " 1044\n", - " 1089\n", - " 1079\n", - " 1098\n", - " 1075\n", - " 1056\n", - " 1037\n", - " 1062\n", - " 1035\n", - " \n", - " \n", - " 2\n", - " 791\n", - " 857\n", - " 801\n", - " 865\n", - " 864\n", - " 906\n", - " 869\n", - " 915\n", - " 882\n", - " 807\n", - " 891\n", - " 814\n", - " \n", - " \n", - " 3\n", - " 1034\n", - " 1104\n", - " 1034\n", - " 1097\n", - " 1078\n", - " 1029\n", - " 1070\n", - " 1031\n", - " 1046\n", - " 1068\n", - " 1041\n", - " 1062\n", - " \n", - " \n", - " 4\n", - " 951\n", - " 873\n", - " 955\n", - " 881\n", - " 919\n", - " 896\n", - " 930\n", - " 908\n", - " 923\n", - " 920\n", - " 934\n", - " 924\n", - " \n", - " \n", - " 5\n", - " 1032\n", - " 1037\n", - " 1023\n", - " 1032\n", - " 1021\n", - " 1053\n", - " 1020\n", - " 1042\n", - " 1007\n", - " 1046\n", - " 1004\n", - " 1046\n", - " \n", - " \n", - " 6\n", - " 1031\n", - " 1028\n", - " 1029\n", - " 1023\n", - " 971\n", - " 989\n", - " 972\n", - " 989\n", - " 1048\n", - " 1050\n", - " 1045\n", - " 1046\n", - " \n", - " \n", - " 7\n", - " 871\n", - " 856\n", - " 868\n", - " 862\n", - " 902\n", - " 897\n", - " 899\n", - " 895\n", - " 876\n", - " 932\n", - " 879\n", - " 934\n", + " car\n", + " -143\n", + " -155\n", + " -178\n", + " -253\n", + " -191\n", + " -150\n", + " -164\n", + " -264\n", + " -181\n", + " -179\n", + " ...\n", + " -230\n", + " -112\n", + " -30\n", + " -174\n", + " -160\n", + " -153\n", + " -156\n", + " -205\n", + " -166\n", + " -136\n", " \n", " \n", - " 8\n", - " 1027\n", - " 1026\n", - " 1031\n", - " 1033\n", - " 970\n", - " 1035\n", - " 975\n", - " 1041\n", - " 1015\n", - " 980\n", - " 1014\n", - " 985\n", + " walk\n", + " 4\n", + " 12\n", + " 49\n", + " 93\n", + " 77\n", + " 68\n", + " 21\n", + " 50\n", + " 55\n", + " 106\n", + " ...\n", + " 64\n", + " -85\n", + " -32\n", + " -44\n", + " -5\n", + " -29\n", + " -18\n", + " 18\n", + " 13\n", + " -16\n", " \n", " \n", - " 9\n", - " 1032\n", - " 1077\n", - " 1035\n", - " 1074\n", - " 1081\n", - " 985\n", - " 1082\n", - " 987\n", - " 1009\n", - " 1056\n", - " 1007\n", - " 1057\n", + " pt\n", + " 139\n", + " 143\n", + " 129\n", + " 160\n", + " 114\n", + " 82\n", + " 143\n", + " 214\n", + " 126\n", + " 73\n", + " ...\n", + " 166\n", + " 197\n", + " 62\n", + " 218\n", + " 165\n", + " 182\n", + " 174\n", + " 187\n", + " 153\n", + " 152\n", " \n", " \n", "\n", + "

3 rows × 200 columns

\n", "" ], "text/plain": [ - " eet_base_0 mc_base_0 eet_scen_0 mc_scen_0 eet_base_1 mc_base_1 \\\n", - "0 1170 1104 1159 1089 1105 1131 \n", - "1 1061 1038 1065 1044 1089 1079 \n", - "2 791 857 801 865 864 906 \n", - "3 1034 1104 1034 1097 1078 1029 \n", - "4 951 873 955 881 919 896 \n", - "5 1032 1037 1023 1032 1021 1053 \n", - "6 1031 1028 1029 1023 971 989 \n", - "7 871 856 868 862 902 897 \n", - "8 1027 1026 1031 1033 970 1035 \n", - "9 1032 1077 1035 1074 1081 985 \n", + " diff_eet_0_0 diff_mc_0_0 diff_eet_0_1 diff_mc_0_1 diff_eet_0_2 \\\n", + "car -143 -155 -178 -253 -191 \n", + "walk 4 12 49 93 77 \n", + "pt 139 143 129 160 114 \n", "\n", - " eet_scen_1 mc_scen_1 eet_base_2 mc_base_2 eet_scen_2 mc_scen_2 \n", - "0 1085 1117 1138 1104 1123 1097 \n", - "1 1098 1075 1056 1037 1062 1035 \n", - "2 869 915 882 807 891 814 \n", - "3 1070 1031 1046 1068 1041 1062 \n", - "4 930 908 923 920 934 924 \n", - "5 1020 1042 1007 1046 1004 1046 \n", - "6 972 989 1048 1050 1045 1046 \n", - "7 899 895 876 932 879 934 \n", - "8 975 1041 1015 980 1014 985 \n", - "9 1082 987 1009 1056 1007 1057 " + " diff_mc_0_2 diff_eet_0_3 diff_mc_0_3 diff_eet_0_4 diff_mc_0_4 ... \\\n", + "car -150 -164 -264 -181 -179 ... \n", + "walk 68 21 50 55 106 ... \n", + "pt 82 143 214 126 73 ... \n", + "\n", + " diff_eet_9_5 diff_mc_9_5 diff_eet_9_6 diff_mc_9_6 diff_eet_9_7 \\\n", + "car -230 -112 -30 -174 -160 \n", + "walk 64 -85 -32 -44 -5 \n", + "pt 166 197 62 218 165 \n", + "\n", + " diff_mc_9_7 diff_eet_9_8 diff_mc_9_8 diff_eet_9_9 diff_mc_9_9 \n", + "car -153 -156 -205 -166 -136 \n", + "walk -29 -18 18 13 -16 \n", + "pt 182 174 187 153 152 \n", + "\n", + "[3 rows x 200 columns]" ] }, - "execution_count": 16, + "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "pd.concat([display_metric('dest', ppl, i) for i in range(0, 3)], axis=1)" + "# check eet \n", + "\n", + "diff_mc" ] }, { @@ -653,6 +619,18 @@ "outputs": [], "source": [] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# increase in pt trips to/from zone 0\n", + "\n", + "\n", + "# increase/decrease in other areas -> and why. NEST SWITCHING? should not switch away from 0, right?" + ] + }, { "cell_type": "code", "execution_count": null, From 73b8c32471485f2c2d7d88fd03046cef309ad993 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 2 Apr 2025 20:14:44 +1000 Subject: [PATCH 101/298] sign on logsum --- .../2025/correlation_variance_tests.ipynb | 289 +++++++++--------- 1 file changed, 147 insertions(+), 142 deletions(-) diff --git a/notebooks/2025/correlation_variance_tests.ipynb b/notebooks/2025/correlation_variance_tests.ipynb index 7b7991c692..33d4a844f3 100644 --- a/notebooks/2025/correlation_variance_tests.ipynb +++ b/notebooks/2025/correlation_variance_tests.ipynb @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 96, "metadata": {}, "outputs": [], "source": [ @@ -91,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 97, "metadata": {}, "outputs": [], "source": [ @@ -136,11 +136,11 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 98, "metadata": {}, "outputs": [], "source": [ - "beta_logsum = -0.1\n", + "beta_logsum = 0.5 # applied to expected maximum utility, so positive sign\n", "\n", "dest_altneratives = destination.zone.values\n", "num_dests = dest_altneratives.shape[0]\n", @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 99, "metadata": {}, "outputs": [], "source": [ @@ -232,7 +232,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 100, "metadata": {}, "outputs": [], "source": [ @@ -242,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 101, "metadata": {}, "outputs": [], "source": [ @@ -267,7 +267,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 102, "metadata": {}, "outputs": [], "source": [ @@ -286,7 +286,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 103, "metadata": {}, "outputs": [], "source": [ @@ -295,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 104, "metadata": {}, "outputs": [ { @@ -335,49 +335,49 @@ " \n", " \n", " \n", - " car\n", - " 6191\n", - " 6215\n", - " 6048\n", - " 6060\n", - " 6170\n", - " 6117\n", - " 6013\n", - " 5962\n", - " 6163\n", - " 6201\n", - " 6000\n", - " 6065\n", + " walk\n", + " 8817\n", + " 8843\n", + " 8806\n", + " 8831\n", + " 8750\n", + " 8794\n", + " 8733\n", + " 8785\n", + " 8824\n", + " 8846\n", + " 8815\n", + " 8839\n", " \n", " \n", - " walk\n", - " 2318\n", - " 2312\n", - " 2322\n", - " 2324\n", - " 2361\n", - " 2390\n", - " 2367\n", - " 2405\n", - " 2389\n", - " 2373\n", - " 2395\n", - " 2380\n", + " car\n", + " 1027\n", + " 1016\n", + " 1027\n", + " 1017\n", + " 1072\n", + " 1033\n", + " 1072\n", + " 1021\n", + " 1000\n", + " 995\n", + " 988\n", + " 984\n", " \n", " \n", " pt\n", - " 1491\n", - " 1473\n", - " 1630\n", - " 1616\n", - " 1469\n", - " 1493\n", - " 1620\n", - " 1633\n", - " 1448\n", - " 1426\n", - " 1605\n", - " 1555\n", + " 156\n", + " 141\n", + " 167\n", + " 152\n", + " 178\n", + " 173\n", + " 195\n", + " 194\n", + " 176\n", + " 159\n", + " 197\n", + " 177\n", " \n", " \n", "\n", @@ -385,17 +385,17 @@ ], "text/plain": [ " eet_base_0 mc_base_0 eet_scen_0 mc_scen_0 eet_base_1 mc_base_1 \\\n", - "car 6191 6215 6048 6060 6170 6117 \n", - "walk 2318 2312 2322 2324 2361 2390 \n", - "pt 1491 1473 1630 1616 1469 1493 \n", + "walk 8817 8843 8806 8831 8750 8794 \n", + "car 1027 1016 1027 1017 1072 1033 \n", + "pt 156 141 167 152 178 173 \n", "\n", " eet_scen_1 mc_scen_1 eet_base_2 mc_base_2 eet_scen_2 mc_scen_2 \n", - "car 6013 5962 6163 6201 6000 6065 \n", - "walk 2367 2405 2389 2373 2395 2380 \n", - "pt 1620 1633 1448 1426 1605 1555 " + "walk 8733 8785 8824 8846 8815 8839 \n", + "car 1072 1021 1000 995 988 984 \n", + "pt 195 194 176 159 197 177 " ] }, - "execution_count": 42, + "execution_count": 104, "metadata": {}, "output_type": "execute_result" } @@ -406,7 +406,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 105, "metadata": {}, "outputs": [], "source": [ @@ -431,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 106, "metadata": {}, "outputs": [ { @@ -480,76 +480,76 @@ " \n", " \n", " \n", - " car\n", - " -143\n", - " -155\n", - " -178\n", - " -253\n", - " -191\n", - " -150\n", - " -164\n", - " -264\n", - " -181\n", - " -179\n", + " walk\n", + " -11\n", + " -12\n", + " -84\n", + " -58\n", + " -2\n", + " -4\n", + " -57\n", + " -66\n", + " 15\n", + " -66\n", " ...\n", - " -230\n", - " -112\n", - " -30\n", - " -174\n", - " -160\n", - " -153\n", - " -156\n", - " -205\n", - " -166\n", - " -136\n", + " -7\n", + " -60\n", + " 7\n", + " -21\n", + " 10\n", + " -22\n", + " 81\n", + " -45\n", + " -6\n", + " -14\n", " \n", " \n", - " walk\n", - " 4\n", - " 12\n", - " 49\n", - " 93\n", - " 77\n", - " 68\n", - " 21\n", - " 50\n", - " 55\n", - " 106\n", - " ...\n", - " 64\n", - " -85\n", + " car\n", + " 0\n", + " 1\n", + " 45\n", + " 5\n", + " -39\n", " -32\n", + " 13\n", + " 9\n", " -44\n", - " -5\n", - " -29\n", + " 28\n", + " ...\n", " -18\n", - " 18\n", - " 13\n", - " -16\n", + " 32\n", + " -8\n", + " -10\n", + " -47\n", + " -5\n", + " -107\n", + " 12\n", + " -14\n", + " 1\n", " \n", " \n", " pt\n", - " 139\n", - " 143\n", - " 129\n", - " 160\n", - " 114\n", - " 82\n", - " 143\n", - " 214\n", - " 126\n", - " 73\n", + " 11\n", + " 11\n", + " 39\n", + " 53\n", + " 41\n", + " 36\n", + " 44\n", + " 57\n", + " 29\n", + " 38\n", " ...\n", - " 166\n", - " 197\n", - " 62\n", - " 218\n", - " 165\n", - " 182\n", - " 174\n", - " 187\n", - " 153\n", - " 152\n", + " 25\n", + " 28\n", + " 1\n", + " 31\n", + " 37\n", + " 27\n", + " 26\n", + " 33\n", + " 20\n", + " 13\n", " \n", " \n", "\n", @@ -558,52 +558,57 @@ ], "text/plain": [ " diff_eet_0_0 diff_mc_0_0 diff_eet_0_1 diff_mc_0_1 diff_eet_0_2 \\\n", - "car -143 -155 -178 -253 -191 \n", - "walk 4 12 49 93 77 \n", - "pt 139 143 129 160 114 \n", + "walk -11 -12 -84 -58 -2 \n", + "car 0 1 45 5 -39 \n", + "pt 11 11 39 53 41 \n", "\n", " diff_mc_0_2 diff_eet_0_3 diff_mc_0_3 diff_eet_0_4 diff_mc_0_4 ... \\\n", - "car -150 -164 -264 -181 -179 ... \n", - "walk 68 21 50 55 106 ... \n", - "pt 82 143 214 126 73 ... \n", + "walk -4 -57 -66 15 -66 ... \n", + "car -32 13 9 -44 28 ... \n", + "pt 36 44 57 29 38 ... \n", "\n", " diff_eet_9_5 diff_mc_9_5 diff_eet_9_6 diff_mc_9_6 diff_eet_9_7 \\\n", - "car -230 -112 -30 -174 -160 \n", - "walk 64 -85 -32 -44 -5 \n", - "pt 166 197 62 218 165 \n", + "walk -7 -60 7 -21 10 \n", + "car -18 32 -8 -10 -47 \n", + "pt 25 28 1 31 37 \n", "\n", " diff_mc_9_7 diff_eet_9_8 diff_mc_9_8 diff_eet_9_9 diff_mc_9_9 \n", - "car -153 -156 -205 -166 -136 \n", - "walk -29 -18 18 13 -16 \n", - "pt 182 174 187 153 152 \n", + "walk -22 81 -45 -6 -14 \n", + "car -5 -107 12 -14 1 \n", + "pt 27 26 33 20 13 \n", "\n", "[3 rows x 200 columns]" ] }, - "execution_count": 61, + "execution_count": 106, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# check eet \n", - "\n", + "# check eet\n", "diff_mc" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 111, "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "(6, 0)" + ] + }, + "execution_count": 111, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ppl.loc[(ppl.mode_choice_mc_0 != \"walk\") & (ppl.mode_choice_mc_scen_0 == \"walk\")].shape[0], ppl.loc[(ppl.mode_choice_0 != \"walk\") & (ppl.mode_choice_scen_0 == \"walk\")].shape[0]" + ] }, { "cell_type": "code", From f5f365fdd8d24be137c255632794c9180bb0797d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 2 Apr 2025 20:23:57 +1000 Subject: [PATCH 102/298] removes notebook folder --- .../2025/correlation_variance_tests.ipynb | 689 ------------------ 1 file changed, 689 deletions(-) delete mode 100644 notebooks/2025/correlation_variance_tests.ipynb diff --git a/notebooks/2025/correlation_variance_tests.ipynb b/notebooks/2025/correlation_variance_tests.ipynb deleted file mode 100644 index 33d4a844f3..0000000000 --- a/notebooks/2025/correlation_variance_tests.ipynb +++ /dev/null @@ -1,689 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Which correlations do we want\n", - "\n", - "simulate destination mode choice model:\n", - " 1) MC\n", - " 2) EET fixed error per level\n", - " 3) EET only fixed when identical\n", - "\n", - "Does it make a difference in analysis?" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "execution": { - "iopub.execute_input": "2022-05-01T05:31:19.564033Z", - "iopub.status.busy": "2022-05-01T05:31:19.563663Z", - "iopub.status.idle": "2022-05-01T05:31:19.824207Z", - "shell.execute_reply": "2022-05-01T05:31:19.823432Z", - "shell.execute_reply.started": "2022-05-01T05:31:19.563960Z" - } - }, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "\n", - "from numpy.random import default_rng\n", - "from copy import deepcopy" - ] - }, - { - "cell_type": "code", - "execution_count": 96, - "metadata": {}, - "outputs": [], - "source": [ - "rng = default_rng(999)\n", - "\n", - "### Zones\n", - "num_zones = 10\n", - "destination = pd.DataFrame(\n", - " data=zip(np.arange(0, num_zones), rng.normal(1000, 10, size=num_zones), strict=True),\n", - " columns=['zone', 'attrs'],\n", - ")\n", - "\n", - "### People - assume each person makes one trip from random origin\n", - "num_ppl = 10000\n", - "vars=['id', 'income', 'orig']\n", - "ppl = pd.DataFrame(\n", - " data=zip(\n", - " np.arange(0, num_ppl),\n", - " rng.normal(50, 20, size=(num_ppl)).clip(2, 150),\n", - " rng.choice(np.arange(0, num_zones), num_ppl),\n", - " strict=True,\n", - " ),\n", - " columns=vars,\n", - ")\n", - "\n", - "\n", - "### Modes\n", - "num_modes = 3\n", - "mode_idx_to_str = {0: 'car', 1: 'pt', 2: 'walk'}\n", - "modes = np.array(['car', 'pt', 'walk'])\n", - "mode_asc = {'car': 2.5, 'pt': 0.0, 'walk': 10.0}\n", - "\n", - "### Cost and time by mode\n", - "skims = {}\n", - "skims['time'] = {}\n", - "skims['time']['car'] = rng.normal(20, 20, size=(num_zones, num_zones)).clip(4, 60)\n", - "skims['time']['pt'] = rng.normal(40, 10, size=(num_zones, num_zones)).clip(4, 80)\n", - "skims['time']['walk'] = 3.0 * skims['time']['car'].copy()\n", - "\n", - "skims['cost'] = {}\n", - "skims['cost']['car'] = skims['time']['car'].copy() / 4.0\n", - "skims['cost']['pt'] = 2.0 * np.ones_like(skims['time']['pt'])\n", - "skims['cost']['walk'] = np.zeros_like(skims['time']['walk'])\n", - "\n", - "\n", - "# scenario: decrease pt travel time to (and within zone) 0 by 70%\n", - "scenario_skims = deepcopy(skims)\n", - "#scenario_skims['time']['pt'][0,:] *= 0.3\n", - "scenario_skims['time']['pt'][:,0] *= 0.3" - ] - }, - { - "cell_type": "code", - "execution_count": 97, - "metadata": {}, - "outputs": [], - "source": [ - "def logsum(utilities, nest_scale=1.0):\n", - " scaled_utils = utilities / nest_scale\n", - " max_util = np.max(scaled_utils, axis=1)\n", - " return max_util + np.log(np.sum(np.exp(scaled_utils - max_util.reshape((-1,1))), axis=1))\n", - "\n", - "def inverse_ev1_cdf(x, location=0.0, scale=1.0):\n", - " return location - scale * np.log(-np.log(x))\n", - "\n", - "### Utility function parameters\n", - "beta_time = {}\n", - "beta_time['car'] = -0.06\n", - "beta_time['pt'] = -0.03\n", - "beta_time['walk'] = -0.2\n", - "beta_cost = -0.01\n", - "\n", - "def mode_utility(mode, income, o, d, skims):\n", - " return (\n", - " beta_time[mode] * skims['time'][mode][o,d]\n", - " + beta_cost * skims['cost'][mode][o,d] / income\n", - " + mode_asc[mode]\n", - " )\n", - "\n", - "def mode_logsum(o: int, d: np.array, income: float, skims):\n", - " mode_utils = np.array([mode_utility(m_, income, o, d, skims) for m_ in modes]).T\n", - "\n", - " return logsum(mode_utils, 1.0)\n", - "\n", - "# mode_logsum(0, np.array([0, 1, 2]), 100)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [], - "source": [ - "#inverse_ev1_cdf(default_rng(seed=np.random.SeedSequence([777, 333])).random(100))" - ] - }, - { - "cell_type": "code", - "execution_count": 98, - "metadata": {}, - "outputs": [], - "source": [ - "beta_logsum = 0.5 # applied to expected maximum utility, so positive sign\n", - "\n", - "dest_altneratives = destination.zone.values\n", - "num_dests = dest_altneratives.shape[0]\n", - "dest_attrs = np.log(destination['attrs'].values)\n", - "ppl['dest_utils'] = ppl.apply(\n", - " lambda x: (\n", - " beta_logsum * mode_logsum(int(x['orig']), dest_altneratives, x['income'], skims)\n", - " + dest_attrs\n", - " ),\n", - " axis=1,\n", - ")\n", - "ppl['dest_utils_scen'] = ppl.apply(\n", - " lambda x: (\n", - " beta_logsum * mode_logsum(int(x['orig']), dest_altneratives, x['income'], scenario_skims)\n", - " + dest_attrs\n", - " ),\n", - " axis=1,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 99, - "metadata": {}, - "outputs": [], - "source": [ - "### EET\n", - "def destination_choice_model(ppl, seed=777, util_column_name=\"dest_utils\"):\n", - " dest_choice = ppl.apply(\n", - " lambda x: np.argmax(\n", - " x[util_column_name] + inverse_ev1_cdf(\n", - " default_rng(seed=np.random.SeedSequence([seed, int(x['id']), 100])).random(size=num_dests)\n", - " )\n", - " )\n", - " , axis=1\n", - " )\n", - " return dest_choice\n", - "\n", - "def mode_choice_model(ppl, skims, dest_col_name='dest', seed=777, od_seed_contrib=False):\n", - " \"\"\"\"od_seed_contrib: if True, seed depedns on destination\"\"\"\n", - " def seed_o_d(d):\n", - " if od_seed_contrib:\n", - " return d\n", - " return 0\n", - "\n", - " mode_choice = ppl.apply(\n", - " lambda x: np.argmax(\n", - " [mode_utility(m_, x['income'], int(x['orig']), int(x[dest_col_name]), skims) for m_ in modes]\n", - " + inverse_ev1_cdf(default_rng(seed=np.random.SeedSequence(\n", - " [seed, int(x['id']), 200, seed_o_d(int(x[dest_col_name]))]\n", - " )).random(size=num_modes))\n", - " ),\n", - " axis=1,\n", - " )\n", - " #mode_choice = mode_utils.apply(np.argmax)\n", - " return mode_choice.map(mode_idx_to_str)\n", - "\n", - "### MC\n", - "def destination_choice_model_mc(ppl, seed=777, util_column_name=\"dest_utils\"):\n", - " dest_choice = ppl.apply(\n", - " lambda x: default_rng(\n", - " seed=np.random.SeedSequence([seed, int(x['id']), 100])\n", - " ).choice(dest_altneratives, p=np.exp(x[util_column_name]) / np.sum(np.exp(x[util_column_name])), size=1)[0],\n", - " axis=1,\n", - " )\n", - " return dest_choice\n", - "\n", - "def mode_choice_model_mc(ppl, skims, dest_col_name='dest', seed=777, od_seed_contrib=False):\n", - " def seed_o_d(d):\n", - " if od_seed_contrib:\n", - " return d\n", - " return 0\n", - "\n", - " ppl['mode_exp_utils'] = ppl.apply(\n", - " lambda x: np.exp(\n", - " [mode_utility(m_, x['income'], int(x['orig']), int(x[dest_col_name]), skims) for m_ in modes]\n", - " ),\n", - " axis=1,\n", - " )\n", - " mode_choice = ppl.apply(\n", - " lambda x: default_rng(\n", - " seed=np.random.SeedSequence([seed, int(x['id']), 200, seed_o_d(int(x[dest_col_name]))])\n", - " ).choice(modes, p=x.mode_exp_utils / np.sum(x.mode_exp_utils), size=1)[0],\n", - " axis=1,\n", - " )\n", - " ppl.drop(columns=['mode_exp_utils'], inplace=True)\n", - " return mode_choice" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "metadata": {}, - "outputs": [], - "source": [ - "#for i in range(0, 10):\n", - "# ppl.dest_utils.apply(lambda x: (np.exp(x) / np.sum(np.exp(x)))[i]).hist(figsize=(3,2))" - ] - }, - { - "cell_type": "code", - "execution_count": 101, - "metadata": {}, - "outputs": [], - "source": [ - "# 10k ppl: per seed about 3.5s total [w/o sedd, with 12.4]\n", - "for s_ in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:\n", - " ppl[f'dest_{s_}'] = destination_choice_model(ppl, util_column_name='dest_utils', seed=s_)\n", - " ppl[f'dest_scen_{s_}'] = destination_choice_model(ppl, util_column_name='dest_utils_scen', seed=s_)\n", - " ppl[f'dest_mc_{s_}'] = destination_choice_model_mc(ppl, util_column_name='dest_utils', seed=s_)\n", - " ppl[f'dest_mc_scen_{s_}'] = destination_choice_model_mc(ppl, util_column_name='dest_utils_scen', seed=s_)\n", - " \n", - " \n", - " ppl[f'mode_choice_{s_}'] = mode_choice_model(ppl, skims, dest_col_name=f'dest_{s_}', seed=s_)\n", - " ppl[f'mode_choice_scen_{s_}'] = mode_choice_model(ppl, scenario_skims, dest_col_name=f'dest_scen_{s_}', seed=s_)\n", - " #ppl[f'mode_choice_seedd_{s_}'] = mode_choice_model(ppl, skims, dest_col_name=f'dest_{s_}', seed=s_, od_seed_contrib=True)\n", - " #ppl[f'mode_choice_seedd_scen_{s_}'] = mode_choice_model(ppl, scenario_skims, dest_col_name=f'dest_scen_{s_}', seed=s_, od_seed_contrib=True)\n", - "\n", - " ppl[f'mode_choice_mc_{s_}'] = mode_choice_model_mc(ppl, skims, dest_col_name=f'dest_mc_{s_}', seed=s_)\n", - " ppl[f'mode_choice_mc_scen_{s_}'] = mode_choice_model_mc(ppl, scenario_skims, dest_col_name=f'dest_mc_scen_{s_}', seed=s_)\n", - " #ppl[f'mode_choice_mc_seedd_{s_}'] = mode_choice_model_mc(ppl, skims, dest_col_name=f'dest_mc_{s_}', seed=s_, od_seed_contrib=True)\n", - " #ppl[f'mode_choice_mc_seedd_scen_{s_}'] = mode_choice_model_mc(ppl, scenario_skims, dest_col_name=f'dest_mc_scen_{s_}', seed=s_, od_seed_contrib=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 102, - "metadata": {}, - "outputs": [], - "source": [ - "def display_metric(metric, ppl, seed):\n", - " return ( \n", - " ppl[f'{metric}_{seed}'].value_counts().to_frame(f'eet_base_{seed}').join(\n", - " ppl[f'{metric}_mc_{seed}'].value_counts().to_frame(f'mc_base_{seed}'), how='outer').join(\n", - " ppl[f'{metric}_scen_{seed}'].value_counts().to_frame(f'eet_scen_{seed}'), how='outer').join(\n", - " ppl[f'{metric}_mc_scen_{seed}'].value_counts().to_frame(f'mc_scen_{seed}'), how='outer').fillna(0).astype(int)\n", - " )\n", - " # ppl[f'mode_choice_seedd_{seed}'].value_counts().to_frame(f'eet_base_seedd_{seed}'), how='outer').join(\n", - " # ppl[f'mode_choice_mc_seedd_{seed}'].value_counts().to_frame(f'mc_base_seedd_{seed}'), how='outer').join(\n", - " # ppl[f'mode_choice_seedd_scen_{seed}'].value_counts().to_frame(f'eet_scen_seedd_{seed}'), how='outer').join(\n", - " # ppl[f'mode_choice_mc_seedd_scen_{seed}'].value_counts().to_frame(f'mc_scen_seedd_{seed}'), how='outer')\\" - ] - }, - { - "cell_type": "code", - "execution_count": 103, - "metadata": {}, - "outputs": [], - "source": [ - "#pd.concat([display_metric('dest', ppl, i) for i in range(0, 3)], axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 104, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
eet_base_0mc_base_0eet_scen_0mc_scen_0eet_base_1mc_base_1eet_scen_1mc_scen_1eet_base_2mc_base_2eet_scen_2mc_scen_2
walk881788438806883187508794873387858824884688158839
car102710161027101710721033107210211000995988984
pt156141167152178173195194176159197177
\n", - "
" - ], - "text/plain": [ - " eet_base_0 mc_base_0 eet_scen_0 mc_scen_0 eet_base_1 mc_base_1 \\\n", - "walk 8817 8843 8806 8831 8750 8794 \n", - "car 1027 1016 1027 1017 1072 1033 \n", - "pt 156 141 167 152 178 173 \n", - "\n", - " eet_scen_1 mc_scen_1 eet_base_2 mc_base_2 eet_scen_2 mc_scen_2 \n", - "walk 8733 8785 8824 8846 8815 8839 \n", - "car 1072 1021 1000 995 988 984 \n", - "pt 195 194 176 159 197 177 " - ] - }, - "execution_count": 104, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pd.concat([display_metric('mode_choice', ppl, i) for i in range(0, 3)], axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 105, - "metadata": {}, - "outputs": [], - "source": [ - "metric = \"mode_choice\"\n", - "\n", - "dfs_ = []\n", - "\n", - "for seed in range(0, 10):\n", - " for seed_2 in range(0, 10):\n", - " df = ppl[f'{metric}_{seed}'].value_counts().to_frame(f'eet_base_{seed}').join(\n", - " ppl[f'{metric}_scen_{seed_2}'].value_counts().to_frame(f'eet_scen_{seed_2}'), how='outer').join(\n", - " ppl[f'{metric}_mc_{seed}'].value_counts().to_frame(f'mc_base_{seed}'), how='outer').join(\n", - " ppl[f'{metric}_mc_scen_{seed_2}'].value_counts().to_frame(f'mc_scen_{seed_2}'), how='outer'\n", - " ).fillna(0).astype(int)\n", - "\n", - " df[f'diff_eet_{seed}_{seed_2}'] = df[f'eet_scen_{seed_2}'] - df[f'eet_base_{seed}']\n", - " df[f'diff_mc_{seed}_{seed_2}'] = df[f'mc_scen_{seed_2}'] - df[f'mc_base_{seed}']\n", - " dfs_.append(df[[f'diff_eet_{seed}_{seed_2}', f'diff_mc_{seed}_{seed_2}']])\n", - "\n", - "diff_mc = pd.concat(dfs_, axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 106, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
diff_eet_0_0diff_mc_0_0diff_eet_0_1diff_mc_0_1diff_eet_0_2diff_mc_0_2diff_eet_0_3diff_mc_0_3diff_eet_0_4diff_mc_0_4...diff_eet_9_5diff_mc_9_5diff_eet_9_6diff_mc_9_6diff_eet_9_7diff_mc_9_7diff_eet_9_8diff_mc_9_8diff_eet_9_9diff_mc_9_9
walk-11-12-84-58-2-4-57-6615-66...-7-607-2110-2281-45-6-14
car01455-39-32139-4428...-1832-8-10-47-5-10712-141
pt11113953413644572938...2528131372726332013
\n", - "

3 rows × 200 columns

\n", - "
" - ], - "text/plain": [ - " diff_eet_0_0 diff_mc_0_0 diff_eet_0_1 diff_mc_0_1 diff_eet_0_2 \\\n", - "walk -11 -12 -84 -58 -2 \n", - "car 0 1 45 5 -39 \n", - "pt 11 11 39 53 41 \n", - "\n", - " diff_mc_0_2 diff_eet_0_3 diff_mc_0_3 diff_eet_0_4 diff_mc_0_4 ... \\\n", - "walk -4 -57 -66 15 -66 ... \n", - "car -32 13 9 -44 28 ... \n", - "pt 36 44 57 29 38 ... \n", - "\n", - " diff_eet_9_5 diff_mc_9_5 diff_eet_9_6 diff_mc_9_6 diff_eet_9_7 \\\n", - "walk -7 -60 7 -21 10 \n", - "car -18 32 -8 -10 -47 \n", - "pt 25 28 1 31 37 \n", - "\n", - " diff_mc_9_7 diff_eet_9_8 diff_mc_9_8 diff_eet_9_9 diff_mc_9_9 \n", - "walk -22 81 -45 -6 -14 \n", - "car -5 -107 12 -14 1 \n", - "pt 27 26 33 20 13 \n", - "\n", - "[3 rows x 200 columns]" - ] - }, - "execution_count": 106, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# check eet\n", - "diff_mc" - ] - }, - { - "cell_type": "code", - "execution_count": 111, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(6, 0)" - ] - }, - "execution_count": 111, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ppl.loc[(ppl.mode_choice_mc_0 != \"walk\") & (ppl.mode_choice_mc_scen_0 == \"walk\")].shape[0], ppl.loc[(ppl.mode_choice_0 != \"walk\") & (ppl.mode_choice_scen_0 == \"walk\")].shape[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# increase in pt trips to/from zone 0\n", - "\n", - "\n", - "# increase/decrease in other areas -> and why. NEST SWITCHING? should not switch away from 0, right?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From 0f54cec22ee73887efd07e2a7c6f17ba5eb64732 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 6 Apr 2025 20:07:50 +1000 Subject: [PATCH 103/298] compute_setting overrides for eet, currently only for interaction_sample to test loc sampling with MC --- activitysim/core/configuration/base.py | 5 +++++ activitysim/core/interaction_sample.py | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/activitysim/core/configuration/base.py b/activitysim/core/configuration/base.py index ec27fee994..374ef8e71b 100644 --- a/activitysim/core/configuration/base.py +++ b/activitysim/core/configuration/base.py @@ -135,6 +135,10 @@ class ComputeSettings(PydanticBase): Sharrow settings for a component. """ + # Make this more general compute settings and use for explicit error term overrides + # TODO: Default None work for sub-components + use_explicit_error_terms: None | bool | dict[str, bool] = None + sharrow_skip: bool | dict[str, bool] = False """Skip sharrow when evaluating this component. @@ -244,6 +248,7 @@ def subcomponent_settings(self, subcomponent: str) -> ComputeSettings: use_numba=self.use_numba, drop_unused_columns=self.drop_unused_columns, protect_columns=self.protect_columns, + use_explicit_error_terms=self.use_explicit_error_terms, ) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 8c3645da9f..5f8f34b98f 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -524,8 +524,14 @@ def _interaction_sample( state.tracing.dump_df(DUMP, utilities, trace_label, "utilities") + if compute_settings and (compute_settings.use_explicit_error_terms is not None): + use_eet = compute_settings.use_explicit_error_terms + logger.info(f"Interaction sample model-specific EET overrides for {trace_label}: eet = {use_eet}") + else: + use_eet = state.settings.use_explicit_error_terms + # sample_size == 0 is for estimation mode, see below - if state.settings.use_explicit_error_terms and (sample_size != 0): + if (sample_size != 0) and use_eet: choices_df = make_sample_choices_utility_based( state, choosers, From 11d86a1f53488587c4d7ce9a4fb37ac871fa4b3c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 7 Apr 2025 02:34:05 +1000 Subject: [PATCH 104/298] Adds subcomponent-specific eet setting --- activitysim/core/configuration/base.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/activitysim/core/configuration/base.py b/activitysim/core/configuration/base.py index 374ef8e71b..b11643cdc3 100644 --- a/activitysim/core/configuration/base.py +++ b/activitysim/core/configuration/base.py @@ -136,7 +136,7 @@ class ComputeSettings(PydanticBase): """ # Make this more general compute settings and use for explicit error term overrides - # TODO: Default None work for sub-components + # Default None work for sub-components defined in getter below (eet_subcomponent) use_explicit_error_terms: None | bool | dict[str, bool] = None sharrow_skip: bool | dict[str, bool] = False @@ -222,6 +222,13 @@ def should_skip(self, subcomponent: str) -> bool: else: return bool(self.sharrow_skip) + def eet_subcomponent(self, subcomponent: str) -> bool: + """Check for EET overrides for a particular subcomponent.""" + if isinstance(self.use_explicit_error_terms, dict): + return self.use_explicit_error_terms.get(subcomponent, None) + else: + return bool(self.use_explicit_error_terms) + @contextmanager def pandas_option_context(self): """Context manager to set pandas options for compute settings.""" @@ -248,7 +255,7 @@ def subcomponent_settings(self, subcomponent: str) -> ComputeSettings: use_numba=self.use_numba, drop_unused_columns=self.drop_unused_columns, protect_columns=self.protect_columns, - use_explicit_error_terms=self.use_explicit_error_terms, + use_explicit_error_terms=self.eet_subcomponent(subcomponent), ) From 30ddb3e1970d6648d6b9f05e44a11ace2f02a14b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 7 Apr 2025 02:44:54 +1000 Subject: [PATCH 105/298] default compute settings set before, no need to check if none --- activitysim/core/interaction_sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 5f8f34b98f..72be64787a 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -524,7 +524,7 @@ def _interaction_sample( state.tracing.dump_df(DUMP, utilities, trace_label, "utilities") - if compute_settings and (compute_settings.use_explicit_error_terms is not None): + if compute_settings.use_explicit_error_terms is not None: use_eet = compute_settings.use_explicit_error_terms logger.info(f"Interaction sample model-specific EET overrides for {trace_label}: eet = {use_eet}") else: From c6d60c6cbf577fc80f0ac6cc73a62d3fb9ff1073 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 12 Apr 2025 05:23:30 +1000 Subject: [PATCH 106/298] comment clean up --- activitysim/abm/models/joint_tour_participation.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 65471d32a9..cda3535e12 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -424,10 +424,6 @@ def joint_tour_participation( if i not in model_settings.compute_settings.protect_columns: model_settings.compute_settings.protect_columns.append(i) - # TODO: loads of code duplication, could make this implicit and get rid of it - # if state.settings.use_explicit_error_terms: - # custom_chooser = participants_chooser_utility_based - # else: custom_chooser = participants_chooser choices = simulate.simple_simulate_by_chunk_id( From 6a0987d96590f8502353f43c710527b419d716ae Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 12 Apr 2025 05:31:56 +1000 Subject: [PATCH 107/298] identifies eet todos --- activitysim/abm/models/joint_tour_participation.py | 2 +- activitysim/abm/models/util/cdap.py | 2 +- activitysim/core/logit.py | 14 +++++++------- activitysim/core/simulate.py | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index cda3535e12..9c3dc9e770 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -207,7 +207,7 @@ def participants_chooser( ) print(unsatisfied_candidates.head(20)) - # TODO: explicit error term impl of force participation + # TODO-EET: explicit error term impl of force participation if model_settings.FORCE_PARTICIPATION and not state.settings.use_explicit_error_terms: logger.warning( f"Forcing joint tour participation for {num_tours_remaining} tours." diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index 5d4d90fc84..7602cfdd40 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -1033,7 +1033,7 @@ def household_activity_choices( "%s.hhsize%d_activity_choices" % (trace_label, hhsize), column_labels=["expression", "household"], ) - # TODO: check if different num rands matters here + # TODO-EET: check if different num rands matters here state.tracing.trace_df( rands, "%s.hhsize%d_rands" % (trace_label, hhsize), columns=[None, "rand"] ) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 8d75487e46..ba1bb79010 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -270,7 +270,7 @@ def utils_to_probs( return probs -# todo: check state, add type annotations, check new-world tracing, etc. +# TODO-EET: check state, add type annotations, check new-world tracing, etc. def add_ev1_random(state: workflow.State, df: pd.DataFrame): nest_utils_for_choice = df.copy() nest_utils_for_choice += state.get_rn_generator().gumbel_for_df(nest_utils_for_choice, n=df.shape[1]) @@ -289,7 +289,7 @@ def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alter raise ValueError("This should never happen - no alternative found") -# TODO [janzill Jun2022]: make everything in nested and here numpy from beginning to make choices consistent with +# TODO-EET [janzill Jun2022]: make everything in nested and here numpy from beginning to make choices consistent with # previous impl (want column index and not alternative name) # Note: this is relatively slow due to the apply. # It could *maybe* be sped up by using the fact that the nesting structure is the same for all rows: Add ev1(0,1) to @@ -322,7 +322,7 @@ def make_choices_explicit_error_term_nl(state, nested_utilities, alt_order_array def make_choices_explicit_error_term_mnl(state, utilities): utilities_incl_unobs = add_ev1_random(state, utilities) choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) - # TODO: reporting like for zero probs + # TODO-EET: reporting like for zero probs assert not np.isnan(choices).any(), "No choice for XXX - implement reporting" choices = pd.Series(choices, index=utilities_incl_unobs.index) return choices @@ -337,7 +337,7 @@ def make_choices_explicit_error_term(state, utilities, alt_order_array, nest_spe return choices -# TODO: memory usage +# TODO-EET: memory usage def make_choices_utility_based( state: workflow.State, utilities: pd.DataFrame, @@ -351,10 +351,10 @@ def make_choices_utility_based( ) -> tuple[pd.Series, pd.Series]: trace_label = tracing.extend_trace_label(trace_label, 'make_choices_utility_based') - # TODO: index of choices for nested utilities is different than unnested - this needs to be consistent for + # TODO-EET: index of choices for nested utilities is different than unnested - this needs to be consistent for # turning indexes into alternative names to keep code changes to minimum for now choices = make_choices_explicit_error_term(state, utilities, name_mapping, nest_spec, trace_label) - # TODO: rands - log all zeros for now + # TODO-EET: rands - log all zeros for now rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) return choices, rands @@ -663,7 +663,7 @@ def each_nest(nest_spec: dict | LogitNestSpec, type=None, post_order=False): yield nest -# TODO: do I need to implement this for LogitNestSpec? +# TODO-EET: do I need to implement this for LogitNestSpec? def count_nests(nest_spec): """ count the nests in nest_spec, return 0 if nest_spec is none diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index a395236624..3e9b600c0d 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1436,12 +1436,12 @@ def eval_nl( ) if state.settings.use_explicit_error_terms: - # TODO [janzill Jun2022]: combine with nested_exp_utilities? + # TODO-EET [janzill Jun2022]: combine with nested_exp_utilities? # utilities of leaves and nests nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) chunk_sizer.log_df(trace_label, "nested_utilities", nested_utilities) - # TODO [janzill Jun2022]: this can be done from utils directly, but use existing methodology for prototype + # TODO-EET [janzill Jun2022]: this can be done from utils directly, but use existing methodology for prototype if want_logsums: # logsum of nest root # exponentiated utilities of leaves and nests @@ -1450,7 +1450,7 @@ def eval_nl( logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) chunk_sizer.log_df(trace_label, "logsums", logsums) - # TODO: index of choices for nested utilities is different than unnested - this needs to be consistent for + # TODO-EET: index of choices for nested utilities is different than unnested - this needs to be consistent for # turning indexes into alternative names to keep code changes to minimum for now name_mapping = raw_utilities.columns.values From 5637fb0d84ec9c13887ed40534750e300914f096 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 12 Apr 2025 05:41:00 +1000 Subject: [PATCH 108/298] test SOA MC for disagg access --- activitysim/abm/models/disaggregate_accessibility.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index fd79a188dc..2a9d983e8b 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -75,7 +75,8 @@ class DisaggregateAccessibilityAnnotateSettings(PydanticReadable, extra="forbid" annotate: PreprocessorSettings -class DisaggregateAccessibilitySettings(PydanticReadable, extra="forbid"): +# TODO-EET: add eet override for SOA MC sampling +class DisaggregateAccessibilitySettings(PydanticReadable): #, extra="forbid") suffixes: DisaggregateAccessibilitySuffixes = DisaggregateAccessibilitySuffixes() ORIGIN_SAMPLE_SIZE: float | int = 0 """ From 6f8958be3a0b869156cb64ab673911585d637d42 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 12 Apr 2025 05:54:06 +1000 Subject: [PATCH 109/298] lint --- .../abm/models/disaggregate_accessibility.py | 2 +- .../abm/models/joint_tour_participation.py | 5 +- .../abm/models/trip_departure_choice.py | 2 +- activitysim/abm/models/util/cdap.py | 4 +- activitysim/core/interaction_sample.py | 67 +++++++++------- .../core/interaction_sample_simulate.py | 36 ++++++--- activitysim/core/interaction_simulate.py | 2 +- activitysim/core/logit.py | 58 +++++++++----- activitysim/core/random.py | 4 +- activitysim/core/simulate.py | 80 +++++++++++-------- 10 files changed, 159 insertions(+), 101 deletions(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 2a9d983e8b..3882384a73 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -76,7 +76,7 @@ class DisaggregateAccessibilityAnnotateSettings(PydanticReadable, extra="forbid" # TODO-EET: add eet override for SOA MC sampling -class DisaggregateAccessibilitySettings(PydanticReadable): #, extra="forbid") +class DisaggregateAccessibilitySettings(PydanticReadable): # , extra="forbid") suffixes: DisaggregateAccessibilitySuffixes = DisaggregateAccessibilitySuffixes() ORIGIN_SAMPLE_SIZE: float | int = 0 """ diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 9c3dc9e770..c74076fc17 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -208,7 +208,10 @@ def participants_chooser( print(unsatisfied_candidates.head(20)) # TODO-EET: explicit error term impl of force participation - if model_settings.FORCE_PARTICIPATION and not state.settings.use_explicit_error_terms: + if ( + model_settings.FORCE_PARTICIPATION + and not state.settings.use_explicit_error_terms + ): logger.warning( f"Forcing joint tour participation for {num_tours_remaining} tours." ) diff --git a/activitysim/abm/models/trip_departure_choice.py b/activitysim/abm/models/trip_departure_choice.py index 3886f3400a..577c65d17a 100644 --- a/activitysim/abm/models/trip_departure_choice.py +++ b/activitysim/abm/models/trip_departure_choice.py @@ -347,7 +347,7 @@ def choose_tour_leg_pattern( ) del utilities_df - chunk_sizer.log_df(trace_label, 'utilities_df', None) + chunk_sizer.log_df(trace_label, "utilities_df", None) else: # convert to probabilities (utilities exponentiated and normalized to probs) # probs is same shape as utilities, one row per chooser and one column for alternative diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index 7602cfdd40..e2f5598207 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -996,7 +996,9 @@ def household_activity_choices( utils = utils.add(joint_tour_utils) if state.settings.use_explicit_error_terms: - idx_choices, rands = logit.make_choices_utility_based(state, utils, trace_label=trace_label) + idx_choices, rands = logit.make_choices_utility_based( + state, utils, trace_label=trace_label + ) else: probs = logit.utils_to_probs(state, utils, trace_label=trace_label) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index e06a490068..5ba8254e25 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -45,61 +45,69 @@ def make_sample_choices_utility_based( # Note [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension # (len(choosers), alternative_count, sample_size) can get very large - #choices = np.zeros_like(utilities, dtype=np.uint32) - #zero_dim_index = np.arange(utilities.shape[0]) + # choices = np.zeros_like(utilities, dtype=np.uint32) + # zero_dim_index = np.arange(utilities.shape[0]) utils_array = utilities.to_numpy() - chunk_sizer.log_df(trace_label, 'utils_array', utils_array) + chunk_sizer.log_df(trace_label, "utils_array", utils_array) chosen_destinations = [] rands = state.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) - chunk_sizer.log_df(trace_label, 'rands', rands) + chunk_sizer.log_df(trace_label, "rands", rands) for i in range(sample_size): - #rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) - #choices[zero_dim_index, np.argmax(inverse_ev1_cdf(rands) + utils_array, axis=1)] += 1 - #choices[ + # rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) + # choices[zero_dim_index, np.argmax(inverse_ev1_cdf(rands) + utils_array, axis=1)] += 1 + # choices[ # zero_dim_index, # np.argmax(pipeline.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + utils_array, axis=1) - #] += 1 + # ] += 1 # created this once for memory logging if i > 0: - rands = state.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + rands = state.get_rn_generator().gumbel_for_df( + utilities, n=alternative_count + ) chosen_destinations.append(np.argmax(utils_array + rands, axis=1)) chosen_destinations = np.concatenate(chosen_destinations, axis=0) - chunk_sizer.log_df(trace_label, 'chosen_destinations', chosen_destinations) + chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) del utils_array - chunk_sizer.log_df(trace_label, 'utils_array', None) + chunk_sizer.log_df(trace_label, "utils_array", None) del rands - chunk_sizer.log_df(trace_label, 'rands', None) + chunk_sizer.log_df(trace_label, "rands", None) chooser_idx = np.tile(np.arange(utilities.shape[0]), sample_size) - #chunk.log_df(trace_label, 'choices_array', choices_array) + # chunk.log_df(trace_label, 'choices_array', choices_array) # choices array has same dim as utilities, with values indicating number of counts per chooser and alternative # let's turn the nonzero values into a dataframe - #i, j = np.nonzero(choices_array) - chunk_sizer.log_df(trace_label, 'chooser_idx', chooser_idx) + # i, j = np.nonzero(choices_array) + chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) probs = logit.utils_to_probs( - state, utilities, allow_zero_probs=allow_zero_probs, trace_label=trace_label, trace_choosers=choosers + state, + utilities, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + trace_choosers=choosers, + ) + chunk_sizer.log_df(trace_label, "probs", probs) + + choices_df = pd.DataFrame( + { + alt_col_name: alternatives.index.values[chosen_destinations], + # "pick_count": choices_array[i, j], + "prob": probs.to_numpy()[chooser_idx, chosen_destinations], + choosers.index.name: choosers.index.values[chooser_idx], + } ) - chunk_sizer.log_df(trace_label, 'probs', probs) - - choices_df = pd.DataFrame({ - alt_col_name: alternatives.index.values[chosen_destinations], - #"pick_count": choices_array[i, j], - "prob": probs.to_numpy()[chooser_idx, chosen_destinations], - choosers.index.name: choosers.index.values[chooser_idx] - }) chunk_sizer.log_df(trace_label, "choices_df", choices_df) del chooser_idx - chunk_sizer.log_df(trace_label, 'chooser_idx', None) + chunk_sizer.log_df(trace_label, "chooser_idx", None) del chosen_destinations - chunk_sizer.log_df(trace_label, 'chosen_destinations', None) + chunk_sizer.log_df(trace_label, "chosen_destinations", None) del probs - chunk_sizer.log_df(trace_label, 'probs', None) + chunk_sizer.log_df(trace_label, "probs", None) # handing this off to caller chunk_sizer.log_df(trace_label, "choices_df", None) @@ -525,7 +533,9 @@ def _interaction_sample( if compute_settings.use_explicit_error_terms is not None: use_eet = compute_settings.use_explicit_error_terms - logger.info(f"Interaction sample model-specific EET overrides for {trace_label}: eet = {use_eet}") + logger.info( + f"Interaction sample model-specific EET overrides for {trace_label}: eet = {use_eet}" + ) else: use_eet = state.settings.use_explicit_error_terms @@ -747,7 +757,6 @@ def interaction_sample( sample_size = min(sample_size, len(alternatives.index)) logger.info(f" --- interaction_sample sample size = {sample_size}") - result_list = [] for ( i, diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 5f37a3e83c..1ca92481e2 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -98,12 +98,16 @@ def _interaction_sample_simulate( # this is the more general check (not requiring is_monotonic_increasing) last_repeat = alternatives.index != np.roll(alternatives.index, -1) - assert (choosers.shape[0] == 1) or choosers.index.equals(alternatives.index[last_repeat]) + assert (choosers.shape[0] == 1) or choosers.index.equals( + alternatives.index[last_repeat] + ) have_trace_targets = state.tracing.has_trace_targets(choosers) if have_trace_targets: - state.tracing.trace_df(choosers, tracing.extend_trace_label(trace_label, "choosers")) + state.tracing.trace_df( + choosers, tracing.extend_trace_label(trace_label, "choosers") + ) state.tracing.trace_df( alternatives, tracing.extend_trace_label(trace_label, "alternatives"), @@ -129,7 +133,9 @@ def _interaction_sample_simulate( # assert alternatives.index.name == choosers.index.name # asserting the index names are the same tells us nothing about the underlying data so why? - logger.info(f"{trace_label} start merging choosers and alternatives to create interaction_df") + logger.info( + f"{trace_label} start merging choosers and alternatives to create interaction_df" + ) # drop variables before the interaction dataframe is created sharrow_enabled = state.settings.sharrow @@ -163,17 +169,23 @@ def _interaction_sample_simulate( ) interaction_df = alternatives.join(choosers, how="left", rsuffix="_chooser") - logger.info(f"{trace_label} end merging choosers and alternatives to create interaction_df") + logger.info( + f"{trace_label} end merging choosers and alternatives to create interaction_df" + ) if log_alt_losers: # logit.interaction_dataset adds ALT_CHOOSER_ID column if log_alt_losers is True # to enable detection of zero_prob-driving utils (e.g. -999 for all alts in a chooser) - interaction_df[interaction_simulate.ALT_CHOOSER_ID] = interaction_df.index.values + interaction_df[ + interaction_simulate.ALT_CHOOSER_ID + ] = interaction_df.index.values chunk_sizer.log_df(trace_label, "interaction_df", interaction_df) if have_trace_targets: - trace_rows, trace_ids = state.tracing.interaction_trace_rows(interaction_df, choosers) + trace_rows, trace_ids = state.tracing.interaction_trace_rows( + interaction_df, choosers + ) state.tracing.trace_df( interaction_df, @@ -229,7 +241,9 @@ def _interaction_sample_simulate( # so we need to pad with dummy utilities so low that they are never chosen # number of samples per chooser - sample_counts = interaction_utilities.groupby(interaction_utilities.index).size().values + sample_counts = ( + interaction_utilities.groupby(interaction_utilities.index).size().values + ) chunk_sizer.log_df(trace_label, "sample_counts", sample_counts) # max number of alternatvies for any chooser @@ -274,7 +288,9 @@ def _interaction_sample_simulate( if state.settings.use_explicit_error_terms: if want_logsums: - logsums = logit.utils_to_logsums(utilities_df, allow_zero_probs=allow_zero_probs) + logsums = logit.utils_to_logsums( + utilities_df, allow_zero_probs=allow_zero_probs + ) chunk_sizer.log_df(trace_label, "logsums", logsums) if skip_choice: @@ -334,7 +350,9 @@ def _interaction_sample_simulate( # make choices # positions is series with the chosen alternative represented as a column index in probs # which is an integer between zero and num alternatives in the alternative sample - positions, rands = logit.make_choices(state, probs, trace_label=trace_label, trace_choosers=choosers) + positions, rands = logit.make_choices( + state, probs, trace_label=trace_label, trace_choosers=choosers + ) del probs chunk_sizer.log_df(trace_label, "probs", None) diff --git a/activitysim/core/interaction_simulate.py b/activitysim/core/interaction_simulate.py index 6dec23d0b2..7f70c83218 100644 --- a/activitysim/core/interaction_simulate.py +++ b/activitysim/core/interaction_simulate.py @@ -887,7 +887,7 @@ def _interaction_simulate( ) del utilities - chunk_sizer.log_df(trace_label, 'utilities', None) + chunk_sizer.log_df(trace_label, "utilities", None) else: # convert to probabilities (utilities exponentiated and normalized to probs) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index ba1bb79010..4351681ade 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -273,11 +273,15 @@ def utils_to_probs( # TODO-EET: check state, add type annotations, check new-world tracing, etc. def add_ev1_random(state: workflow.State, df: pd.DataFrame): nest_utils_for_choice = df.copy() - nest_utils_for_choice += state.get_rn_generator().gumbel_for_df(nest_utils_for_choice, n=df.shape[1]) + nest_utils_for_choice += state.get_rn_generator().gumbel_for_df( + nest_utils_for_choice, n=df.shape[1] + ) return nest_utils_for_choice -def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name): +def choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name +): for level, nest_names in logit_nest_groups.items(): if level == 1: next_level_alts = nest_alternatives_by_name[nest_names[0]] @@ -297,17 +301,21 @@ def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alter # alternatives and set the corresponding entry to 1 for each row, set all other alternatives at this level to zero. # Once the tree is walked (all alternatives have been processed), take the product of the alternatives in each # leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. -def make_choices_explicit_error_term_nl(state, nested_utilities, alt_order_array, nest_spec): - """ walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" +def make_choices_explicit_error_term_nl( + state, nested_utilities, alt_order_array, nest_spec +): + """walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" nest_utils_for_choice = add_ev1_random(state, nested_utilities) - all_alternatives = set(nest.name for nest in each_nest(nest_spec, type='leaf')) + all_alternatives = set(nest.name for nest in each_nest(nest_spec, type="leaf")) logit_nest_groups = group_nest_names_by_level(nest_spec) nest_alternatives_by_name = {n.name: n.alternatives for n in each_nest(nest_spec)} choices = nest_utils_for_choice.apply( - lambda x: choose_from_tree(x, all_alternatives, logit_nest_groups, nest_alternatives_by_name), - axis=1 + lambda x: choose_from_tree( + x, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ), + axis=1, ) assert not choices.isnull().any(), "No choice for XXX - implement reporting" choices = pd.Series(choices, index=nest_utils_for_choice.index) @@ -328,32 +336,38 @@ def make_choices_explicit_error_term_mnl(state, utilities): return choices -def make_choices_explicit_error_term(state, utilities, alt_order_array, nest_spec=None, trace_label=None): - trace_label = tracing.extend_trace_label(trace_label, 'make_choices_ru_frozen') +def make_choices_explicit_error_term( + state, utilities, alt_order_array, nest_spec=None, trace_label=None +): + trace_label = tracing.extend_trace_label(trace_label, "make_choices_ru_frozen") if nest_spec is None: choices = make_choices_explicit_error_term_mnl(state, utilities) else: - choices = make_choices_explicit_error_term_nl(state, utilities, alt_order_array, nest_spec) + choices = make_choices_explicit_error_term_nl( + state, utilities, alt_order_array, nest_spec + ) return choices # TODO-EET: memory usage def make_choices_utility_based( - state: workflow.State, - utilities: pd.DataFrame, - # for nested: need mapping of index to alternative name to "fake" indexes if I want to keep with current - # structure, OR need to make returning names optional. sharrow impl will make our life so much easier - name_mapping=None, - nest_spec=None, - trace_label: str = None, - trace_choosers=None, - allow_bad_probs=False, + state: workflow.State, + utilities: pd.DataFrame, + # for nested: need mapping of index to alternative name to "fake" indexes if I want to keep with current + # structure, OR need to make returning names optional. sharrow impl will make our life so much easier + name_mapping=None, + nest_spec=None, + trace_label: str = None, + trace_choosers=None, + allow_bad_probs=False, ) -> tuple[pd.Series, pd.Series]: - trace_label = tracing.extend_trace_label(trace_label, 'make_choices_utility_based') + trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") # TODO-EET: index of choices for nested utilities is different than unnested - this needs to be consistent for # turning indexes into alternative names to keep code changes to minimum for now - choices = make_choices_explicit_error_term(state, utilities, name_mapping, nest_spec, trace_label) + choices = make_choices_explicit_error_term( + state, utilities, name_mapping, nest_spec, trace_label + ) # TODO-EET: rands - log all zeros for now rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) return choices, rands @@ -686,7 +700,7 @@ def count_each_nest(spec, count): def group_nest_names_by_level(nest_spec): # group nests by level, returns {level: [nest.name at that level]} depth = np.max([x.level for x in each_nest(nest_spec)]) - nest_levels = {x: [] for x in range(1, depth+1)} + nest_levels = {x: [] for x in range(1, depth + 1)} for n in each_nest(nest_spec): nest_levels[n.level].append(n.name) return nest_levels diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 0ad75fdf70..dcef272ff6 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -282,12 +282,12 @@ def gumbel_for_df(self, df, step_name, n=1): # - reminder: prng must be called when yielded as generated sequence, not serialized generators = self._generators_for_df(df) - #rands = np.asanyarray([prng.gumbel(size=n) for prng in generators]) + # rands = np.asanyarray([prng.gumbel(size=n) for prng in generators]) # this is about 20% faster for large arrays, like for destination choice rands = np.asanyarray([-np.log(-np.log(prng.rand(n))) for prng in generators]) # update offset for rows we handled - self.row_states.loc[df.index, 'offset'] += n + self.row_states.loc[df.index, "offset"] += n return rands def normal_for_df(self, df, step_name, mu, sigma, lognormal=False, size=None): diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 3e9b600c0d..75ff479926 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1035,41 +1035,45 @@ def set_skim_wrapper_targets(df, skims): def compute_nested_utilities(raw_utilities, nest_spec): """ - compute nest utilities based on nesting coefficients - - For nest nodes this is the logsum of alternatives adjusted by nesting coefficient - - leaf <- raw_utility / nest_coefficient - nest <- ln(sum of exponentiated raw_utility of leaves) * nest_coefficient) - - Parameters - ---------- - raw_utilities : pandas.DataFrame - dataframe with the raw alternative utilities of all leaves - (what in non-nested logit would be the utilities of all the alternatives) - nest_spec : dict - Nest tree dict from the model spec yaml file - - Returns - ------- - nested_utilities : pandas.DataFrame - Will have the index of `raw_utilities` and columns for leaf and node utilities - """ + compute nest utilities based on nesting coefficients + + For nest nodes this is the logsum of alternatives adjusted by nesting coefficient + + leaf <- raw_utility / nest_coefficient + nest <- ln(sum of exponentiated raw_utility of leaves) * nest_coefficient) + + Parameters + ---------- + raw_utilities : pandas.DataFrame + dataframe with the raw alternative utilities of all leaves + (what in non-nested logit would be the utilities of all the alternatives) + nest_spec : dict + Nest tree dict from the model spec yaml file + + Returns + ------- + nested_utilities : pandas.DataFrame + Will have the index of `raw_utilities` and columns for leaf and node utilities + """ nested_utilities = pd.DataFrame(index=raw_utilities.index) for nest in logit.each_nest(nest_spec, post_order=True): name = nest.name if nest.is_leaf: # do not scale here, do afterwards so recursive structure works - nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients + nested_utilities[name] = ( + raw_utilities[name].astype(float) / nest.product_of_coefficients + ) else: # the alternative nested_utilities will already have been computed due to post_order - with np.errstate(divide='ignore'): - nested_utilities[name] = \ - nest.coefficient * np.log( - np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) + with np.errstate(divide="ignore"): + nested_utilities[name] = nest.coefficient * np.log( + np.exp(nested_utilities[nest.alternatives]).sum(axis=1) + ) return nested_utilities + + # So it looks like TM1 was estimated such that the nest coefficient is the ratio of lower level and upper level # nest in larch. This means the values can all be between 0 and 1, unlike for larch where they need to be decreasing # going down the tree. In that world, the above would read @@ -1304,12 +1308,16 @@ def eval_mnl( if state.settings.use_explicit_error_terms: if custom_chooser: - choices, rands = custom_chooser(state, utilities, choosers, spec, trace_label) + choices, rands = custom_chooser( + state, utilities, choosers, spec, trace_label + ) else: - choices, rands = logit.make_choices_utility_based(state, utilities, trace_label=trace_label) + choices, rands = logit.make_choices_utility_based( + state, utilities, trace_label=trace_label + ) del utilities - chunk_sizer.log_df(trace_label, 'utilities', None) + chunk_sizer.log_df(trace_label, "utilities", None) else: probs = logit.utils_to_probs( @@ -1445,8 +1453,12 @@ def eval_nl( if want_logsums: # logsum of nest root # exponentiated utilities of leaves and nests - nested_exp_utilities = compute_nested_exp_utilities(raw_utilities, nest_spec) - chunk_sizer.log_df(trace_label, "nested_exp_utilities", nested_exp_utilities) + nested_exp_utilities = compute_nested_exp_utilities( + raw_utilities, nest_spec + ) + chunk_sizer.log_df( + trace_label, "nested_exp_utilities", nested_exp_utilities + ) logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) chunk_sizer.log_df(trace_label, "logsums", logsums) @@ -1455,7 +1467,7 @@ def eval_nl( name_mapping = raw_utilities.columns.values del raw_utilities - chunk_sizer.log_df(trace_label, 'raw_utilities', None) + chunk_sizer.log_df(trace_label, "raw_utilities", None) if custom_chooser: choices, rands = custom_chooser( @@ -1465,7 +1477,7 @@ def eval_nl( choosers=choosers, spec=spec, nest_spec=nest_spec, - trace_label=trace_label + trace_label=trace_label, ) else: choices, rands = logit.make_choices_utility_based( @@ -1473,10 +1485,10 @@ def eval_nl( nested_utilities, name_mapping=name_mapping, nest_spec=nest_spec, - trace_label=trace_label + trace_label=trace_label, ) - del(nested_utilities) + del nested_utilities chunk_sizer.log_df(trace_label, "nested_utilities", None) else: From 9d12b08fd3dbcbb740d5317d6d91a385c160c437 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 25 May 2025 07:55:04 +1000 Subject: [PATCH 110/298] comments --- activitysim/core/interaction_sample.py | 3 ++- activitysim/core/simulate.py | 17 ----------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 5ba8254e25..355290454d 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -43,7 +43,7 @@ def make_sample_choices_utility_based( assert isinstance(alternatives, pd.DataFrame) assert len(alternatives) == alternative_count - # Note [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension + # TODO-EET [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension # (len(choosers), alternative_count, sample_size) can get very large # choices = np.zeros_like(utilities, dtype=np.uint32) # zero_dim_index = np.arange(utilities.shape[0]) @@ -540,6 +540,7 @@ def _interaction_sample( use_eet = state.settings.use_explicit_error_terms # sample_size == 0 is for estimation mode, see below + # TODO-EET: add sample_size == 0 to EET, this needs to be an option not just for estimation mode if (sample_size != 0) and use_eet: choices_df = make_sample_choices_utility_based( state, diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 75ff479926..8acdc6f19c 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1074,23 +1074,6 @@ def compute_nested_utilities(raw_utilities, nest_spec): return nested_utilities -# So it looks like TM1 was estimated such that the nest coefficient is the ratio of lower level and upper level -# nest in larch. This means the values can all be between 0 and 1, unlike for larch where they need to be decreasing -# going down the tree. In that world, the above would read -# for nest in logit.each_nest(nest_spec, post_order=True): -# name = nest.name -# if nest.is_leaf: -# # do not scale here, do afterwards so recursive structure works -# nested_utilities[name] = raw_utilities[name].astype(float) -# else: -# # the alternative nested_utilities will already have been computed due to post_order -# with np.errstate(divide='ignore'): -# nested_utilities[name] = \ -# nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) -# nested_utilities[name] /= parent_nest_scale # parent_nest_scale would need to be defined as part of nest -# # and would be = coeffiecient for leaves and for nests it would be that of the parent nest - - def compute_nested_exp_utilities(raw_utilities, nest_spec): """ compute exponentiated nest utilities based on nesting coefficients From 9a0fb663dc11e68b288d067aaa5916930f541035 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 25 May 2025 08:32:05 +1000 Subject: [PATCH 111/298] more comments --- activitysim/core/logit.py | 6 ++---- activitysim/core/simulate.py | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 4351681ade..91a9d2e973 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -317,6 +317,7 @@ def make_choices_explicit_error_term_nl( ), axis=1, ) + # TODO-EET: reporting like for zero probs assert not choices.isnull().any(), "No choice for XXX - implement reporting" choices = pd.Series(choices, index=nest_utils_for_choice.index) @@ -349,12 +350,10 @@ def make_choices_explicit_error_term( return choices -# TODO-EET: memory usage +# TODO-EET: memory logging def make_choices_utility_based( state: workflow.State, utilities: pd.DataFrame, - # for nested: need mapping of index to alternative name to "fake" indexes if I want to keep with current - # structure, OR need to make returning names optional. sharrow impl will make our life so much easier name_mapping=None, nest_spec=None, trace_label: str = None, @@ -677,7 +676,6 @@ def each_nest(nest_spec: dict | LogitNestSpec, type=None, post_order=False): yield nest -# TODO-EET: do I need to implement this for LogitNestSpec? def count_nests(nest_spec): """ count the nests in nest_spec, return 0 if nest_spec is none diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 8acdc6f19c..e4ed3f3878 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1060,7 +1060,6 @@ def compute_nested_utilities(raw_utilities, nest_spec): for nest in logit.each_nest(nest_spec, post_order=True): name = nest.name if nest.is_leaf: - # do not scale here, do afterwards so recursive structure works nested_utilities[name] = ( raw_utilities[name].astype(float) / nest.product_of_coefficients ) @@ -1427,12 +1426,11 @@ def eval_nl( ) if state.settings.use_explicit_error_terms: - # TODO-EET [janzill Jun2022]: combine with nested_exp_utilities? # utilities of leaves and nests nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) chunk_sizer.log_df(trace_label, "nested_utilities", nested_utilities) - # TODO-EET [janzill Jun2022]: this can be done from utils directly, but use existing methodology for prototype + # TODO-EET: use nested_utiltites directly to compute logsums? if want_logsums: # logsum of nest root # exponentiated utilities of leaves and nests From 0fa183ec26ad8e564b862054a0d3a38a0d285ad8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 25 May 2025 11:15:07 +1000 Subject: [PATCH 112/298] jtp custom chooser for EET --- .../abm/models/joint_tour_participation.py | 59 +++++++++++-------- activitysim/core/logit.py | 13 ++-- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index c74076fc17..9819c87135 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -123,7 +123,7 @@ def get_tour_satisfaction(candidates, participate): def participants_chooser( state: workflow.State, - probs: pd.DataFrame, + probs_or_utils: pd.DataFrame, choosers: pd.DataFrame, spec: pd.DataFrame, trace_label: str, @@ -143,11 +143,11 @@ def participants_chooser( Parameters ---------- - probs : pandas.DataFrame + probs_or_utils : pandas.DataFrame Rows for choosers and columns for the alternatives from which they - are choosing. Values are expected to be valid probabilities across - each row, e.g. they should sum to 1, unless using explicit error terms, - in which case values are utilities. + are choosing. If running with explicit_error_terms, these are utilities. + Otherwise, values are expected to be valid probabilities across + each row, e.g. they should sum to 1. choosers : pandas.dataframe simple_simulate choosers df spec : pandas.DataFrame @@ -163,7 +163,7 @@ def participants_chooser( """ - assert probs.index.equals(choosers.index) + assert probs_or_utils.index.equals(choosers.index) # choice is boolean (participate or not) model_settings = JointTourParticipationSettings.read_settings_file( @@ -199,7 +199,7 @@ def participants_chooser( "%s max iterations exceeded (%s).", trace_label, MAX_ITERATIONS ) diagnostic_cols = ["tour_id", "household_id", "composition", "adult"] - unsatisfied_candidates = candidates[diagnostic_cols].join(probs) + unsatisfied_candidates = candidates[diagnostic_cols].join(probs_or_utils) state.tracing.write_csv( unsatisfied_candidates, file_name="%s.UNSATISFIED" % trace_label, @@ -207,18 +207,25 @@ def participants_chooser( ) print(unsatisfied_candidates.head(20)) - # TODO-EET: explicit error term impl of force participation - if ( - model_settings.FORCE_PARTICIPATION - and not state.settings.use_explicit_error_terms - ): + if model_settings.FORCE_PARTICIPATION: logger.warning( f"Forcing joint tour participation for {num_tours_remaining} tours." ) # anybody with probability > 0 is forced to join the joint tour - probs[choice_col] = np.where(probs[choice_col] > 0, 1, 0) - non_choice_col = [col for col in probs.columns if col != choice_col][0] - probs[non_choice_col] = 1 - probs[choice_col] + if state.settings.use_explicit_error_terms: + # need "is valid choice", which requires minimum probability. This depends + # on all other utilities, and we need to make sure that we set values such that we + # certainly choose those with non-zero values, and do not choose other ones. Let's + # use -999 as zero prob choice and 10 as definitive choice (based on diff and std gumbel). + probs_from_utils = logit.utils_to_probs(state, probs_or_utils) + probs_or_utils[choice_col] = np.where(probs_from_utils[choice_col] > 0, 10, -999) + non_choice_col = [col for col in probs_or_utils.columns if col != choice_col][0] + probs_or_utils[non_choice_col] = np.where(probs_or_utils[choice_col] == -999, 10, -999) + else: + probs_or_utils[choice_col] = np.where(probs_or_utils[choice_col] > 0, 1, 0) + non_choice_col = [col for col in probs_or_utils.columns if col != choice_col][0] + probs_or_utils[non_choice_col] = 1 - probs_or_utils[choice_col] + if iter > MAX_ITERATIONS + 1: raise RuntimeError( f"{num_tours_remaining} tours could not be satisfied even with forcing participation" @@ -228,14 +235,10 @@ def participants_chooser( f"{num_tours_remaining} tours could not be satisfied after {iter} iterations" ) - if state.settings.use_explicit_error_terms: - choices, rands = logit.make_choices_utility_based( - state, probs, trace_label=trace_label, trace_choosers=choosers - ) - else: - choices, rands = logit.make_choices( - state, probs, trace_label=trace_label, trace_choosers=choosers - ) + choice_function = ( + logit.make_choices_utility_based if state.settings.use_explicit_error_terms else logit.make_choices + ) + choices, rands = choice_function(state, probs_or_utils, trace_label=trace_label, trace_choosers=choosers) participate = choices == PARTICIPATE_CHOICE # satisfaction indexed by tour_id @@ -258,7 +261,7 @@ def participants_chooser( rands_list.append(rands[satisfied]) # remove candidates of satisfied tours - probs = probs[~satisfied] + probs_or_utils = probs_or_utils[~satisfied] candidates = candidates[~satisfied] logger.debug( @@ -427,6 +430,14 @@ def joint_tour_participation( if i not in model_settings.compute_settings.protect_columns: model_settings.compute_settings.protect_columns.append(i) + # TODO EET: this is related to the difference in nested logit and logit choice as per comment in + # make_choices_utility_based. As soon as alt_order_array is removed from arguments to + # make_choices_explicit_error_term_nl this guard can be removed + if state.settings.use_explicit_error_terms: + assert nest_spec is not None, ( + "Nested logit model custom chooser for EET requires name_mapping, currently not implemented in jtp" + ) + custom_chooser = participants_chooser choices = simulate.simple_simulate_by_chunk_id( diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 91a9d2e973..8bec1b910d 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -293,14 +293,6 @@ def choose_from_tree( raise ValueError("This should never happen - no alternative found") -# TODO-EET [janzill Jun2022]: make everything in nested and here numpy from beginning to make choices consistent with -# previous impl (want column index and not alternative name) -# Note: this is relatively slow due to the apply. -# It could *maybe* be sped up by using the fact that the nesting structure is the same for all rows: Add ev1(0,1) to -# all entries (as is currently being done). Then, at each level, pick the maximum of the available composite -# alternatives and set the corresponding entry to 1 for each row, set all other alternatives at this level to zero. -# Once the tree is walked (all alternatives have been processed), take the product of the alternatives in each -# leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. def make_choices_explicit_error_term_nl( state, nested_utilities, alt_order_array, nest_spec ): @@ -311,6 +303,11 @@ def make_choices_explicit_error_term_nl( logit_nest_groups = group_nest_names_by_level(nest_spec) nest_alternatives_by_name = {n.name: n.alternatives for n in each_nest(nest_spec)} + # Apply is slow. It could *maybe* be sped up by using the fact that the nesting structure is the same for all rows: + # Add ev1(0,1) to all entries (as is currently being done). Then, at each level, pick the maximum of the available + # composite alternatives and set the corresponding entry to 1 for each row, set all other alternatives at this level + # to zero. Once the tree is walked (all alternatives have been processed), take the product of the alternatives in + # each leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. choices = nest_utils_for_choice.apply( lambda x: choose_from_tree( x, all_alternatives, logit_nest_groups, nest_alternatives_by_name From 9900f48486282d9017109782308396dfe5487026 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 25 May 2025 19:23:41 +1000 Subject: [PATCH 113/298] lint --- .../abm/models/joint_tour_participation.py | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 9819c87135..80458ce27b 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -218,12 +218,22 @@ def participants_chooser( # certainly choose those with non-zero values, and do not choose other ones. Let's # use -999 as zero prob choice and 10 as definitive choice (based on diff and std gumbel). probs_from_utils = logit.utils_to_probs(state, probs_or_utils) - probs_or_utils[choice_col] = np.where(probs_from_utils[choice_col] > 0, 10, -999) - non_choice_col = [col for col in probs_or_utils.columns if col != choice_col][0] - probs_or_utils[non_choice_col] = np.where(probs_or_utils[choice_col] == -999, 10, -999) + probs_or_utils[choice_col] = np.where( + probs_from_utils[choice_col] > 0, 10, -999 + ) + non_choice_col = [ + col for col in probs_or_utils.columns if col != choice_col + ][0] + probs_or_utils[non_choice_col] = np.where( + probs_or_utils[choice_col] == -999, 10, -999 + ) else: - probs_or_utils[choice_col] = np.where(probs_or_utils[choice_col] > 0, 1, 0) - non_choice_col = [col for col in probs_or_utils.columns if col != choice_col][0] + probs_or_utils[choice_col] = np.where( + probs_or_utils[choice_col] > 0, 1, 0 + ) + non_choice_col = [ + col for col in probs_or_utils.columns if col != choice_col + ][0] probs_or_utils[non_choice_col] = 1 - probs_or_utils[choice_col] if iter > MAX_ITERATIONS + 1: @@ -236,9 +246,13 @@ def participants_chooser( ) choice_function = ( - logit.make_choices_utility_based if state.settings.use_explicit_error_terms else logit.make_choices + logit.make_choices_utility_based + if state.settings.use_explicit_error_terms + else logit.make_choices + ) + choices, rands = choice_function( + state, probs_or_utils, trace_label=trace_label, trace_choosers=choosers ) - choices, rands = choice_function(state, probs_or_utils, trace_label=trace_label, trace_choosers=choosers) participate = choices == PARTICIPATE_CHOICE # satisfaction indexed by tour_id @@ -431,12 +445,12 @@ def joint_tour_participation( model_settings.compute_settings.protect_columns.append(i) # TODO EET: this is related to the difference in nested logit and logit choice as per comment in - # make_choices_utility_based. As soon as alt_order_array is removed from arguments to + # make_choices_utility_based. As soon as alt_order_array is removed from arguments to # make_choices_explicit_error_term_nl this guard can be removed if state.settings.use_explicit_error_terms: - assert nest_spec is not None, ( - "Nested logit model custom chooser for EET requires name_mapping, currently not implemented in jtp" - ) + assert ( + nest_spec is not None + ), "Nested logit model custom chooser for EET requires name_mapping, currently not implemented in jtp" custom_chooser = participants_chooser From 892c3fbe8d2b2f29f1549804f14c5b8c70608496 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 06:39:37 +1000 Subject: [PATCH 114/298] move interaction sample without sampling out of MC loop to enable for both MC and eet --- activitysim/core/interaction_sample.py | 85 +++++++++++++++----------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 355290454d..7469618555 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -539,6 +539,45 @@ def _interaction_sample( else: use_eet = state.settings.use_explicit_error_terms + if sample_size == 0: + # FIXME return full alternative set rather than sample + logger.info( + "Estimation mode for %s using unsampled alternatives" % (trace_label,) + ) + probs = logit.utils_to_probs( + state, + utilities, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + trace_choosers=choosers, + overflow_protection=not allow_zero_probs, + ) + chunk_sizer.log_df(trace_label, "probs", probs) + + del utilities + chunk_sizer.log_df(trace_label, "utilities", None) + + index_name = probs.index.name + choices_df = ( + pd.melt(probs.reset_index(), id_vars=[index_name]) + .sort_values(by=index_name, kind="mergesort") + .set_index(index_name) + .rename(columns={"value": "prob"}) + .drop(columns="variable") + ) + chunk_sizer.log_df(trace_label, "choices_df", choices_df) + + del probs + chunk_sizer.log_df(trace_label, "probs", None) + + choices_df["pick_count"] = 1 + choices_df.insert( + 0, alt_col_name, np.tile(alternatives.index.values, len(choosers.index)) + ) + chunk_sizer.log_df(trace_label, "choices_df", choices_df) + + return choices_df + # sample_size == 0 is for estimation mode, see below # TODO-EET: add sample_size == 0 to EET, this needs to be an option not just for estimation mode if (sample_size != 0) and use_eet: @@ -579,40 +618,18 @@ def _interaction_sample( column_labels=["alternative", "probability"], ) - if sample_size == 0: - # FIXME return full alternative set rather than sample - logger.info( - "Estimation mode for %s using unsampled alternatives" % (trace_label,) - ) - - index_name = probs.index.name - choices_df = ( - pd.melt(probs.reset_index(), id_vars=[index_name]) - .sort_values(by=index_name, kind="mergesort") - .set_index(index_name) - .rename(columns={"value": "prob"}) - .drop(columns="variable") - ) - - choices_df["pick_count"] = 1 - choices_df.insert( - 0, alt_col_name, np.tile(alternatives.index.values, len(choosers.index)) - ) - - return choices_df - else: - choices_df = make_sample_choices( - state, - choosers, - probs, - alternatives, - sample_size, - alternative_count, - alt_col_name, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - chunk_sizer=chunk_sizer, - ) + choices_df = make_sample_choices( + state, + choosers, + probs, + alternatives, + sample_size, + alternative_count, + alt_col_name, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + chunk_sizer=chunk_sizer, + ) del probs chunk_sizer.log_df(trace_label, "probs", None) From d6ecb0d9a1b9670fe2122f2a05db3953edd39f3a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 06:51:17 +1000 Subject: [PATCH 115/298] update comments --- activitysim/core/interaction_sample.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 7469618555..9788573e15 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -578,9 +578,7 @@ def _interaction_sample( return choices_df - # sample_size == 0 is for estimation mode, see below - # TODO-EET: add sample_size == 0 to EET, this needs to be an option not just for estimation mode - if (sample_size != 0) and use_eet: + if use_eet: choices_df = make_sample_choices_utility_based( state, choosers, From 3793ec24442b9dbf759d8f900b5bb4a684f0a283 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 07:36:30 +1000 Subject: [PATCH 116/298] fix no sampling in interaction_sample --- activitysim/core/interaction_sample.py | 38 ++++++++------------------ 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 9788573e15..c9a9f81b38 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -540,41 +540,25 @@ def _interaction_sample( use_eet = state.settings.use_explicit_error_terms if sample_size == 0: - # FIXME return full alternative set rather than sample - logger.info( - "Estimation mode for %s using unsampled alternatives" % (trace_label,) - ) - probs = logit.utils_to_probs( - state, - utilities, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - trace_choosers=choosers, - overflow_protection=not allow_zero_probs, - ) - chunk_sizer.log_df(trace_label, "probs", probs) + # Return full alternative set rather than sample + logger.info("Using unsampled alternatives for %s" % (trace_label,)) - del utilities - chunk_sizer.log_df(trace_label, "utilities", None) - - index_name = probs.index.name + index_name = utilities.index.name choices_df = ( - pd.melt(probs.reset_index(), id_vars=[index_name]) + pd.melt(utilities.reset_index(), id_vars=[index_name], value_name='prob', var_name=alt_col_name) .sort_values(by=index_name, kind="mergesort") .set_index(index_name) - .rename(columns={"value": "prob"}) - .drop(columns="variable") + .assign(prob = 1) + .assign(pick_count = 1) ) chunk_sizer.log_df(trace_label, "choices_df", choices_df) - del probs - chunk_sizer.log_df(trace_label, "probs", None) + # utilities are numbered 0..n-1 so we need to map back to alt ids + alternative_map = pd.Series(alternatives.index).to_dict() + choices_df[alt_col_name] = choices_df[alt_col_name].map(alternative_map) - choices_df["pick_count"] = 1 - choices_df.insert( - 0, alt_col_name, np.tile(alternatives.index.values, len(choosers.index)) - ) - chunk_sizer.log_df(trace_label, "choices_df", choices_df) + del utilities + chunk_sizer.log_df(trace_label, "utilities", None) return choices_df From fc686978108f018a433fd9d491cae6d88a443506 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 07:40:31 +1000 Subject: [PATCH 117/298] fix _interaction_sample return doc --- activitysim/core/interaction_sample.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index c9a9f81b38..a4efe30a8f 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -279,12 +279,12 @@ def _interaction_sample( choices_df : pandas.DataFrame A DataFrame where index should match the index of the choosers DataFrame - and columns alt_col_name, prob, rand, pick_count + and columns alt_col_name, prob, pick_count + alt_col_name: int + the identifier of the alternatives prob: float the probability of the chosen alternative - rand: float - the rand that did the choosing pick_count : int number of duplicate picks for chooser, alt """ From 28142cdf2cbf63a70ec38b16454c14273659bcb6 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 07:59:04 +1000 Subject: [PATCH 118/298] fix bool conversion of None for eet compute settings in sub-components --- activitysim/core/configuration/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/configuration/base.py b/activitysim/core/configuration/base.py index b11643cdc3..320278bf1a 100644 --- a/activitysim/core/configuration/base.py +++ b/activitysim/core/configuration/base.py @@ -227,7 +227,7 @@ def eet_subcomponent(self, subcomponent: str) -> bool: if isinstance(self.use_explicit_error_terms, dict): return self.use_explicit_error_terms.get(subcomponent, None) else: - return bool(self.use_explicit_error_terms) + return self.use_explicit_error_terms @contextmanager def pandas_option_context(self): From 5de1d23903677e5c744c95517e2804b768df4717 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 08:29:10 +1000 Subject: [PATCH 119/298] compute settings in disaggreagte accessibility w/o hack --- activitysim/abm/models/disaggregate_accessibility.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 3882384a73..82c6d4249f 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -15,7 +15,7 @@ from activitysim.abm.models.util import tour_destination from activitysim.abm.tables import shadow_pricing from activitysim.core import estimation, los, tracing, util, workflow -from activitysim.core.configuration.base import PreprocessorSettings, PydanticReadable +from activitysim.core.configuration.base import PreprocessorSettings, PydanticReadable, ComputeSettings from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.expressions import assign_columns @@ -75,8 +75,7 @@ class DisaggregateAccessibilityAnnotateSettings(PydanticReadable, extra="forbid" annotate: PreprocessorSettings -# TODO-EET: add eet override for SOA MC sampling -class DisaggregateAccessibilitySettings(PydanticReadable): # , extra="forbid") +class DisaggregateAccessibilitySettings(PydanticReadable, extra="forbid"): suffixes: DisaggregateAccessibilitySuffixes = DisaggregateAccessibilitySuffixes() ORIGIN_SAMPLE_SIZE: float | int = 0 """ @@ -185,6 +184,8 @@ class DisaggregateAccessibilitySettings(PydanticReadable): # , extra="forbid") If not supplied or None, will default to the chunk size in the location choice model settings. """ + compute_settings: ComputeSettings | None = None + def read_disaggregate_accessibility_yaml( state: workflow.State, file_name @@ -770,6 +771,11 @@ def get_disaggregate_logsums( if disagg_model_settings.explicit_chunk is not None: model_settings.explicit_chunk = disagg_model_settings.explicit_chunk + # Can set compute settings for disaggregate accessibility + # Otherwise this will be set to whatever is in the location model settings + if disagg_model_settings.compute_settings is not None: + model_settings.compute_settings = disagg_model_settings.compute_settings + # Include the suffix tags to pass onto downstream logsum models (e.g., tour mode choice) if model_settings.LOGSUM_SETTINGS: suffixes = util.concat_suffix_dict(disagg_model_settings.suffixes) From 2a563ff1ea2b3e869c87022f17e5bf9455db35bb Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 08:39:50 +1000 Subject: [PATCH 120/298] lint --- activitysim/abm/models/disaggregate_accessibility.py | 6 +++++- activitysim/core/interaction_sample.py | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 82c6d4249f..87bdaffca3 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -15,7 +15,11 @@ from activitysim.abm.models.util import tour_destination from activitysim.abm.tables import shadow_pricing from activitysim.core import estimation, los, tracing, util, workflow -from activitysim.core.configuration.base import PreprocessorSettings, PydanticReadable, ComputeSettings +from activitysim.core.configuration.base import ( + PreprocessorSettings, + PydanticReadable, + ComputeSettings, +) from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.expressions import assign_columns diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index a4efe30a8f..89804730fc 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -545,11 +545,16 @@ def _interaction_sample( index_name = utilities.index.name choices_df = ( - pd.melt(utilities.reset_index(), id_vars=[index_name], value_name='prob', var_name=alt_col_name) + pd.melt( + utilities.reset_index(), + id_vars=[index_name], + value_name="prob", + var_name=alt_col_name, + ) .sort_values(by=index_name, kind="mergesort") .set_index(index_name) - .assign(prob = 1) - .assign(pick_count = 1) + .assign(prob=1) + .assign(pick_count=1) ) chunk_sizer.log_df(trace_label, "choices_df", choices_df) From ef0be214a901418e6a58c1eb946a5328bcaa7239 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 10:06:06 +1000 Subject: [PATCH 121/298] fix nest_spec is None bug --- activitysim/abm/models/joint_tour_participation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 80458ce27b..f8a0c3b755 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -449,7 +449,7 @@ def joint_tour_participation( # make_choices_explicit_error_term_nl this guard can be removed if state.settings.use_explicit_error_terms: assert ( - nest_spec is not None + nest_spec is None ), "Nested logit model custom chooser for EET requires name_mapping, currently not implemented in jtp" custom_chooser = participants_chooser From 1f815e82a78b042da88e85a0cb8daf81896f511f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 10:24:21 +1000 Subject: [PATCH 122/298] remove comment --- activitysim/abm/models/util/cdap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index e2f5598207..848200097c 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -1035,7 +1035,7 @@ def household_activity_choices( "%s.hhsize%d_activity_choices" % (trace_label, hhsize), column_labels=["expression", "household"], ) - # TODO-EET: check if different num rands matters here + state.tracing.trace_df( rands, "%s.hhsize%d_rands" % (trace_label, hhsize), columns=[None, "rand"] ) From 41d49ffd3d7eff90420708abd6927937e20f456c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 10:39:59 +1000 Subject: [PATCH 123/298] clean up comments --- activitysim/core/interaction_sample.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 89804730fc..c84fd2c4d1 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -43,10 +43,6 @@ def make_sample_choices_utility_based( assert isinstance(alternatives, pd.DataFrame) assert len(alternatives) == alternative_count - # TODO-EET [janzill Jun2022]: this needs for loop for memory like previous method, an array of dimension - # (len(choosers), alternative_count, sample_size) can get very large - # choices = np.zeros_like(utilities, dtype=np.uint32) - # zero_dim_index = np.arange(utilities.shape[0]) utils_array = utilities.to_numpy() chunk_sizer.log_df(trace_label, "utils_array", utils_array) chosen_destinations = [] @@ -54,13 +50,10 @@ def make_sample_choices_utility_based( rands = state.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) chunk_sizer.log_df(trace_label, "rands", rands) + # TODO-EET [janzill Jun2022]: using for-loop to keep memory usage low, an array of dimension + # (len(choosers), alternative_count, sample_size) can get very large. Probably better to + # use chunking for this. for i in range(sample_size): - # rands = pipeline.get_rn_generator().random_for_df(utilities, n=alternative_count) - # choices[zero_dim_index, np.argmax(inverse_ev1_cdf(rands) + utils_array, axis=1)] += 1 - # choices[ - # zero_dim_index, - # np.argmax(pipeline.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + utils_array, axis=1) - # ] += 1 # created this once for memory logging if i > 0: rands = state.get_rn_generator().gumbel_for_df( @@ -77,10 +70,6 @@ def make_sample_choices_utility_based( chunk_sizer.log_df(trace_label, "rands", None) chooser_idx = np.tile(np.arange(utilities.shape[0]), sample_size) - # chunk.log_df(trace_label, 'choices_array', choices_array) - # choices array has same dim as utilities, with values indicating number of counts per chooser and alternative - # let's turn the nonzero values into a dataframe - # i, j = np.nonzero(choices_array) chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) probs = logit.utils_to_probs( @@ -95,7 +84,6 @@ def make_sample_choices_utility_based( choices_df = pd.DataFrame( { alt_col_name: alternatives.index.values[chosen_destinations], - # "pick_count": choices_array[i, j], "prob": probs.to_numpy()[chooser_idx, chosen_destinations], choosers.index.name: choosers.index.values[chooser_idx], } From 582c998f516ab3e26fa888a6de7fd8282d945926 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 26 May 2025 10:52:14 +1000 Subject: [PATCH 124/298] comment clean up --- activitysim/core/logit.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 8bec1b910d..5202a8cf44 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -270,11 +270,11 @@ def utils_to_probs( return probs -# TODO-EET: check state, add type annotations, check new-world tracing, etc. +# TODO-EET: add doc string, tracing def add_ev1_random(state: workflow.State, df: pd.DataFrame): nest_utils_for_choice = df.copy() nest_utils_for_choice += state.get_rn_generator().gumbel_for_df( - nest_utils_for_choice, n=df.shape[1] + nest_utils_for_choice, n=nest_utils_for_choice.shape[1] ) return nest_utils_for_choice @@ -293,8 +293,9 @@ def choose_from_tree( raise ValueError("This should never happen - no alternative found") +# TODO-EET: add doc string, tracing def make_choices_explicit_error_term_nl( - state, nested_utilities, alt_order_array, nest_spec + state, nested_utilities, alt_order_array, nest_spec, trace_label ): """walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" nest_utils_for_choice = add_ev1_random(state, nested_utilities) @@ -315,7 +316,7 @@ def make_choices_explicit_error_term_nl( axis=1, ) # TODO-EET: reporting like for zero probs - assert not choices.isnull().any(), "No choice for XXX - implement reporting" + assert not choices.isnull().any(), f"No choice for {trace_label}" choices = pd.Series(choices, index=nest_utils_for_choice.index) # In order for choice indexing to be consistent with MNL and cumsum MC choices, we need to index in the order @@ -325,11 +326,12 @@ def make_choices_explicit_error_term_nl( return choices -def make_choices_explicit_error_term_mnl(state, utilities): +# TODO-EET: add doc string, tracing +def make_choices_explicit_error_term_mnl(state, utilities, trace_label): utilities_incl_unobs = add_ev1_random(state, utilities) choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) # TODO-EET: reporting like for zero probs - assert not np.isnan(choices).any(), "No choice for XXX - implement reporting" + assert not np.isnan(choices).any(), f"No choice for {trace_label}" choices = pd.Series(choices, index=utilities_incl_unobs.index) return choices @@ -337,17 +339,16 @@ def make_choices_explicit_error_term_mnl(state, utilities): def make_choices_explicit_error_term( state, utilities, alt_order_array, nest_spec=None, trace_label=None ): - trace_label = tracing.extend_trace_label(trace_label, "make_choices_ru_frozen") + trace_label = tracing.extend_trace_label(trace_label, "make_choices_eet") if nest_spec is None: - choices = make_choices_explicit_error_term_mnl(state, utilities) + choices = make_choices_explicit_error_term_mnl(state, utilities, trace_label) else: choices = make_choices_explicit_error_term_nl( - state, utilities, alt_order_array, nest_spec + state, utilities, alt_order_array, nest_spec, trace_label ) return choices -# TODO-EET: memory logging def make_choices_utility_based( state: workflow.State, utilities: pd.DataFrame, From c40b989f717176f227c4705d09d50412899b49dd Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 29 May 2025 20:55:47 +1000 Subject: [PATCH 125/298] avoid choosing alternatives corresponding to padded utilities in interaction_sample_simulate --- activitysim/core/interaction_sample_simulate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 1ca92481e2..f45a0c1a01 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -262,7 +262,10 @@ def _interaction_sample_simulate( chunk_sizer.log_df(trace_label, "sample_counts", None) # insert the zero-prob utilities to pad each alternative set to same size - padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999) + # -999 is not small enough for EET, there are edge cases where alternatives + # corresponding to padded utilities are chosen. TODO: proper zero-prob handling. + zero_probability_util = -999999 # min(interaction_utilities.utility.min().min() - 100, -999) + padded_utilities = np.insert(interaction_utilities.utility.values, inserts, zero_probability_util) chunk_sizer.log_df(trace_label, "padded_utilities", padded_utilities) del inserts From 5636cd9025a012c2b53891234309949cdb11885d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 29 May 2025 21:09:25 +1000 Subject: [PATCH 126/298] lint --- activitysim/core/interaction_sample_simulate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index f45a0c1a01..f506f43316 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -264,8 +264,12 @@ def _interaction_sample_simulate( # insert the zero-prob utilities to pad each alternative set to same size # -999 is not small enough for EET, there are edge cases where alternatives # corresponding to padded utilities are chosen. TODO: proper zero-prob handling. - zero_probability_util = -999999 # min(interaction_utilities.utility.min().min() - 100, -999) - padded_utilities = np.insert(interaction_utilities.utility.values, inserts, zero_probability_util) + zero_probability_util = ( + -999999 + ) # min(interaction_utilities.utility.min().min() - 100, -999) + padded_utilities = np.insert( + interaction_utilities.utility.values, inserts, zero_probability_util + ) chunk_sizer.log_df(trace_label, "padded_utilities", padded_utilities) del inserts From 94e629ace077e64fbfa9db74da89c8ca53e7ec03 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 29 May 2025 22:10:43 +1000 Subject: [PATCH 127/298] patch other instance of padded_utility for eet --- activitysim/abm/models/trip_departure_choice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/abm/models/trip_departure_choice.py b/activitysim/abm/models/trip_departure_choice.py index 4a33d9635a..7dc3353320 100644 --- a/activitysim/abm/models/trip_departure_choice.py +++ b/activitysim/abm/models/trip_departure_choice.py @@ -314,7 +314,7 @@ def choose_tour_leg_pattern( chunk_sizer.log_df(trace_label, "sample_counts", None) # insert the zero-prob utilities to pad each alternative set to same size - padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999) + padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999999) del inserts del interaction_utilities From e102a9e54dbfb9e3dc663c4da06cde840fd42a86 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 30 May 2025 09:17:47 +1000 Subject: [PATCH 128/298] adds validate utility method to align with unavailable choices in MC simulation --- .../core/interaction_sample_simulate.py | 29 ++++++-- activitysim/core/logit.py | 69 +++++++++++++++++++ 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index f506f43316..06efa3fb1b 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -303,11 +303,32 @@ def _interaction_sample_simulate( if skip_choice: return choosers.join(logsums.to_frame("logsums")) + utilities_df = logit.validate_utils( + state, + utilities_df, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + trace_choosers=choosers, + ) + + if allow_zero_probs: + zero_probs = ( + utilities_df.sum(axis=1) + <= utilities_df.shape[1] * logit.UTIL_UNAVAILABLE + ) + if zero_probs.any(): + # copied from proabability below, fix when that gets fixed + # FIXME this is kind of gnarly, but we force choice of first alt + utilities_df.loc[ + zero_probs, 0 + ] = 3.0 # arbitrary value much larger than UTIL_UNAVAILABLE + # positions is series with the chosen alternative represented as a column index in utilities_df # which is an integer between zero and num alternatives in the alternative sample positions, rands = logit.make_choices_utility_based( state, utilities_df, trace_label=trace_label, trace_choosers=choosers ) + del utilities_df chunk_sizer.log_df(trace_label, "utilities_df", None) else: @@ -382,13 +403,7 @@ def _interaction_sample_simulate( chunk_sizer.log_df(trace_label, "choices", choices) - # order is important for short circuiting - no explicit error terms => no zero_probs - if ( - allow_zero_probs - and not state.settings.use_explicit_error_terms - and zero_probs.any() - and zero_prob_choice_val is not None - ): + if allow_zero_probs and zero_probs.any() and zero_prob_choice_val is not None: # FIXME this is kind of gnarly, patch choice for zero_probs choices.loc[zero_probs] = zero_prob_choice_val diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 5202a8cf44..0162f0b8c8 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -17,6 +17,11 @@ EXP_UTIL_MIN = 1e-300 EXP_UTIL_MAX = np.inf +# TODO-EET: Figure out what type we want UTIL_MIN to be, currently np.float64 +UTIL_MIN = np.log(EXP_UTIL_MIN, dtype=np.float64) +UTIL_UNAVAILABLE = 1000.0 * (UTIL_MIN - 1.0) + + PROB_MIN = 0.0 PROB_MAX = 1.0 @@ -123,6 +128,70 @@ def utils_to_logsums(utils, exponentiated=False, allow_zero_probs=False): return logsums +def validate_utils( + state: workflow.State, + utils, + trace_label=None, + allow_zero_probs=False, + trace_choosers=None, +): + """ + Validate utilities to ensure non-available choices are treated the same in EET and MC. + For EET decisions, no conversion to probabilities is required because choices + are made on the basis of comparing utilities (only differences matter). + However, large negative utility values are used in practice to make choices + unavailable based on probability calculations, which boils down to evaluating + exp(utility). We here use this to define a minimum utility that corresponds + to an unavailable choice. + + Parameters + ---------- + utils : pandas.DataFrame + Rows should be choosers and columns should be alternatives. + + trace_label : str, optional + label for tracing bad utility or probability values + + allow_zero_probs : bool + if True value rows in which all utility alts are UTIL_MIN will be set to + UTIL_UNAVAILABLE. + + trace_choosers : pandas.dataframe + the choosers df (for interaction_simulate) to facilitate the reporting of hh_id + by report_bad_choices because it can't deduce hh_id from the interaction_dataset + which is indexed on index values from alternatives df + + Returns + ------- + utils : pandas.DataFrame + utils with values that would lead to zero probability replaced by UTIL_UNAVAILABLE + + """ + trace_label = tracing.extend_trace_label(trace_label, "validate_utils") + + utils_arr = utils.values + + np.putmask(utils_arr, utils_arr <= UTIL_MIN, UTIL_UNAVAILABLE) + + arr_sum = utils_arr.sum(axis=1) + + if not allow_zero_probs: + zero_probs = arr_sum <= utils_arr.shape[1] * UTIL_UNAVAILABLE + if zero_probs.any(): + report_bad_choices( + state, + zero_probs, + utils, + trace_label=tracing.extend_trace_label(trace_label, "zero_prob_utils"), + msg="all probabilities are zero", + trace_choosers=trace_choosers, + ) + + utils = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index) + + return utils + + def utils_to_probs( state: workflow.State, utils, From 66cdac906395d70e52ab2970e3fb5af86b59dfcb Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 30 May 2025 10:23:42 +1000 Subject: [PATCH 129/298] consistently treat utilities that would lead to zero choices in MC simulation --- .../abm/models/joint_tour_participation.py | 11 ++---- .../abm/models/trip_departure_choice.py | 5 ++- activitysim/abm/models/util/cdap.py | 2 + activitysim/core/interaction_sample.py | 22 +++++++++++ .../core/interaction_sample_simulate.py | 9 +---- activitysim/core/interaction_simulate.py | 3 ++ activitysim/core/pathbuilder.py | 37 +++++++++++++------ activitysim/core/simulate.py | 7 ++++ activitysim/core/test/test_logit.py | 1 + 9 files changed, 69 insertions(+), 28 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index f8a0c3b755..f57fb176f3 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -213,19 +213,16 @@ def participants_chooser( ) # anybody with probability > 0 is forced to join the joint tour if state.settings.use_explicit_error_terms: - # need "is valid choice", which requires minimum probability. This depends - # on all other utilities, and we need to make sure that we set values such that we - # certainly choose those with non-zero values, and do not choose other ones. Let's - # use -999 as zero prob choice and 10 as definitive choice (based on diff and std gumbel). - probs_from_utils = logit.utils_to_probs(state, probs_or_utils) + # need "is valid choice" such that we certainly choose those with non-zero values, + # and do not choose others. Let's use 3.0 as large value here. probs_or_utils[choice_col] = np.where( - probs_from_utils[choice_col] > 0, 10, -999 + probs_or_utils[choice_col] > logit.UTIL_MIN, 3.0, logit.UTIL_UNAVAILABLE ) non_choice_col = [ col for col in probs_or_utils.columns if col != choice_col ][0] probs_or_utils[non_choice_col] = np.where( - probs_or_utils[choice_col] == -999, 10, -999 + probs_or_utils[choice_col] <= logit.UTIL_MIN, 3.0, logit.UTIL_UNAVAILABLE ) else: probs_or_utils[choice_col] = np.where( diff --git a/activitysim/abm/models/trip_departure_choice.py b/activitysim/abm/models/trip_departure_choice.py index 7dc3353320..65a4468a62 100644 --- a/activitysim/abm/models/trip_departure_choice.py +++ b/activitysim/abm/models/trip_departure_choice.py @@ -314,7 +314,7 @@ def choose_tour_leg_pattern( chunk_sizer.log_df(trace_label, "sample_counts", None) # insert the zero-prob utilities to pad each alternative set to same size - padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999999) + padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999) del inserts del interaction_utilities @@ -339,6 +339,9 @@ def choose_tour_leg_pattern( ) if state.settings.use_explicit_error_terms: + utilities_df = logit.validate_utils( + state, utilities_df, trace_label=trace_label, trace_choosers=trip_segment + ) # make choices # positions is series with the chosen alternative represented as a column index in probs # which is an integer between zero and num alternatives in the alternative sample diff --git a/activitysim/abm/models/util/cdap.py b/activitysim/abm/models/util/cdap.py index 848200097c..71aca621de 100644 --- a/activitysim/abm/models/util/cdap.py +++ b/activitysim/abm/models/util/cdap.py @@ -996,6 +996,8 @@ def household_activity_choices( utils = utils.add(joint_tour_utils) if state.settings.use_explicit_error_terms: + utils = logit.validate_utils(state, utils, trace_label=trace_label) + idx_choices, rands = logit.make_choices_utility_based( state, utils, trace_label=trace_label ) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index c84fd2c4d1..60dfa64b01 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -43,6 +43,20 @@ def make_sample_choices_utility_based( assert isinstance(alternatives, pd.DataFrame) assert len(alternatives) == alternative_count + if allow_zero_probs: + zero_probs = ( + utilities.sum(axis=1) + <= utilities.shape[1] * logit.UTIL_UNAVAILABLE + ) + if zero_probs.all(): + return pd.DataFrame( + columns=[alt_col_name, "rand", "prob", choosers.index.name] + ) + if zero_probs.any(): + # remove from sample + utilities = utilities[~zero_probs] + choosers = choosers[~zero_probs] + utils_array = utilities.to_numpy() chunk_sizer.log_df(trace_label, "utils_array", utils_array) chosen_destinations = [] @@ -556,6 +570,14 @@ def _interaction_sample( return choices_df if use_eet: + utilities = logit.validate_utils( + state, + utilities, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + trace_choosers=choosers, + ) + choices_df = make_sample_choices_utility_based( state, choosers, diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 06efa3fb1b..0914334276 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -262,14 +262,7 @@ def _interaction_sample_simulate( chunk_sizer.log_df(trace_label, "sample_counts", None) # insert the zero-prob utilities to pad each alternative set to same size - # -999 is not small enough for EET, there are edge cases where alternatives - # corresponding to padded utilities are chosen. TODO: proper zero-prob handling. - zero_probability_util = ( - -999999 - ) # min(interaction_utilities.utility.min().min() - 100, -999) - padded_utilities = np.insert( - interaction_utilities.utility.values, inserts, zero_probability_util - ) + padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999) chunk_sizer.log_df(trace_label, "padded_utilities", padded_utilities) del inserts diff --git a/activitysim/core/interaction_simulate.py b/activitysim/core/interaction_simulate.py index 6867c2aebe..4aa3166d87 100644 --- a/activitysim/core/interaction_simulate.py +++ b/activitysim/core/interaction_simulate.py @@ -883,6 +883,9 @@ def _interaction_simulate( state.tracing.dump_df(DUMP, utilities, trace_label, "utilities") if state.settings.use_explicit_error_terms: + utilities = logit.validate_utils( + state, utilities, trace_label=trace_label, trace_choosers=choosers + ) positions, rands = logit.make_choices_utility_based( state, utilities, trace_label=trace_label, trace_choosers=choosers ) diff --git a/activitysim/core/pathbuilder.py b/activitysim/core/pathbuilder.py index bc0517df36..6ca6f7bb48 100644 --- a/activitysim/core/pathbuilder.py +++ b/activitysim/core/pathbuilder.py @@ -991,16 +991,16 @@ def build_virtual_path( utilities_df.index = orig.index with memo("#TVPB build_virtual_path make_choices"): - probs = logit.utils_to_probs( - self.network_los.state, - utilities_df, - allow_zero_probs=True, - trace_label=trace_label, - overflow_protection=False, - ) - chunk_sizer.log_df(trace_label, "probs", probs) - if trace: + probs = logit.utils_to_probs( + self.network_los.state, + utilities_df, + allow_zero_probs=True, + trace_label=trace_label, + overflow_protection=False, + ) + chunk_sizer.log_df(trace_label, "probs", probs) + choices = override_choices utilities_df["choices"] = choices @@ -1008,28 +1008,41 @@ def build_virtual_path( probs["choices"] = choices self.trace_df(probs, trace_label, "probs") + del probs + chunk_sizer.log_df(trace_label, "probs", None) else: if self.network_los.state.settings.use_explicit_error_terms: + utilities_df = logit.validate_utils( + self.network_los.state, utilities_df, allow_zero_probs=True, trace_label=trace_label + ) choices, rands = logit.make_choices_utility_based( self.network_los.state, utilities_df, - allow_bad_probs=True, trace_label=trace_label, ) else: + probs = logit.utils_to_probs( + self.network_los.state, + utilities_df, + allow_zero_probs=True, + trace_label=trace_label, + overflow_protection=False, + ) + chunk_sizer.log_df(trace_label, "probs", probs) + choices, rands = logit.make_choices( self.network_los.state, probs, allow_bad_probs=True, trace_label=trace_label, ) + del probs + chunk_sizer.log_df(trace_label, "probs", None) chunk_sizer.log_df(trace_label, "rands", rands) del rands chunk_sizer.log_df(trace_label, "rands", None) - del probs - chunk_sizer.log_df(trace_label, "probs", None) # we need to get path_set, btap, atap from path_df row with same seq and path_num # drop seq join column, but keep path_num of choice to override_choices when tracing diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index afb74c09dd..5baacb25e8 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1290,6 +1290,8 @@ def eval_mnl( ) if state.settings.use_explicit_error_terms: + utilities = logit.validate_utils(state, utilities, trace_label=trace_label, trace_choosers=choosers) + if custom_chooser: choices, rands = custom_chooser( state, utilities, choosers, spec, trace_label @@ -1427,6 +1429,11 @@ def eval_nl( ) if state.settings.use_explicit_error_terms: + # TODO-EET: Nested utility zero choice probability + raw_utilities = logit.validate_utils( + state, raw_utilities, allow_zero_probs=True, trace_label=trace_label + ) + # utilities of leaves and nests nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) chunk_sizer.log_df(trace_label, "nested_utilities", nested_utilities) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index e249475de2..eb8f7587cf 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -69,6 +69,7 @@ def utilities(choosers, spec, test_data): columns=test_data["probabilities"].columns, ) +# TODO-EET: Add tests here! def test_utils_to_probs(utilities, test_data): state = workflow.State().default_settings() From 3b48a076636cd0afc6d1ae19c0f7de6fa4638661 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 30 May 2025 10:26:11 +1000 Subject: [PATCH 130/298] lint --- activitysim/abm/models/joint_tour_participation.py | 8 ++++++-- activitysim/core/interaction_sample.py | 3 +-- activitysim/core/pathbuilder.py | 6 ++++-- activitysim/core/simulate.py | 4 +++- activitysim/core/test/test_logit.py | 2 ++ 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index f57fb176f3..3844a3f217 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -216,13 +216,17 @@ def participants_chooser( # need "is valid choice" such that we certainly choose those with non-zero values, # and do not choose others. Let's use 3.0 as large value here. probs_or_utils[choice_col] = np.where( - probs_or_utils[choice_col] > logit.UTIL_MIN, 3.0, logit.UTIL_UNAVAILABLE + probs_or_utils[choice_col] > logit.UTIL_MIN, + 3.0, + logit.UTIL_UNAVAILABLE, ) non_choice_col = [ col for col in probs_or_utils.columns if col != choice_col ][0] probs_or_utils[non_choice_col] = np.where( - probs_or_utils[choice_col] <= logit.UTIL_MIN, 3.0, logit.UTIL_UNAVAILABLE + probs_or_utils[choice_col] <= logit.UTIL_MIN, + 3.0, + logit.UTIL_UNAVAILABLE, ) else: probs_or_utils[choice_col] = np.where( diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 60dfa64b01..886b010e03 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -45,8 +45,7 @@ def make_sample_choices_utility_based( if allow_zero_probs: zero_probs = ( - utilities.sum(axis=1) - <= utilities.shape[1] * logit.UTIL_UNAVAILABLE + utilities.sum(axis=1) <= utilities.shape[1] * logit.UTIL_UNAVAILABLE ) if zero_probs.all(): return pd.DataFrame( diff --git a/activitysim/core/pathbuilder.py b/activitysim/core/pathbuilder.py index 6ca6f7bb48..da241addcd 100644 --- a/activitysim/core/pathbuilder.py +++ b/activitysim/core/pathbuilder.py @@ -1013,7 +1013,10 @@ def build_virtual_path( else: if self.network_los.state.settings.use_explicit_error_terms: utilities_df = logit.validate_utils( - self.network_los.state, utilities_df, allow_zero_probs=True, trace_label=trace_label + self.network_los.state, + utilities_df, + allow_zero_probs=True, + trace_label=trace_label, ) choices, rands = logit.make_choices_utility_based( self.network_los.state, @@ -1043,7 +1046,6 @@ def build_virtual_path( del rands chunk_sizer.log_df(trace_label, "rands", None) - # we need to get path_set, btap, atap from path_df row with same seq and path_num # drop seq join column, but keep path_num of choice to override_choices when tracing columns_to_cache = ["btap", "atap", "path_set", "path_num"] diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 5baacb25e8..651db117c5 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1290,7 +1290,9 @@ def eval_mnl( ) if state.settings.use_explicit_error_terms: - utilities = logit.validate_utils(state, utilities, trace_label=trace_label, trace_choosers=choosers) + utilities = logit.validate_utils( + state, utilities, trace_label=trace_label, trace_choosers=choosers + ) if custom_chooser: choices, rands = custom_chooser( diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index eb8f7587cf..c82606981f 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -69,8 +69,10 @@ def utilities(choosers, spec, test_data): columns=test_data["probabilities"].columns, ) + # TODO-EET: Add tests here! + def test_utils_to_probs(utilities, test_data): state = workflow.State().default_settings() probs = logit.utils_to_probs(state, utilities, trace_label=None) From 6f31c76dbb608468b0dfd86a0253827fca968c8b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 30 May 2025 13:11:31 +1000 Subject: [PATCH 131/298] fix allow_zero_probs for prob calculation in EET interaction_sample --- activitysim/core/interaction_sample.py | 1 + 1 file changed, 1 insertion(+) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 886b010e03..47548e442d 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -90,6 +90,7 @@ def make_sample_choices_utility_based( utilities, allow_zero_probs=allow_zero_probs, trace_label=trace_label, + overflow_protection=not allow_zero_probs, trace_choosers=choosers, ) chunk_sizer.log_df(trace_label, "probs", probs) From 57ba5bc1943fddf40ad78e63000810d1636fb78d Mon Sep 17 00:00:00 2001 From: Jeff Newman Date: Mon, 9 Mar 2026 13:50:26 -0500 Subject: [PATCH 132/298] fix unbound variable in estimation mode --- activitysim/core/interaction_sample.py | 74 ++++++++++++++------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 7c009bd29d..439389060e 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -572,6 +572,10 @@ def _interaction_sample( return choices_df if use_eet: + + if estimation.manager.enabled: + raise ValueError("cannot use explicit error terms with estimation mode at this time") + utilities = logit.validate_utils( state, utilities, @@ -630,43 +634,43 @@ def _interaction_sample( chunk_sizer=chunk_sizer, ) - del probs - chunk_sizer.log_df(trace_label, "probs", None) - - if estimation.manager.enabled and sample_size > 0: - # we need to ensure chosen alternative is included in the sample - survey_choices = estimation.manager.get_survey_destination_choices( - state, choosers, trace_label - ) - if survey_choices is not None: - assert ( - survey_choices.index == choosers.index - ).all(), "survey_choices and choosers must have the same index" - survey_choices.name = alt_col_name - survey_choices = survey_choices.dropna().astype( - choices_df[alt_col_name].dtype + if estimation.manager.enabled and sample_size > 0: + # we need to ensure chosen alternative is included in the sample + survey_choices = estimation.manager.get_survey_destination_choices( + state, choosers, trace_label ) + if survey_choices is not None: + assert ( + survey_choices.index == choosers.index + ).all(), "survey_choices and choosers must have the same index" + survey_choices.name = alt_col_name + survey_choices = survey_choices.dropna().astype( + choices_df[alt_col_name].dtype + ) - # merge all survey choices onto choices_df - probs_df = probs.reset_index().melt( - id_vars=[choosers.index.name], - var_name=alt_col_name, - value_name="prob", - ) - # probs are numbered 0..n-1 so we need to map back to alt ids - zone_map = pd.Series(alternatives.index).to_dict() - probs_df[alt_col_name] = probs_df[alt_col_name].map(zone_map) - - survey_choices = pd.merge( - survey_choices, - probs_df, - on=[choosers.index.name, alt_col_name], - how="left", - ) - survey_choices["rand"] = 0 - survey_choices["prob"].fillna(0, inplace=True) - choices_df = pd.concat([choices_df, survey_choices], ignore_index=True) - choices_df.sort_values(by=[choosers.index.name], inplace=True) + # merge all survey choices onto choices_df + probs_df = probs.reset_index().melt( + id_vars=[choosers.index.name], + var_name=alt_col_name, + value_name="prob", + ) + # probs are numbered 0..n-1 so we need to map back to alt ids + zone_map = pd.Series(alternatives.index).to_dict() + probs_df[alt_col_name] = probs_df[alt_col_name].map(zone_map) + + survey_choices = pd.merge( + survey_choices, + probs_df, + on=[choosers.index.name, alt_col_name], + how="left", + ) + survey_choices["rand"] = 0 + survey_choices["prob"].fillna(0, inplace=True) + choices_df = pd.concat([choices_df, survey_choices], ignore_index=True) + choices_df.sort_values(by=[choosers.index.name], inplace=True) + + del probs + chunk_sizer.log_df(trace_label, "probs", None) chunk_sizer.log_df(trace_label, "choices_df", choices_df) From 45ab2ca40632d9d3c1ff78bc680606bcecc3cf75 Mon Sep 17 00:00:00 2001 From: Jeff Newman Date: Mon, 9 Mar 2026 14:02:37 -0500 Subject: [PATCH 133/298] blacken --- .pre-commit-config.yaml | 3 +++ activitysim/core/interaction_sample.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41fecfab5a..12d970e189 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,9 @@ repos: rev: 22.6.0 hooks: - id: black + language_version: python3.13 + # explicitly set the Python version for the hook environment for black, + # as older versions of black don't work with the latest Python. #- repo: https://github.com/PyCQA/flake8 # rev: 5.0.4 diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 439389060e..93834c690a 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -18,9 +18,9 @@ workflow, ) from activitysim.core.configuration.base import ComputeSettings +from activitysim.core.exceptions import SegmentedSpecificationError from activitysim.core.skim_dataset import DatasetWrapper from activitysim.core.skim_dictionary import SkimWrapper -from activitysim.core.exceptions import SegmentedSpecificationError logger = logging.getLogger(__name__) @@ -574,7 +574,9 @@ def _interaction_sample( if use_eet: if estimation.manager.enabled: - raise ValueError("cannot use explicit error terms with estimation mode at this time") + raise ValueError( + "cannot use explicit error terms with estimation mode at this time" + ) utilities = logit.validate_utils( state, From 82f1f7d6477960870a2a9e233db5af27b6f0564f Mon Sep 17 00:00:00 2001 From: Jeff Newman Date: Thu, 12 Mar 2026 23:01:52 -0500 Subject: [PATCH 134/298] update larch pin to 6.0.46 --- pyproject.toml | 2 +- uv.lock | 110 ++++++++++--------------------------------------- 2 files changed, 22 insertions(+), 90 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a8fef9948e..8d6496c032 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,7 +118,7 @@ dev = [ "isort==5.12.0", "jupyter-book", "jupyterlab", - "larch>=6.0.42", + "larch>=6.0.46", "matplotlib", "myst-nb", "myst-parser", # allows markdown in sphinx diff --git a/uv.lock b/uv.lock index f1ffb126df..f890e21e14 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14'", @@ -145,7 +145,7 @@ dev = [ { name = "isort", specifier = "==5.12.0" }, { name = "jupyter-book" }, { name = "jupyterlab" }, - { name = "larch", specifier = ">=6.0.42" }, + { name = "larch", specifier = ">=6.0.46" }, { name = "matplotlib" }, { name = "myst-nb" }, { name = "myst-parser" }, @@ -1663,6 +1663,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, { url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, { url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/f1/29/74242b7d72385e29bcc5563fba67dad94943d7cd03552bac320d597f29b2/greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7", size = 1544904, upload-time = "2025-11-04T12:42:04.763Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e2/1572b8eeab0f77df5f6729d6ab6b141e4a84ee8eb9bc8c1e7918f94eda6d/greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", size = 1611228, upload-time = "2025-11-04T12:42:08.423Z" }, { url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, @@ -1672,6 +1674,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, + { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, @@ -1681,6 +1685,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, @@ -1690,6 +1696,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, @@ -1697,6 +1705,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, ] @@ -2321,7 +2331,7 @@ wheels = [ [[package]] name = "larch" -version = "6.0.42" +version = "6.0.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "addicty" }, @@ -2337,15 +2347,15 @@ dependencies = [ { name = "pandas", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "pyarrow" }, { name = "rich" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy" }, + { name = "sharrow" }, { name = "sparse" }, { name = "xarray" }, { name = "xmle" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/3c/0db11f6fbe7dcf2edf2c2843f4b74f03d42f3f607a20e774bf3ec74d3467/larch-6.0.42.tar.gz", hash = "sha256:09c76404a3a91505acd04902743a156ad5b84697d3c69796452d3852a4b2f2dd", size = 2153168, upload-time = "2025-09-28T22:58:23.784Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/0d/6274565f53dba31cfc5af5033e95e64261baee7352d82f67804d49d330ce/larch-6.0.46.tar.gz", hash = "sha256:be78a4196a6c5cbe6300ef6d7718406fab545799d8262af18f6f20021115cb5c", size = 2101313, upload-time = "2026-03-13T01:14:51.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/a9/2bd3ea4b0c4297b06db04553709bd7d3a01c0e8847a621832d3f1cec8fec/larch-6.0.42-py3-none-any.whl", hash = "sha256:5d9cf582c9c0b293eca15ddb1859e516ad89cdb33c9509b447f7c137b97c647c", size = 1851908, upload-time = "2025-09-28T22:58:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f5/f875dafd1da4e80157783800ecf9548b2259cd287fe10f9bf1b2a65f5269/larch-6.0.46-py3-none-any.whl", hash = "sha256:dccbb0f014bd5dc53fef58957801e06da2f06afb0a90c5f6ef60b903b245f7f6", size = 1740545, upload-time = "2026-03-13T01:14:49.644Z" }, ] [[package]] @@ -4730,6 +4740,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/fa/3234f913fe9a6525a7b97c6dad1f51e72b917e6872e051a5e2ffd8b16fbb/ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83", size = 137970, upload-time = "2025-09-22T19:51:09.472Z" }, { url = "https://files.pythonhosted.org/packages/ef/ec/4edbf17ac2c87fa0845dd366ef8d5852b96eb58fcd65fc1ecf5fe27b4641/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27", size = 739639, upload-time = "2025-09-22T19:51:10.566Z" }, { url = "https://files.pythonhosted.org/packages/15/18/b0e1fafe59051de9e79cdd431863b03593ecfa8341c110affad7c8121efc/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640", size = 764456, upload-time = "2025-09-22T19:51:11.736Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cd/150fdb96b8fab27fe08d8a59fe67554568727981806e6bc2677a16081ec7/ruamel_yaml_clib-0.2.14-cp314-cp314-win32.whl", hash = "sha256:9b4104bf43ca0cd4e6f738cb86326a3b2f6eef00f417bd1e7efb7bdffe74c539", size = 102394, upload-time = "2025-11-14T21:57:36.703Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/a3fa40084558c7e1dc9546385f22a93949c890a8b2e445b2ba43935f51da/ruamel_yaml_clib-0.2.14-cp314-cp314-win_amd64.whl", hash = "sha256:13997d7d354a9890ea1ec5937a219817464e5cc344805b37671562a401ca3008", size = 122673, upload-time = "2025-11-14T21:57:38.177Z" }, ] [[package]] @@ -4777,8 +4789,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, { name = "numpy" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy" }, { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } @@ -4819,11 +4830,8 @@ wheels = [ name = "scipy" version = "1.15.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] dependencies = [ - { name = "numpy", marker = "python_full_version < '3.11'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -4874,82 +4882,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, ] -[[package]] -name = "scipy" -version = "1.16.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.12' and python_full_version < '3.14'", - "python_full_version == '3.11.*'", -] -dependencies = [ - { name = "numpy", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92", size = 36619956, upload-time = "2025-09-11T17:39:20.5Z" }, - { url = "https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e", size = 28931117, upload-time = "2025-09-11T17:39:29.06Z" }, - { url = "https://files.pythonhosted.org/packages/80/d1/eed51ab64d227fe60229a2d57fb60ca5898cfa50ba27d4f573e9e5f0b430/scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173", size = 20921997, upload-time = "2025-09-11T17:39:34.892Z" }, - { url = "https://files.pythonhosted.org/packages/be/7c/33ea3e23bbadde96726edba6bf9111fb1969d14d9d477ffa202c67bec9da/scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d", size = 23523374, upload-time = "2025-09-11T17:39:40.846Z" }, - { url = "https://files.pythonhosted.org/packages/96/0b/7399dc96e1e3f9a05e258c98d716196a34f528eef2ec55aad651ed136d03/scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2", size = 33583702, upload-time = "2025-09-11T17:39:49.011Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9", size = 35883427, upload-time = "2025-09-11T17:39:57.406Z" }, - { url = "https://files.pythonhosted.org/packages/ab/66/e25705ca3d2b87b97fe0a278a24b7f477b4023a926847935a1a71488a6a6/scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3", size = 36212940, upload-time = "2025-09-11T17:40:06.013Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fd/0bb911585e12f3abdd603d721d83fc1c7492835e1401a0e6d498d7822b4b/scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88", size = 38865092, upload-time = "2025-09-11T17:40:15.143Z" }, - { url = "https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa", size = 38687626, upload-time = "2025-09-11T17:40:24.041Z" }, - { url = "https://files.pythonhosted.org/packages/68/72/02f37316adf95307f5d9e579023c6899f89ff3a051fa079dbd6faafc48e5/scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c", size = 25503506, upload-time = "2025-09-11T17:40:30.703Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8d/6396e00db1282279a4ddd507c5f5e11f606812b608ee58517ce8abbf883f/scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d", size = 36646259, upload-time = "2025-09-11T17:40:39.329Z" }, - { url = "https://files.pythonhosted.org/packages/3b/93/ea9edd7e193fceb8eef149804491890bde73fb169c896b61aa3e2d1e4e77/scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371", size = 28888976, upload-time = "2025-09-11T17:40:46.82Z" }, - { url = "https://files.pythonhosted.org/packages/91/4d/281fddc3d80fd738ba86fd3aed9202331180b01e2c78eaae0642f22f7e83/scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0", size = 20879905, upload-time = "2025-09-11T17:40:52.545Z" }, - { url = "https://files.pythonhosted.org/packages/69/40/b33b74c84606fd301b2915f0062e45733c6ff5708d121dd0deaa8871e2d0/scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232", size = 23553066, upload-time = "2025-09-11T17:40:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/55/a7/22c739e2f21a42cc8f16bc76b47cff4ed54fbe0962832c589591c2abec34/scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1", size = 33336407, upload-time = "2025-09-11T17:41:06.796Z" }, - { url = "https://files.pythonhosted.org/packages/53/11/a0160990b82999b45874dc60c0c183d3a3a969a563fffc476d5a9995c407/scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f", size = 35673281, upload-time = "2025-09-11T17:41:15.055Z" }, - { url = "https://files.pythonhosted.org/packages/96/53/7ef48a4cfcf243c3d0f1643f5887c81f29fdf76911c4e49331828e19fc0a/scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef", size = 36004222, upload-time = "2025-09-11T17:41:23.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7f/71a69e0afd460049d41c65c630c919c537815277dfea214031005f474d78/scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1", size = 38664586, upload-time = "2025-09-11T17:41:31.021Z" }, - { url = "https://files.pythonhosted.org/packages/34/95/20e02ca66fb495a95fba0642fd48e0c390d0ece9b9b14c6e931a60a12dea/scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e", size = 38550641, upload-time = "2025-09-11T17:41:36.61Z" }, - { url = "https://files.pythonhosted.org/packages/92/ad/13646b9beb0a95528ca46d52b7babafbe115017814a611f2065ee4e61d20/scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851", size = 25456070, upload-time = "2025-09-11T17:41:41.3Z" }, - { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70", size = 36604856, upload-time = "2025-09-11T17:41:47.695Z" }, - { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9", size = 28864626, upload-time = "2025-09-11T17:41:52.642Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5", size = 20855689, upload-time = "2025-09-11T17:41:57.886Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925", size = 23512151, upload-time = "2025-09-11T17:42:02.303Z" }, - { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9", size = 33329824, upload-time = "2025-09-11T17:42:07.549Z" }, - { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7", size = 35681881, upload-time = "2025-09-11T17:42:13.255Z" }, - { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb", size = 36006219, upload-time = "2025-09-11T17:42:18.765Z" }, - { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e", size = 38682147, upload-time = "2025-09-11T17:42:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c", size = 38520766, upload-time = "2025-09-11T17:43:25.342Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104", size = 25451169, upload-time = "2025-09-11T17:43:30.198Z" }, - { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1", size = 37012682, upload-time = "2025-09-11T17:42:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a", size = 29389926, upload-time = "2025-09-11T17:42:35.845Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f", size = 21381152, upload-time = "2025-09-11T17:42:40.07Z" }, - { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4", size = 23914410, upload-time = "2025-09-11T17:42:44.313Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21", size = 33481880, upload-time = "2025-09-11T17:42:49.325Z" }, - { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7", size = 35791425, upload-time = "2025-09-11T17:42:54.711Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8", size = 36178622, upload-time = "2025-09-11T17:43:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472", size = 38783985, upload-time = "2025-09-11T17:43:06.661Z" }, - { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351", size = 38631367, upload-time = "2025-09-11T17:43:14.44Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d", size = 25787992, upload-time = "2025-09-11T17:43:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77", size = 36595109, upload-time = "2025-09-11T17:43:35.713Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70", size = 28859110, upload-time = "2025-09-11T17:43:40.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88", size = 20850110, upload-time = "2025-09-11T17:43:44.981Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f", size = 23497014, upload-time = "2025-09-11T17:43:49.074Z" }, - { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb", size = 33401155, upload-time = "2025-09-11T17:43:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7", size = 35691174, upload-time = "2025-09-11T17:44:00.101Z" }, - { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548", size = 36070752, upload-time = "2025-09-11T17:44:05.619Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936", size = 38701010, upload-time = "2025-09-11T17:44:11.322Z" }, - { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff", size = 39360061, upload-time = "2025-09-11T17:45:09.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d", size = 26126914, upload-time = "2025-09-11T17:45:14.73Z" }, - { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8", size = 37013193, upload-time = "2025-09-11T17:44:16.757Z" }, - { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4", size = 29390172, upload-time = "2025-09-11T17:44:21.783Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831", size = 21381326, upload-time = "2025-09-11T17:44:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3", size = 23915036, upload-time = "2025-09-11T17:44:30.527Z" }, - { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac", size = 33484341, upload-time = "2025-09-11T17:44:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374", size = 35790840, upload-time = "2025-09-11T17:44:41.76Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6", size = 36174716, upload-time = "2025-09-11T17:44:47.316Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c", size = 38790088, upload-time = "2025-09-11T17:44:53.011Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9", size = 39457455, upload-time = "2025-09-11T17:44:58.899Z" }, - { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, -] - [[package]] name = "send2trash" version = "1.8.3" From 5c506f146fa65bfd8e92a122d794dacc7081e65f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 15:21:26 +1000 Subject: [PATCH 135/298] np array instead of for loop --- activitysim/core/interaction_sample.py | 34 +++++++++++--------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 93834c690a..4953a210b6 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -58,32 +58,26 @@ def make_sample_choices_utility_based( utilities = utilities[~zero_probs] choosers = choosers[~zero_probs] - utils_array = utilities.to_numpy() - chunk_sizer.log_df(trace_label, "utils_array", utils_array) - chosen_destinations = [] - - rands = state.get_rn_generator().gumbel_for_df(utilities, n=alternative_count) + rands = state.get_rn_generator().gumbel_for_df( + utilities, n=alternative_count * sample_size + ) chunk_sizer.log_df(trace_label, "rands", rands) - # TODO-EET [janzill Jun2022]: using for-loop to keep memory usage low, an array of dimension - # (len(choosers), alternative_count, sample_size) can get very large. Probably better to - # use chunking for this. - for i in range(sample_size): - # created this once for memory logging - if i > 0: - rands = state.get_rn_generator().gumbel_for_df( - utilities, n=alternative_count - ) - chosen_destinations.append(np.argmax(utils_array + rands, axis=1)) - chosen_destinations = np.concatenate(chosen_destinations, axis=0) + # duplicate utils sample_size times along third axis, then add reshaped randoms to it + full_utils = np.tile( + utilities.to_numpy()[:, :, np.newaxis], (1, 1, sample_size) + ) + rands.reshape((utilities.shape[0], alternative_count, sample_size)) + chunk_sizer.log_df(trace_label, "full_utils", full_utils) - chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) - - del utils_array - chunk_sizer.log_df(trace_label, "utils_array", None) del rands chunk_sizer.log_df(trace_label, "rands", None) + # choose maximum along all alternatives (axis 1) for all choosers and samples + chosen_destinations = np.argmax(full_utils, axis=1).flatten() + chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) + del full_utils + chunk_sizer.log_df(trace_label, "full_utils", None) + chooser_idx = np.tile(np.arange(utilities.shape[0]), sample_size) chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) From 710b3b0881b1bba3873ab761238bd013fe2ce0a0 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 15:21:26 +1000 Subject: [PATCH 136/298] memory reduction --- activitysim/core/interaction_sample.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 4953a210b6..04f0160c16 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -63,11 +63,13 @@ def make_sample_choices_utility_based( ) chunk_sizer.log_df(trace_label, "rands", rands) - # duplicate utils sample_size times along third axis, then add reshaped randoms to it - full_utils = np.tile( - utilities.to_numpy()[:, :, np.newaxis], (1, 1, sample_size) - ) + rands.reshape((utilities.shape[0], alternative_count, sample_size)) - chunk_sizer.log_df(trace_label, "full_utils", full_utils) + rands = rands.reshape((utilities.shape[0], alternative_count, sample_size)) + rands += utilities.to_numpy()[:, :, np.newaxis] + # # duplicate utils sample_size times along third axis, then add reshaped randoms to it + # full_utils = np.tile( + # utilities.to_numpy()[:, :, np.newaxis], (1, 1, sample_size) + # ) + rands.reshape((utilities.shape[0], alternative_count, sample_size)) + # chunk_sizer.log_df(trace_label, "full_utils", full_utils) del rands chunk_sizer.log_df(trace_label, "rands", None) From 60a744a7aec72884e26b8fc2e3b35d23e2998ccd Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 15:21:26 +1000 Subject: [PATCH 137/298] no duplicate arrays --- activitysim/core/interaction_sample.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 04f0160c16..244964ea6f 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -65,20 +65,12 @@ def make_sample_choices_utility_based( rands = rands.reshape((utilities.shape[0], alternative_count, sample_size)) rands += utilities.to_numpy()[:, :, np.newaxis] - # # duplicate utils sample_size times along third axis, then add reshaped randoms to it - # full_utils = np.tile( - # utilities.to_numpy()[:, :, np.newaxis], (1, 1, sample_size) - # ) + rands.reshape((utilities.shape[0], alternative_count, sample_size)) - # chunk_sizer.log_df(trace_label, "full_utils", full_utils) - - del rands - chunk_sizer.log_df(trace_label, "rands", None) # choose maximum along all alternatives (axis 1) for all choosers and samples - chosen_destinations = np.argmax(full_utils, axis=1).flatten() + chosen_destinations = np.argmax(rands, axis=1).flatten() chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) - del full_utils - chunk_sizer.log_df(trace_label, "full_utils", None) + del rands + chunk_sizer.log_df(trace_label, "rands", None) chooser_idx = np.tile(np.arange(utilities.shape[0]), sample_size) chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) From f97185eda08b52ec84bb2167af1f3511a6580c5a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 15:21:26 +1000 Subject: [PATCH 138/298] bug fix: order of chooser_idx in interaction_simulate --- activitysim/core/interaction_sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 244964ea6f..8b369b18b8 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -72,7 +72,7 @@ def make_sample_choices_utility_based( del rands chunk_sizer.log_df(trace_label, "rands", None) - chooser_idx = np.tile(np.arange(utilities.shape[0]), sample_size) + chooser_idx = np.repeat(np.arange(utilities.shape[0]), sample_size) chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) probs = logit.utils_to_probs( From 9d69dabb79786101212747055430d4ae6509776f Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Mon, 16 Mar 2026 17:15:28 +1000 Subject: [PATCH 139/298] add tests, docstrings for logit --- activitysim/core/logit.py | 132 +++++++++- activitysim/core/test/test_logit.py | 390 +++++++++++++++++++++++++++- 2 files changed, 499 insertions(+), 23 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 0030168bb2..34a95f7e22 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -117,7 +117,7 @@ def utils_to_logsums(utils, exponentiated=False, allow_zero_probs=False): # fixme - conversion to float not needed in either case? # utils_arr = utils.values.astype('float') - utils_arr = utils.values + utils_arr = utils.to_numpy(copy=True) if not exponentiated: utils_arr = np.exp(utils_arr) @@ -174,7 +174,7 @@ def validate_utils( """ trace_label = tracing.extend_trace_label(trace_label, "validate_utils") - utils_arr = utils.values + utils_arr = utils.to_numpy(copy=True) np.putmask(utils_arr, utils_arr <= UTIL_MIN, UTIL_UNAVAILABLE) @@ -225,6 +225,9 @@ def utils_to_probs( if True value rows in which all utility alts are EXP_UTIL_MIN will result in rows in probs to have all zero probability (and not sum to 1.0) This is for the benefit of calculating probabilities of nested logit nests + When allow_zero_probs is True, overflow protection is disabled (with a warning) + to preserve zero-probability rows. For large float32 utilities, overflow + protection is still enabled and may raise a ValueError. trace_choosers : pandas.dataframe the choosers df (for interaction_simulate) to facilitate the reporting of hh_id @@ -255,7 +258,7 @@ def utils_to_probs( # fixme - conversion to float not needed in either case? # utils_arr = utils.values.astype('float') - utils_arr = utils.values + utils_arr = utils.to_numpy(copy=True) if allow_zero_probs: if overflow_protection: @@ -344,8 +347,21 @@ def utils_to_probs( return probs -# TODO-EET: add doc string, tracing def add_ev1_random(state: workflow.State, df: pd.DataFrame): + """ + Add iid EV1 (Gumbel) random error terms to utilities for EET choice. + + Parameters + ---------- + state : workflow.State + df : pandas.DataFrame + Utilities indexed by chooser and with alternatives as columns. + + Returns + ------- + pandas.DataFrame + Utilities with EV1 errors added. + """ nest_utils_for_choice = df.copy() nest_utils_for_choice += state.get_rn_generator().gumbel_for_df( nest_utils_for_choice, n=nest_utils_for_choice.shape[1] @@ -367,12 +383,39 @@ def choose_from_tree( raise ValueError("This should never happen - no alternative found") -# TODO-EET: add doc string, tracing def make_choices_explicit_error_term_nl( state, nested_utilities, alt_order_array, nest_spec, trace_label ): - """walk down the nesting tree and make choice at each level, which is the root of the next level choice.""" + """ + Walk down the nesting tree and make a choice at each level using EET. + + Parameters + ---------- + state : workflow.State + nested_utilities : pandas.DataFrame + Utilities for nest and leaf nodes. + alt_order_array : numpy.ndarray + Leaf alternatives in the original ordering. + nest_spec : dict or LogitNestSpec + Nest specification for the choice model. + trace_label : str + Trace label for logging and tracing. + + Returns + ------- + pandas.Series + Choice indices aligned to `alt_order_array`. + """ + if trace_label: + state.tracing.trace_df( + nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils") + ) nest_utils_for_choice = add_ev1_random(state, nested_utilities) + if trace_label: + state.tracing.trace_df( + nest_utils_for_choice, + tracing.extend_trace_label(trace_label, "nested_utils_eet"), + ) all_alternatives = set(nest.name for nest in each_nest(nest_spec, type="leaf")) logit_nest_groups = group_nest_names_by_level(nest_spec) @@ -389,24 +432,79 @@ def make_choices_explicit_error_term_nl( ), axis=1, ) - # TODO-EET: reporting like for zero probs - assert not choices.isnull().any(), f"No choice for {trace_label}" + missing_choices = choices.isnull() + if missing_choices.any(): + report_bad_choices( + state, + missing_choices, + nested_utilities, + trace_label=tracing.extend_trace_label(trace_label, "no_choice"), + msg="no alternative selected", + raise_error=False, + ) + assert not missing_choices.any(), f"No choice for {trace_label}" choices = pd.Series(choices, index=nest_utils_for_choice.index) # In order for choice indexing to be consistent with MNL and cumsum MC choices, we need to index in the order # alternatives were originally created before adding nest nodes that are not elemental alternatives choices = choices.map({v: k for k, v in enumerate(alt_order_array)}) + if trace_label: + state.tracing.trace_df( + choices, + tracing.extend_trace_label(trace_label, "choices"), + columns=[None, "choice"], + ) + return choices -# TODO-EET: add doc string, tracing def make_choices_explicit_error_term_mnl(state, utilities, trace_label): + """ + Make EET choices for a multinomial logit model by adding EV1 errors. + + Parameters + ---------- + state : workflow.State + utilities : pandas.DataFrame + Utilities with choosers as rows and alternatives as columns. + trace_label : str + Trace label for logging and tracing. + + Returns + ------- + pandas.Series + Choice indices aligned to the utilities columns order. + """ + if trace_label: + state.tracing.trace_df( + utilities, tracing.extend_trace_label(trace_label, "utilities") + ) utilities_incl_unobs = add_ev1_random(state, utilities) + if trace_label: + state.tracing.trace_df( + utilities_incl_unobs, + tracing.extend_trace_label(trace_label, "utilities_eet"), + ) choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) - # TODO-EET: reporting like for zero probs - assert not np.isnan(choices).any(), f"No choice for {trace_label}" + missing_choices = np.isnan(choices) + if missing_choices.any(): + report_bad_choices( + state, + missing_choices, + utilities, + trace_label=tracing.extend_trace_label(trace_label, "no_choice"), + msg="no alternative selected", + raise_error=False, + ) + assert not missing_choices.any(), f"No choice for {trace_label}" choices = pd.Series(choices, index=utilities_incl_unobs.index) + if trace_label: + state.tracing.trace_df( + choices, + tracing.extend_trace_label(trace_label, "choices"), + columns=[None, "choice"], + ) return choices @@ -434,13 +532,19 @@ def make_choices_utility_based( ) -> tuple[pd.Series, pd.Series]: trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") - # TODO-EET: index of choices for nested utilities is different than unnested - this needs to be consistent for - # turning indexes into alternative names to keep code changes to minimum for now + # For nested models, choices are mapped to `name_mapping` ordering inside the + # EET helper. For MNL, choices already follow the utilities column order. choices = make_choices_explicit_error_term( state, utilities, name_mapping, nest_spec, trace_label ) - # TODO-EET: rands - log all zeros for now + # EET does not expose per-row random draws; return zeros for compatibility. rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) + if trace_label: + state.tracing.trace_df( + rands, + tracing.extend_trace_label(trace_label, "rands"), + columns=[None, "rand"], + ) return choices, rands diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index c82606981f..231cd4074a 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -10,6 +10,7 @@ import pytest from activitysim.core import logit, workflow +from activitysim.core.exceptions import InvalidTravelError from activitysim.core.simulate import eval_variables @@ -70,7 +71,125 @@ def utilities(choosers, spec, test_data): ) -# TODO-EET: Add tests here! +def test_validate_utils_replaces_unavailable_values(): + state = workflow.State().default_settings() + utils = pd.DataFrame([[0.0, logit.UTIL_MIN - 1.0], [1.0, 2.0]]) + + validated = logit.validate_utils(state, utils, allow_zero_probs=False) + + assert validated.iloc[0, 0] == pytest.approx(0.0) + assert validated.iloc[0, 1] == pytest.approx(logit.UTIL_UNAVAILABLE) + assert validated.iloc[1, 0] == pytest.approx(1.0) + assert validated.iloc[1, 1] == pytest.approx(2.0) + + +def test_validate_utils_raises_when_all_unavailable(): + state = workflow.State().default_settings() + utils = pd.DataFrame([[logit.UTIL_MIN - 1.0, logit.UTIL_MIN - 2.0]]) + + with pytest.raises(InvalidTravelError) as excinfo: + logit.validate_utils(state, utils, allow_zero_probs=False) + + assert "all probabilities are zero" in str(excinfo.value) + + +def test_validate_utils_allows_zero_probs(): + state = workflow.State().default_settings() + utils = pd.DataFrame([[logit.UTIL_MIN - 1.0, logit.UTIL_MIN - 2.0]]) + + validated = logit.validate_utils(state, utils, allow_zero_probs=True) + + assert (validated.iloc[0] == logit.UTIL_UNAVAILABLE).all() + + +def test_validate_utils_does_not_mutate_input(): + state = workflow.State().default_settings() + utils = pd.DataFrame([[0.0, logit.UTIL_MIN - 1.0], [1.0, 2.0]]) + original = utils.copy() + + _ = logit.validate_utils(state, utils, allow_zero_probs=False) + + pdt.assert_frame_equal(utils, original) + + +def test_utils_to_probs_logsums_with_overflow_protection(): + state = workflow.State().default_settings() + utils = pd.DataFrame( + [[1000.0, 1001.0, 999.0], [-1000.0, -1001.0, -999.0]], + columns=["a", "b", "c"], + ) + original_utils = utils.copy() + + probs, logsums = logit.utils_to_probs( + state, + utils, + trace_label=None, + overflow_protection=True, + return_logsums=True, + ) + + utils_np = original_utils.to_numpy() + row_max = utils_np.max(axis=1, keepdims=True) + exp_shifted = np.exp(utils_np - row_max) + expected_probs = exp_shifted / exp_shifted.sum(axis=1, keepdims=True) + expected_logsums = pd.Series( + np.log(exp_shifted.sum(axis=1)) + row_max.squeeze(), + index=utils.index, + ) + + pdt.assert_frame_equal( + probs, + pd.DataFrame(expected_probs, index=utils.index, columns=utils.columns), + rtol=1.0e-7, + atol=0.0, + ) + pdt.assert_series_equal(logsums, expected_logsums, rtol=1.0e-7, atol=0.0) + + +def test_utils_to_probs_warns_on_zero_probs_overflow(): + state = workflow.State().default_settings() + utils = pd.DataFrame( + [[logit.UTIL_MIN - 1.0, logit.UTIL_MIN - 2.0], [0.0, 0.0]], + columns=["a", "b"], + ) + + with pytest.warns(UserWarning, match="cannot set overflow_protection"): + probs = logit.utils_to_probs( + state, + utils, + trace_label=None, + allow_zero_probs=True, + overflow_protection=True, + ) + + assert (probs.iloc[0] == 0.0).all() + assert probs.iloc[1].sum() == pytest.approx(1.0) + assert probs.iloc[1].iloc[0] == pytest.approx(0.5) + assert probs.iloc[1].iloc[1] == pytest.approx(0.5) + + +def test_utils_to_probs_raises_on_float32_zero_probs_overflow(): + state = workflow.State().default_settings() + utils = pd.DataFrame(np.array([[90.0, 0.0]], dtype=np.float32)) + + with pytest.raises(ValueError, match="cannot prevent expected overflow"): + logit.utils_to_probs( + state, + utils, + trace_label=None, + allow_zero_probs=True, + overflow_protection=True, + ) + + +def test_utils_to_probs_does_not_mutate_input(): + state = workflow.State().default_settings() + utils = pd.DataFrame([[1.0, 2.0], [3.0, 4.0]], columns=["a", "b"]) + original = utils.copy() + + _ = logit.utils_to_probs(state, utils, trace_label=None) + + pdt.assert_frame_equal(utils, original) def test_utils_to_probs(utilities, test_data): @@ -131,18 +250,265 @@ def test_make_choices_only_one(): ) -def test_make_choices_real_probs(utilities): - state = workflow.State().default_settings() - probs = logit.utils_to_probs(state, utilities, trace_label=None) +def test_make_choices_matches_random_draws(): + class DummyRNG: + def random_for_df(self, df, n=1): + assert n == 1 + return np.array([[0.05], [0.6], [0.95]]) + + class DummyState: + @staticmethod + def get_rn_generator(): + return DummyRNG() + + state = DummyState() + probs = pd.DataFrame( + [[0.1, 0.2, 0.7], [0.4, 0.4, 0.2], [0.05, 0.9, 0.05]], + index=["a", "b", "c"], + columns=["x", "y", "z"], + ) choices, rands = logit.make_choices(state, probs) + expected_rands = np.array([0.05, 0.6, 0.95]) + expected_choices = np.array([0, 1, 1]) + + pdt.assert_series_equal( + rands, + pd.Series(expected_rands, index=probs.index), + check_names=False, + ) pdt.assert_series_equal( choices, - pd.Series([1, 2], index=[0, 1]), + pd.Series(expected_choices, index=probs.index), check_dtype=False, ) +def test_add_ev1_random(): + class DummyRNG: + def gumbel_for_df(self, df, n): + # Deterministic, non-constant draws make it easy to verify + # correct per-row/per-column addition behavior. + row_component = df.index.to_numpy(dtype=float).reshape(-1, 1) / 10.0 + col_component = np.arange(n, dtype=float).reshape(1, -1) + return row_component + col_component + + rng = DummyRNG() + + class DummyState: + @staticmethod + def get_rn_generator(): + return rng + + utilities = pd.DataFrame( + [[1.0, 2.0], [3.0, 4.0]], + index=[10, 11], + columns=["a", "b"], + ) + + randomized = logit.add_ev1_random(DummyState(), utilities) + + expected = pd.DataFrame( + [[2.0, 4.0], [4.1, 6.1]], + index=[10, 11], + columns=["a", "b"], + ) + + # check that the random component was added correctly, and that the original utilities were not mutated + pdt.assert_frame_equal(randomized, expected) + pdt.assert_index_equal(randomized.index, utilities.index) + pdt.assert_index_equal(randomized.columns, utilities.columns) + pdt.assert_frame_equal( + utilities, + pd.DataFrame( + [[1.0, 2.0], [3.0, 4.0]], + index=[10, 11], + columns=["a", "b"], + ), + ) + + +def test_group_nest_names_by_level(): + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "motorized", "coefficient": 0.7, "alternatives": ["car", "bus"]}, + "walk", + ], + } + + grouped = logit.group_nest_names_by_level(nest_spec) + + assert grouped == {1: ["root"], 2: ["motorized", "walk"], 3: ["car", "bus"]} + + +def test_choose_from_tree_selects_leaf(): + nest_utils = pd.Series( + { + "motorized": 2.0, + "walk": 1.0, + "car": 5.0, + "bus": 3.0, + } + ) + all_alternatives = {"walk", "car", "bus"} + logit_nest_groups = {1: ["root"], 2: ["motorized", "walk"], 3: ["car", "bus"]} + nest_alternatives_by_name = { + "root": ["motorized", "walk"], + "motorized": ["car", "bus"], + } + + choice = logit.choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ) + + assert choice == "car" + + +def test_choose_from_tree_raises_on_missing_leaf(): + nest_utils = pd.Series({"motorized": 2.0, "walk": 1.0}) + all_alternatives = {"car", "bus"} + logit_nest_groups = {1: ["root"], 2: ["motorized", "walk"]} + nest_alternatives_by_name = { + "root": ["motorized", "walk"], + "motorized": ["car", "bus"], + } + + with pytest.raises(ValueError, match="no alternative found"): + logit.choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ) + + +def test_make_choices_eet_mnl(monkeypatch): + def fake_add_ev1_random(_state, _df): + return pd.DataFrame( + [[1.0, 3.0], [4.0, 2.0]], + index=[100, 101], + columns=["a", "b"], + ) + + monkeypatch.setattr(logit, "add_ev1_random", fake_add_ev1_random) + + choices = logit.make_choices_explicit_error_term_mnl( + workflow.State().default_settings(), + pd.DataFrame([[0.0, 0.0], [0.0, 0.0]], index=[100, 101], columns=["a", "b"]), + trace_label=None, + ) + + pdt.assert_series_equal(choices, pd.Series([1, 0], index=[100, 101])) + + +def test_make_choices_eet_nl(monkeypatch): + def fake_add_ev1_random(_state, _df): + return pd.DataFrame( + [[5.0, 1.0, 4.0, 2.0], [3.0, 4.0, 1.0, 2.0]], + index=[10, 11], + columns=["motorized", "walk", "car", "bus"], + ) + + monkeypatch.setattr(logit, "add_ev1_random", fake_add_ev1_random) + + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "motorized", "coefficient": 0.7, "alternatives": ["car", "bus"]}, + "walk", + ], + } + alt_order_array = np.array(["walk", "car", "bus"]) + + choices = logit.make_choices_explicit_error_term_nl( + workflow.State().default_settings(), + pd.DataFrame( + [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], + index=[10, 11], + columns=["motorized", "walk", "car", "bus"], + ), + alt_order_array, + nest_spec, + trace_label=None, + ) + + pdt.assert_series_equal(choices, pd.Series([1, 0], index=[10, 11])) + + +def test_make_choices_utility_based_sets_zero_rands(monkeypatch): + def fake_add_ev1_random(_state, df): + return pd.DataFrame( + [[2.0, 1.0], [0.5, 2.5]], + index=df.index, + columns=df.columns, + ) + + monkeypatch.setattr(logit, "add_ev1_random", fake_add_ev1_random) + + utilities = pd.DataFrame([[3.0, 2.0], [1.0, 4.0]], index=[11, 12]) + choices, rands = logit.make_choices_utility_based( + workflow.State().default_settings(), + utilities, + name_mapping=np.array(["a", "b"]), + nest_spec=None, + trace_label=None, + ) + + expected_choices = pd.Series([0, 1], index=[11, 12]) + pdt.assert_series_equal(choices, expected_choices) + pdt.assert_series_equal(rands, pd.Series([0, 0], index=[11, 12])) + + +def test_make_choices_vs_eet_same_distribution(): + """With many draws, make_choices (probability-based) and + make_choices_explicit_error_term_mnl (EET) should produce roughly the + same empirical choice-frequency distribution for the same utilities.""" + n_draws = 100_000 + utils_values = [5.0, 6.0, 7.0, 8.0, 9.0] + n_alts = len(utils_values) + columns = ["a", "b", "c", "d", "e"] + + utils = pd.DataFrame([utils_values] * n_draws, columns=columns) + + # Probability-based (Monte Carlo) path — independent RNG + mc_rng = np.random.default_rng(42) + + class MCDummyRNG: + def random_for_df(self, df, n=1): + return mc_rng.random((len(df), n)) + + class MCDummyState: + @staticmethod + def get_rn_generator(): + return MCDummyRNG() + + probs = logit.utils_to_probs( + MCDummyState(), utils, trace_label=None, overflow_protection=True + ) + choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) + + # Explicit-error-term (EET) path — independent RNG + eet_rng = np.random.default_rng(123) + + class EETDummyRNG: + def gumbel_for_df(self, df, n): + return eet_rng.gumbel(size=(len(df), n)) + + class EETDummyState: + @staticmethod + def get_rn_generator(): + return EETDummyRNG() + + choices_eet = logit.make_choices_explicit_error_term_mnl( + EETDummyState(), utils, trace_label=None + ) + + mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=n_alts) / n_draws + eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=n_alts) / n_draws + + np.testing.assert_allclose(mc_fracs, eet_fracs, atol=0.005) + + @pytest.fixture(scope="module") def interaction_choosers(): return pd.DataFrame({"attr": ["a", "b", "c", "b"]}, index=["w", "x", "y", "z"]) @@ -167,13 +533,19 @@ def test_interaction_dataset_no_sample(interaction_choosers, interaction_alts): ) interacted, expected = interacted.align(expected, axis=1) - - print("interacted\n", interacted) - print("expected\n", expected) pdt.assert_frame_equal(interacted, expected) def test_interaction_dataset_sampled(interaction_choosers, interaction_alts): + class DummyRNG: + def choice_for_df(self, df, a, size, replace=False): + return np.array([2, 3, 0, 2, 3, 0, 1, 0]) + + class DummyState: + @staticmethod + def get_rn_generator(): + return DummyRNG() + expected = pd.DataFrame( { "attr": ["a"] * 2 + ["b"] * 2 + ["c"] * 2 + ["b"] * 2, @@ -183,7 +555,7 @@ def test_interaction_dataset_sampled(interaction_choosers, interaction_alts): ) interacted = logit.interaction_dataset( - workflow.State().default_settings(), + DummyState(), interaction_choosers, interaction_alts, sample_size=2, From a9db13142f85fd8ae6860ce9243fea75779491f0 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 17 Mar 2026 12:04:56 +1000 Subject: [PATCH 140/298] Add basic with/without EET test for interaction simulate --- .../core/test/test_interaction_simulate.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 activitysim/core/test/test_interaction_simulate.py diff --git a/activitysim/core/test/test_interaction_simulate.py b/activitysim/core/test/test_interaction_simulate.py new file mode 100644 index 0000000000..1505b01a91 --- /dev/null +++ b/activitysim/core/test/test_interaction_simulate.py @@ -0,0 +1,81 @@ +# ActivitySim +# See full license in LICENSE.txt. +from __future__ import annotations + +import numpy as np +import pandas as pd +import pytest + +from activitysim.core import interaction_simulate, workflow + + +@pytest.fixture +def state() -> workflow.State: + state = workflow.State().default_settings() + state.settings.check_for_variability = False + return state + + +def test_interaction_simulate_explicit_error_terms_parity(state): + # Run interaction_simulate with and without explicit error terms and check that results are similar. + + # Set up a simple case: 10000 choosers, 5 alternatives for better statistical convergence + num_choosers = 100_000 + num_alts = 10 + sample_size = num_alts + + # Create random choosers and alternatives + np.random.seed(42) + choosers = pd.DataFrame( + {"chooser_attr": np.random.rand(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + alternatives = pd.DataFrame( + {"alt_attr": np.random.rand(num_alts)}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + + # Simple spec: utility = chooser_attr * alt_attr + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), + ) + + # Run _without_ explicit error terms + state.settings.use_explicit_error_terms = False + state.rng().set_base_seed(42) # Set seed BEFORE adding channels or steps + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_mnl") + + choices_mnl = interaction_simulate.interaction_simulate( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + ) + + # Run _with_ explicit error terms + state.init_state() # reset the state to rerun with same seed + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_explicit") + + choices_explicit = interaction_simulate.interaction_simulate( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + ) + + mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() + explicit_counts = choices_explicit.value_counts(normalize=True).sort_index() + + # Check that they aren't wildly different (e.g., within 1% share for each alt) + for alt in alternatives.index: + share_mnl = mnl_counts.get(alt, 0) + share_explicit = explicit_counts.get(alt, 0) + assert abs(share_mnl - share_explicit) < 0.01, f"Large discrepancy at alt {alt}: {share_mnl} vs {share_explicit}" \ No newline at end of file From a7f2e8f8daaefdd5ef83d540e0e8d5a6333359b2 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Wed, 18 Mar 2026 14:30:02 +1000 Subject: [PATCH 141/298] Fix, complete tests for interaction sample, simulate --- .../core/test/test_interaction_sample.py | 138 ++++++++++++++++ .../test/test_interaction_sample_simulate.py | 151 ++++++++++++++++++ .../core/test/test_interaction_simulate.py | 109 ++++++++++++- activitysim/core/test/test_logit.py | 2 +- 4 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 activitysim/core/test/test_interaction_sample.py create mode 100644 activitysim/core/test/test_interaction_sample_simulate.py diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py new file mode 100644 index 0000000000..240de2e949 --- /dev/null +++ b/activitysim/core/test/test_interaction_sample.py @@ -0,0 +1,138 @@ +# ActivitySim +# See full license in LICENSE.txt. +from __future__ import annotations + +import numpy as np +import pandas as pd +import pytest + +from activitysim.core import interaction_sample, workflow + + +@pytest.fixture +def state() -> workflow.State: + state = workflow.State().default_settings() + state.settings.check_for_variability = False + return state + + +def test_interaction_sample_parity(state): + # Run interaction_sample with and without explicit error terms and check that results are similar. + + num_choosers = 100_000 + num_alts = 100 + sample_size = 10 + + # Create random choosers and alternatives + rng = np.random.default_rng(42) + choosers = pd.DataFrame( + {"chooser_attr": rng.random(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + alternatives = pd.DataFrame( + {"alt_attr": rng.random(num_alts)}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + + # Simple spec: utility = chooser_attr * alt_attr + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), + ) + + # Run _without_ explicit error terms + state.settings.use_explicit_error_terms = False + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_mnl") + + choices_mnl = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + ) + + # Run _with_ explicit error terms + state.init_state() # reset the state to rerun with same seed + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_explicit") + + choices_explicit = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + ) + + assert "alt_id" in choices_mnl.columns + assert "alt_id" in choices_explicit.columns + assert not choices_mnl["alt_id"].isna().any() + assert not choices_explicit["alt_id"].isna().any() + assert choices_mnl["alt_id"].isin(alternatives.index).all() + assert choices_explicit["alt_id"].isin(alternatives.index).all() + + # In interaction_sample, choices_explicit and choices_mnl are DataFrames with sampled alternatives. + # The statistics of chosen alternatives should be similar. + mnl_counts = choices_mnl["alt_id"].value_counts(normalize=True).sort_index() + explicit_counts = choices_explicit["alt_id"].value_counts(normalize=True).sort_index() + + # Check top choices overlap significantly or shares are close + all_alts = set(mnl_counts.index) | set(explicit_counts.index) + for alt in all_alts: + share_mnl = mnl_counts.get(alt, 0) + share_explicit = explicit_counts.get(alt, 0) + diff = abs(share_mnl - share_explicit) + assert diff < 0.05, ( + f"Large discrepancy at alt {alt}: " + f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" + ) + +def test_interaction_sample_eet_unavailable_alternatives(state): + # Test that EET handles unavailable alternatives in sampling + + num_choosers = 100 + num_alts = 10 + sample_size = 2 + + choosers = pd.DataFrame( + {"chooser_attr": np.ones(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + # Alt 0-4 are attractive, Alt 5-9 are "unavailable" + alternatives = pd.DataFrame( + {"alt_attr": [10.0]*5 + [-1000.0]*5}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + # Run with EET + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_unavailable_eet") + + choices_eet = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + ) + + # Sampled alternatives should only be from Alt 0-4 + assert choices_eet["alt_id"].isin([0, 1, 2, 3, 4]).all() + assert not choices_eet["alt_id"].isin([5, 6, 7, 8, 9]).any() diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py new file mode 100644 index 0000000000..0dbf5bd945 --- /dev/null +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -0,0 +1,151 @@ +# ActivitySim +# See full license in LICENSE.txt. +from __future__ import annotations + +import numpy as np +import pandas as pd +import pytest + +from activitysim.core import interaction_sample_simulate, workflow + + +@pytest.fixture +def state() -> workflow.State: + state = workflow.State().default_settings() + state.settings.check_for_variability = False + return state + + +def test_interaction_sample_simulate_parity(state): + # Run interaction_sample_simulate with and without explicit error terms and check that results are similar. + + num_choosers = 100_000 + num_alts_per_chooser = 5 # small sample size to keep things simple + + # Create random choosers + rng = np.random.default_rng(42) + choosers = pd.DataFrame( + {"chooser_attr": rng.random(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + # Create random alternatives for each chooser + # In interaction_sample_simulate, alternatives is typically a DataFrame with the same index as choosers + # but repeated for each alternative in the sample. + alt_ids = np.tile(np.arange(num_alts_per_chooser), num_choosers) + alternatives = pd.DataFrame( + { + "alt_attr": rng.random(num_choosers * num_alts_per_chooser), + "alt_id": alt_ids, + "tdd": alt_ids, + }, + index=np.repeat(choosers.index, num_alts_per_chooser), + ) + alternatives.index.name = "person_id" + + # Simple spec: utility = chooser_attr * alt_attr + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), + ) + + # Run _without_ explicit error terms + state.settings.use_explicit_error_terms = False + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_mnl") + + choices_mnl = interaction_sample_simulate.interaction_sample_simulate( + state, + choosers, + alternatives, + spec, + choice_column="tdd", + ) + + # Run _with_ explicit error terms + state.init_state() + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_explicit") + + choices_explicit = interaction_sample_simulate.interaction_sample_simulate( + state, + choosers, + alternatives, + spec, + choice_column="tdd", + ) + + assert len(choices_mnl) == num_choosers + assert len(choices_explicit) == num_choosers + assert choices_mnl.index.equals(choosers.index) + assert choices_explicit.index.equals(choosers.index) + assert not choices_mnl.isna().any() + assert not choices_explicit.isna().any() + + # choices are series with the same index as choosers and containing the choice (from choice_column) + mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() + explicit_counts = choices_explicit.value_counts(normalize=True).sort_index() + + for alt in range(num_alts_per_chooser): + share_mnl = mnl_counts.get(alt, 0) + share_explicit = explicit_counts.get(alt, 0) + diff = abs(share_mnl - share_explicit) + assert diff < 0.05, ( + f"Large discrepancy at alt {alt}: " + f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" + ) + +def test_interaction_sample_simulate_eet_unavailable_alternatives(state): + # Test that EET handles unavailable alternatives in sample simulation + + num_choosers = 10 + num_alts_per_chooser = 5 + + choosers = pd.DataFrame( + {"chooser_attr": np.ones(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + # For each chooser, 2 attractive alts, 3 unavailable + alt_attrs = [10.0, 10.0, -1000.0, -1000.0, -1000.0] * num_choosers + alt_ids = [0, 1, 2, 3, 4] * num_choosers + + alternatives = pd.DataFrame( + { + "alt_attr": alt_attrs, + "alt_id": alt_ids, + "tdd": alt_ids, + }, + index=np.repeat(choosers.index, num_alts_per_chooser), + ) + alternatives.index.name = "person_id" + + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + # Run with EET + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_unavailable_eet") + + choices_eet = interaction_sample_simulate.interaction_sample_simulate( + state, + choosers, + alternatives, + spec, + choice_column="tdd", + ) + + assert len(choices_eet) == num_choosers + assert choices_eet.index.equals(choosers.index) + assert not choices_eet.isna().any() + + # Choices should only be 0 or 1 + assert choices_eet.isin([0, 1]).all() + assert not choices_eet.isin([2, 3, 4]).any() diff --git a/activitysim/core/test/test_interaction_simulate.py b/activitysim/core/test/test_interaction_simulate.py index 1505b01a91..6a12595664 100644 --- a/activitysim/core/test/test_interaction_simulate.py +++ b/activitysim/core/test/test_interaction_simulate.py @@ -19,24 +19,23 @@ def state() -> workflow.State: def test_interaction_simulate_explicit_error_terms_parity(state): # Run interaction_simulate with and without explicit error terms and check that results are similar. - # Set up a simple case: 10000 choosers, 5 alternatives for better statistical convergence - num_choosers = 100_000 + # Keep this large enough for stable parity checks without overloading CI. + num_choosers = 20_000 num_alts = 10 sample_size = num_alts # Create random choosers and alternatives - np.random.seed(42) + rng = np.random.default_rng(42) choosers = pd.DataFrame( - {"chooser_attr": np.random.rand(num_choosers)}, + {"chooser_attr": rng.random(num_choosers)}, index=pd.Index(range(num_choosers), name="person_id"), ) alternatives = pd.DataFrame( - {"alt_attr": np.random.rand(num_alts)}, + {"alt_attr": rng.random(num_alts)}, index=pd.Index(range(num_alts), name="alt_id"), ) - # Simple spec: utility = chooser_attr * alt_attr spec = pd.DataFrame( {"coefficient": [1.0]}, index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), @@ -71,6 +70,13 @@ def test_interaction_simulate_explicit_error_terms_parity(state): sample_size=sample_size, ) + assert len(choices_mnl) == num_choosers + assert len(choices_explicit) == num_choosers + assert choices_mnl.index.equals(choosers.index) + assert choices_explicit.index.equals(choosers.index) + assert not choices_mnl.isna().any() + assert not choices_explicit.isna().any() + mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() explicit_counts = choices_explicit.value_counts(normalize=True).sort_index() @@ -78,4 +84,93 @@ def test_interaction_simulate_explicit_error_terms_parity(state): for alt in alternatives.index: share_mnl = mnl_counts.get(alt, 0) share_explicit = explicit_counts.get(alt, 0) - assert abs(share_mnl - share_explicit) < 0.01, f"Large discrepancy at alt {alt}: {share_mnl} vs {share_explicit}" \ No newline at end of file + diff = abs(share_mnl - share_explicit) + assert diff < 0.01, ( + f"Large discrepancy at alt {alt}: " + f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" + ) + +def test_interaction_simulate_eet_unavailable_alternatives(state): + # Test that EET handles unavailable alternatives (very low utilities) + # similarly to MNL (zero probabilities). + + num_choosers = 100 + num_alts = 5 + + choosers = pd.DataFrame( + {"chooser_attr": np.ones(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + # Alt 0 and 1 are attractive, Alt 2, 3, 4 are "unavailable" (very low utility) + alternatives = pd.DataFrame( + {"alt_attr": [10.0, 10.0, -1000.0, -1000.0, -1000.0]}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + # Run with EET + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_unavailable_eet") + + choices_eet = interaction_simulate.interaction_simulate( + state, + choosers, + alternatives, + spec, + sample_size=num_alts, + ) + + assert len(choices_eet) == num_choosers + assert choices_eet.index.equals(choosers.index) + assert not choices_eet.isna().any() + + # Choices should only be from Alt 0 or 1 + assert choices_eet.isin([0, 1]).all(), f"EET picked an 'unavailable' alternative: {choices_eet[~choices_eet.isin([0, 1])]}" + +def test_interaction_simulate_eet_large_utilities(state): + # Test that EET handles very large utilities without overflow issues + # that might occur in exp(util) calculations in standard MNL. + + num_choosers = 10 + num_alts = 2 + + choosers = pd.DataFrame( + {"chooser_attr": np.ones(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + # Standard MNL might struggle with exp(700) or exp(800) depending on float precision + alternatives = pd.DataFrame( + {"alt_attr": [700.0, 800.0]}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_large_utils_eet") + + # This should run without crashing or returning NaNs + choices_eet = interaction_simulate.interaction_simulate( + state, + choosers, + alternatives, + spec, + sample_size=num_alts, + ) + + assert not choices_eet.isna().any() + # With such a large difference, Alt 1 should be the dominant choice + assert (choices_eet == 1).all() diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 231cd4074a..8cabce0d8b 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -562,4 +562,4 @@ def get_rn_generator(): ) interacted, expected = interacted.align(expected, axis=1) - pdt.assert_frame_equal(interacted, expected) + pdt.assert_frame_equal(interacted, expected) \ No newline at end of file From df574eeda8517ea6835b2a5e4852878ac5f23b09 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Wed, 18 Mar 2026 14:35:29 +1000 Subject: [PATCH 142/298] Linting --- .../core/test/test_interaction_sample.py | 13 ++++++------ .../test/test_interaction_sample_simulate.py | 11 +++++----- .../core/test/test_interaction_simulate.py | 20 +++++++++++-------- activitysim/core/test/test_logit.py | 2 +- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 240de2e949..3c093595e1 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -46,7 +46,7 @@ def test_interaction_sample_parity(state): state.rng().set_base_seed(42) state.rng().add_channel("person_id", choosers) state.rng().begin_step("test_step_mnl") - + choices_mnl = interaction_sample.interaction_sample( state, choosers, @@ -57,7 +57,7 @@ def test_interaction_sample_parity(state): ) # Run _with_ explicit error terms - state.init_state() # reset the state to rerun with same seed + state.init_state() # reset the state to rerun with same seed state.settings.use_explicit_error_terms = True state.rng().set_base_seed(42) state.rng().add_channel("person_id", choosers) @@ -82,7 +82,9 @@ def test_interaction_sample_parity(state): # In interaction_sample, choices_explicit and choices_mnl are DataFrames with sampled alternatives. # The statistics of chosen alternatives should be similar. mnl_counts = choices_mnl["alt_id"].value_counts(normalize=True).sort_index() - explicit_counts = choices_explicit["alt_id"].value_counts(normalize=True).sort_index() + explicit_counts = ( + choices_explicit["alt_id"].value_counts(normalize=True).sort_index() + ) # Check top choices overlap significantly or shares are close all_alts = set(mnl_counts.index) | set(explicit_counts.index) @@ -95,13 +97,12 @@ def test_interaction_sample_parity(state): f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" ) + def test_interaction_sample_eet_unavailable_alternatives(state): # Test that EET handles unavailable alternatives in sampling - num_choosers = 100 num_alts = 10 sample_size = 2 - choosers = pd.DataFrame( {"chooser_attr": np.ones(num_choosers)}, index=pd.Index(range(num_choosers), name="person_id"), @@ -109,7 +110,7 @@ def test_interaction_sample_eet_unavailable_alternatives(state): # Alt 0-4 are attractive, Alt 5-9 are "unavailable" alternatives = pd.DataFrame( - {"alt_attr": [10.0]*5 + [-1000.0]*5}, + {"alt_attr": [10.0] * 5 + [-1000.0] * 5}, index=pd.Index(range(num_alts), name="alt_id"), ) diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 0dbf5bd945..3e0aac6cb2 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -20,7 +20,7 @@ def test_interaction_sample_simulate_parity(state): # Run interaction_sample_simulate with and without explicit error terms and check that results are similar. num_choosers = 100_000 - num_alts_per_chooser = 5 # small sample size to keep things simple + num_alts_per_chooser = 5 # small sample size to keep things simple # Create random choosers rng = np.random.default_rng(42) @@ -54,7 +54,7 @@ def test_interaction_sample_simulate_parity(state): state.rng().set_base_seed(42) state.rng().add_channel("person_id", choosers) state.rng().begin_step("test_step_mnl") - + choices_mnl = interaction_sample_simulate.interaction_sample_simulate( state, choosers, @@ -98,12 +98,13 @@ def test_interaction_sample_simulate_parity(state): f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" ) + def test_interaction_sample_simulate_eet_unavailable_alternatives(state): # Test that EET handles unavailable alternatives in sample simulation - + num_choosers = 10 num_alts_per_chooser = 5 - + choosers = pd.DataFrame( {"chooser_attr": np.ones(num_choosers)}, index=pd.Index(range(num_choosers), name="person_id"), @@ -112,7 +113,7 @@ def test_interaction_sample_simulate_eet_unavailable_alternatives(state): # For each chooser, 2 attractive alts, 3 unavailable alt_attrs = [10.0, 10.0, -1000.0, -1000.0, -1000.0] * num_choosers alt_ids = [0, 1, 2, 3, 4] * num_choosers - + alternatives = pd.DataFrame( { "alt_attr": alt_attrs, diff --git a/activitysim/core/test/test_interaction_simulate.py b/activitysim/core/test/test_interaction_simulate.py index 6a12595664..b7635c7fb3 100644 --- a/activitysim/core/test/test_interaction_simulate.py +++ b/activitysim/core/test/test_interaction_simulate.py @@ -46,7 +46,7 @@ def test_interaction_simulate_explicit_error_terms_parity(state): state.rng().set_base_seed(42) # Set seed BEFORE adding channels or steps state.rng().add_channel("person_id", choosers) state.rng().begin_step("test_step_mnl") - + choices_mnl = interaction_simulate.interaction_simulate( state, choosers, @@ -56,7 +56,7 @@ def test_interaction_simulate_explicit_error_terms_parity(state): ) # Run _with_ explicit error terms - state.init_state() # reset the state to rerun with same seed + state.init_state() # reset the state to rerun with same seed state.settings.use_explicit_error_terms = True state.rng().set_base_seed(42) state.rng().add_channel("person_id", choosers) @@ -90,13 +90,14 @@ def test_interaction_simulate_explicit_error_terms_parity(state): f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" ) + def test_interaction_simulate_eet_unavailable_alternatives(state): # Test that EET handles unavailable alternatives (very low utilities) # similarly to MNL (zero probabilities). - + num_choosers = 100 num_alts = 5 - + choosers = pd.DataFrame( {"chooser_attr": np.ones(num_choosers)}, index=pd.Index(range(num_choosers), name="person_id"), @@ -132,15 +133,18 @@ def test_interaction_simulate_eet_unavailable_alternatives(state): assert not choices_eet.isna().any() # Choices should only be from Alt 0 or 1 - assert choices_eet.isin([0, 1]).all(), f"EET picked an 'unavailable' alternative: {choices_eet[~choices_eet.isin([0, 1])]}" + assert choices_eet.isin( + [0, 1] + ).all(), f"EET picked an 'unavailable' alternative: {choices_eet[~choices_eet.isin([0, 1])]}" + def test_interaction_simulate_eet_large_utilities(state): # Test that EET handles very large utilities without overflow issues # that might occur in exp(util) calculations in standard MNL. - + num_choosers = 10 num_alts = 2 - + choosers = pd.DataFrame( {"chooser_attr": np.ones(num_choosers)}, index=pd.Index(range(num_choosers), name="person_id"), @@ -170,7 +174,7 @@ def test_interaction_simulate_eet_large_utilities(state): spec, sample_size=num_alts, ) - + assert not choices_eet.isna().any() # With such a large difference, Alt 1 should be the dominant choice assert (choices_eet == 1).all() diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 8cabce0d8b..231cd4074a 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -562,4 +562,4 @@ def get_rn_generator(): ) interacted, expected = interacted.align(expected, axis=1) - pdt.assert_frame_equal(interacted, expected) \ No newline at end of file + pdt.assert_frame_equal(interacted, expected) From cf92aada878e118c5eaaa9ff212e84112469a698 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Wed, 18 Mar 2026 14:44:41 +1000 Subject: [PATCH 143/298] Normalise number of choosers, alternatives and minimum tolerance for EET comparison tests --- activitysim/core/test/test_interaction_sample.py | 2 +- activitysim/core/test/test_interaction_sample_simulate.py | 2 +- activitysim/core/test/test_interaction_simulate.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 3c093595e1..079067f22c 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -92,7 +92,7 @@ def test_interaction_sample_parity(state): share_mnl = mnl_counts.get(alt, 0) share_explicit = explicit_counts.get(alt, 0) diff = abs(share_mnl - share_explicit) - assert diff < 0.05, ( + assert diff < 0.01, ( f"Large discrepancy at alt {alt}: " f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" ) diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 3e0aac6cb2..202ca95e3e 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -93,7 +93,7 @@ def test_interaction_sample_simulate_parity(state): share_mnl = mnl_counts.get(alt, 0) share_explicit = explicit_counts.get(alt, 0) diff = abs(share_mnl - share_explicit) - assert diff < 0.05, ( + assert diff < 0.01, ( f"Large discrepancy at alt {alt}: " f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" ) diff --git a/activitysim/core/test/test_interaction_simulate.py b/activitysim/core/test/test_interaction_simulate.py index b7635c7fb3..5df7d5968d 100644 --- a/activitysim/core/test/test_interaction_simulate.py +++ b/activitysim/core/test/test_interaction_simulate.py @@ -20,8 +20,8 @@ def test_interaction_simulate_explicit_error_terms_parity(state): # Run interaction_simulate with and without explicit error terms and check that results are similar. # Keep this large enough for stable parity checks without overloading CI. - num_choosers = 20_000 - num_alts = 10 + num_choosers = 100_000 + num_alts = 5 sample_size = num_alts # Create random choosers and alternatives From 73f45fecfda2732c5c03112a0d08125f2e0757d4 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 15:29:59 +1000 Subject: [PATCH 144/298] reshape, do not flatten for potential performance --- activitysim/core/interaction_sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 8b369b18b8..4241fd6935 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -67,7 +67,7 @@ def make_sample_choices_utility_based( rands += utilities.to_numpy()[:, :, np.newaxis] # choose maximum along all alternatives (axis 1) for all choosers and samples - chosen_destinations = np.argmax(rands, axis=1).flatten() + chosen_destinations = np.argmax(rands, axis=1).reshape(-1) chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) del rands chunk_sizer.log_df(trace_label, "rands", None) From 6440d3f9757299103ae4c0588344110a2830bd6d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 15:34:47 +1000 Subject: [PATCH 145/298] undo stray comment --- activitysim/core/logit.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 34a95f7e22..60d75d115e 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -225,9 +225,6 @@ def utils_to_probs( if True value rows in which all utility alts are EXP_UTIL_MIN will result in rows in probs to have all zero probability (and not sum to 1.0) This is for the benefit of calculating probabilities of nested logit nests - When allow_zero_probs is True, overflow protection is disabled (with a warning) - to preserve zero-probability rows. For large float32 utilities, overflow - protection is still enabled and may raise a ValueError. trace_choosers : pandas.dataframe the choosers df (for interaction_simulate) to facilitate the reporting of hh_id From 1df7d0d6080f4c6c2d8e57bdabbda49c42c6e2d1 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 16:02:31 +1000 Subject: [PATCH 146/298] unify mc and eet reporting during choice making --- activitysim/core/logit.py | 90 +++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 60d75d115e..e9f45b4403 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -381,7 +381,13 @@ def choose_from_tree( def make_choices_explicit_error_term_nl( - state, nested_utilities, alt_order_array, nest_spec, trace_label + state, + nested_utilities, + alt_order_array, + nest_spec, + trace_label, + trace_choosers=None, + allow_bad_utils=False, ): """ Walk down the nesting tree and make a choice at each level using EET. @@ -408,11 +414,6 @@ def make_choices_explicit_error_term_nl( nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils") ) nest_utils_for_choice = add_ev1_random(state, nested_utilities) - if trace_label: - state.tracing.trace_df( - nest_utils_for_choice, - tracing.extend_trace_label(trace_label, "nested_utils_eet"), - ) all_alternatives = set(nest.name for nest in each_nest(nest_spec, type="leaf")) logit_nest_groups = group_nest_names_by_level(nest_spec) @@ -429,34 +430,29 @@ def make_choices_explicit_error_term_nl( ), axis=1, ) - missing_choices = choices.isnull() - if missing_choices.any(): + missing_choices = np.isnan(choices) # TODO: should we check for infs here too? + if missing_choices.any() and not allow_bad_utils: report_bad_choices( state, missing_choices, nested_utilities, - trace_label=tracing.extend_trace_label(trace_label, "no_choice"), + trace_label=tracing.extend_trace_label(trace_label, "bad_utils"), msg="no alternative selected", - raise_error=False, + # raise_error=False, + trace_choosers=trace_choosers, ) - assert not missing_choices.any(), f"No choice for {trace_label}" choices = pd.Series(choices, index=nest_utils_for_choice.index) # In order for choice indexing to be consistent with MNL and cumsum MC choices, we need to index in the order # alternatives were originally created before adding nest nodes that are not elemental alternatives choices = choices.map({v: k for k, v in enumerate(alt_order_array)}) - if trace_label: - state.tracing.trace_df( - choices, - tracing.extend_trace_label(trace_label, "choices"), - columns=[None, "choice"], - ) - return choices -def make_choices_explicit_error_term_mnl(state, utilities, trace_label): +def make_choices_explicit_error_term_mnl( + state, utilities, trace_label, trace_choosers=None, allow_bad_utils=False +) -> pd.Series: """ Make EET choices for a multinomial logit model by adding EV1 errors. @@ -484,36 +480,45 @@ def make_choices_explicit_error_term_mnl(state, utilities, trace_label): tracing.extend_trace_label(trace_label, "utilities_eet"), ) choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) - missing_choices = np.isnan(choices) - if missing_choices.any(): + missing_choices = np.isnan(choices) # TODO: should we check for infs here too? + if missing_choices.any() and not allow_bad_utils: report_bad_choices( state, missing_choices, utilities, - trace_label=tracing.extend_trace_label(trace_label, "no_choice"), + trace_label=tracing.extend_trace_label(trace_label, "bad_utils"), msg="no alternative selected", - raise_error=False, + # raise_error=False, + trace_choosers=trace_choosers, ) - assert not missing_choices.any(), f"No choice for {trace_label}" choices = pd.Series(choices, index=utilities_incl_unobs.index) - if trace_label: - state.tracing.trace_df( - choices, - tracing.extend_trace_label(trace_label, "choices"), - columns=[None, "choice"], - ) + return choices def make_choices_explicit_error_term( - state, utilities, alt_order_array, nest_spec=None, trace_label=None -): + state, + utilities, + alt_order_array, + nest_spec=None, + trace_label=None, + trace_choosers=None, + allow_bad_utils=False, +) -> pd.Series: trace_label = tracing.extend_trace_label(trace_label, "make_choices_eet") if nest_spec is None: - choices = make_choices_explicit_error_term_mnl(state, utilities, trace_label) + choices = make_choices_explicit_error_term_mnl( + state, utilities, trace_label, trace_choosers, allow_bad_utils + ) else: choices = make_choices_explicit_error_term_nl( - state, utilities, alt_order_array, nest_spec, trace_label + state, + utilities, + alt_order_array, + nest_spec, + trace_label, + trace_choosers, + allow_bad_utils, ) return choices @@ -525,23 +530,24 @@ def make_choices_utility_based( nest_spec=None, trace_label: str = None, trace_choosers=None, - allow_bad_probs=False, + allow_bad_utils=False, ) -> tuple[pd.Series, pd.Series]: trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") # For nested models, choices are mapped to `name_mapping` ordering inside the # EET helper. For MNL, choices already follow the utilities column order. choices = make_choices_explicit_error_term( - state, utilities, name_mapping, nest_spec, trace_label + state, + utilities, + name_mapping, + nest_spec, + trace_label, + trace_choosers=trace_choosers, + allow_bad_utils=allow_bad_utils, ) # EET does not expose per-row random draws; return zeros for compatibility. rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) - if trace_label: - state.tracing.trace_df( - rands, - tracing.extend_trace_label(trace_label, "rands"), - columns=[None, "rand"], - ) + return choices, rands From 5e9847d9582a7be6b453aa72d708532454d8dfd5 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 16:10:02 +1000 Subject: [PATCH 147/298] series not array --- activitysim/core/logit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index e9f45b4403..bd96c38f4e 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -430,7 +430,7 @@ def make_choices_explicit_error_term_nl( ), axis=1, ) - missing_choices = np.isnan(choices) # TODO: should we check for infs here too? + missing_choices = choices.isnull() # TODO: should we check for infs here too? if missing_choices.any() and not allow_bad_utils: report_bad_choices( state, From 1213b56bb55e38a46c858fc20304d26fb2b3fc0a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 16:27:35 +1000 Subject: [PATCH 148/298] reinstate test, up number of draws for comparison --- activitysim/core/test/test_logit.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 231cd4074a..a3cc36cbda 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -250,6 +250,18 @@ def test_make_choices_only_one(): ) +def test_make_choices_real_probs(utilities): + state = workflow.State().default_settings() + probs = logit.utils_to_probs(state, utilities, trace_label=None) + choices, rands = logit.make_choices(state, probs) + + pdt.assert_series_equal( + choices, + pd.Series([1, 2], index=[0, 1]), + check_dtype=False, + ) + + def test_make_choices_matches_random_draws(): class DummyRNG: def random_for_df(self, df, n=1): @@ -463,7 +475,9 @@ def test_make_choices_vs_eet_same_distribution(): """With many draws, make_choices (probability-based) and make_choices_explicit_error_term_mnl (EET) should produce roughly the same empirical choice-frequency distribution for the same utilities.""" - n_draws = 100_000 + n_draws = 1_000_000 + a_tol = 0.001 + r_tol = 0.01 utils_values = [5.0, 6.0, 7.0, 8.0, 9.0] n_alts = len(utils_values) columns = ["a", "b", "c", "d", "e"] @@ -506,7 +520,13 @@ def get_rn_generator(): mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=n_alts) / n_draws eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=n_alts) / n_draws - np.testing.assert_allclose(mc_fracs, eet_fracs, atol=0.005) + np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol, rtol=r_tol) + np.testing.assert_allclose( + mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol + ) + np.testing.assert_allclose( + eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol + ) @pytest.fixture(scope="module") From fa54204799d4c96d34bc55ec535cc5c7b3805bba Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 16:40:37 +1000 Subject: [PATCH 149/298] numpy not loop --- activitysim/core/test/test_interaction_simulate.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/activitysim/core/test/test_interaction_simulate.py b/activitysim/core/test/test_interaction_simulate.py index 5df7d5968d..db91e5d6a8 100644 --- a/activitysim/core/test/test_interaction_simulate.py +++ b/activitysim/core/test/test_interaction_simulate.py @@ -80,15 +80,10 @@ def test_interaction_simulate_explicit_error_terms_parity(state): mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() explicit_counts = choices_explicit.value_counts(normalize=True).sort_index() - # Check that they aren't wildly different (e.g., within 1% share for each alt) - for alt in alternatives.index: - share_mnl = mnl_counts.get(alt, 0) - share_explicit = explicit_counts.get(alt, 0) - diff = abs(share_mnl - share_explicit) - assert diff < 0.01, ( - f"Large discrepancy at alt {alt}: " - f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" - ) + # Check that they are close, relative to the number of draws + assert np.allclose( + mnl_counts.to_numpy(), explicit_counts.to_numpy(), atol=0.01, rtol=0.001 + ) def test_interaction_simulate_eet_unavailable_alternatives(state): From 1b2b6dcd8821a89e1acf5aad50541bcfa17ca3e4 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 19 Mar 2026 19:55:46 +1000 Subject: [PATCH 150/298] interaction_sample test to catch index order bug --- activitysim/core/simulate.py | 8 +- .../core/test/test_interaction_sample.py | 167 ++++++++++++++++++ 2 files changed, 172 insertions(+), 3 deletions(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index ed0b344528..54609f4065 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -9,7 +9,7 @@ from collections.abc import Callable from datetime import timedelta from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np import pandas as pd @@ -32,7 +32,7 @@ LogitNestSpec, TemplatedLogitComponentSettings, ) -from activitysim.core.estimation import Estimator +from activitysim.core.exceptions import ModelConfigurationError from activitysim.core.fast_eval import fast_eval from activitysim.core.simulate_consts import ( ALT_LOSER_UTIL, @@ -40,7 +40,9 @@ SPEC_EXPRESSION_NAME, SPEC_LABEL_NAME, ) -from activitysim.core.exceptions import ModelConfigurationError + +if TYPE_CHECKING: + from activitysim.core.estimation import Estimator logger = logging.getLogger(__name__) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 079067f22c..73e1723553 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -137,3 +137,170 @@ def test_interaction_sample_eet_unavailable_alternatives(state): # Sampled alternatives should only be from Alt 0-4 assert choices_eet["alt_id"].isin([0, 1, 2, 3, 4]).all() assert not choices_eet["alt_id"].isin([5, 6, 7, 8, 9]).any() + + +def test_interaction_sample_parity_peaked_utilities(state): + # Stress parity under a highly peaked utility profile: + # one dominant alternative, one secondary, and many tiny utilities. + num_choosers = 20_000 + num_alts = 100 + sample_size = 5 + + choosers = pd.DataFrame( + {"chooser_attr": np.ones(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + alt_utils = np.array([10.0, 1.0] + [0.0] * (num_alts - 2), dtype=np.float64) + alternatives = pd.DataFrame( + {"alt_attr": alt_utils}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + # Run non-EET path. + state.settings.use_explicit_error_terms = False + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_peaked_mnl") + choices_mnl = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + ) + + # Run EET path with the same seed. + state.init_state() + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_peaked_explicit") + choices_explicit = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + ) + + def weighted_shares(df: pd.DataFrame) -> pd.Series: + counts = df.groupby("alt_id")["pick_count"].sum() + return (counts / counts.sum()).sort_index() + + mnl_shares = weighted_shares(choices_mnl) + explicit_shares = weighted_shares(choices_explicit) + + all_alts = set(mnl_shares.index) | set(explicit_shares.index) + for alt in all_alts: + diff = abs(mnl_shares.get(alt, 0.0) - explicit_shares.get(alt, 0.0)) + assert diff < 0.005, ( + f"Peaked utility parity mismatch at alt {alt}: " + f"mnl={mnl_shares.get(alt, 0.0):.6f}, " + f"explicit={explicit_shares.get(alt, 0.0):.6f}, diff={diff:.6f}" + ) + + # The dominant alternative should absorb almost all mass in both paths. + assert mnl_shares.get(0, 0.0) > 0.99 + assert explicit_shares.get(0, 0.0) > 0.99 + + +class _DummyChunkSizer: + def log_df(self, *_args, **_kwargs): + return None + + +class _DummyState: + def __init__(self, rng): + self._rng = rng + + def get_rn_generator(self): + return self._rng + + +class _DummyRngUtilityBased: + def __init__(self, rands_3d): + self.rands_3d = rands_3d + + def gumbel_for_df(self, _utilities, n): + assert n == self.rands_3d.shape[1] * self.rands_3d.shape[2] + return self.rands_3d.reshape(-1) + + +def test_make_sample_choices_utility_based_repeat_alignment(monkeypatch): + # Construct a deterministic case where chooser/sample alignment is visible in the output. + # This is a regression test for a bug where the chooser/sample alignment was wrong, causing + # the wrong probabilities to be attached to chosen alternatives. + chooser_index = pd.Index([10, 20, 30], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([100, 101, 102, 103], name="alt_id")) + + n_choosers = len(choosers) + n_alts = len(alternatives) + sample_size = 2 + + utilities = pd.DataFrame( + np.zeros((n_choosers, n_alts)), + index=chooser_index, + ) + + # Winner alternatives by chooser x sample. + winners = np.array( + [ + [0, 1], + [2, 3], + [1, 0], + ], + dtype=np.int64, + ) + + # Build gumbel draws so argmax along alternatives yields the winners above. + rands_3d = np.full((n_choosers, n_alts, sample_size), -1000.0) + for i in range(n_choosers): + for s in range(sample_size): + rands_3d[i, winners[i, s], s] = 1000.0 + + # Encode chooser/alt identity in probabilities so bad indexing is obvious. + probs_df = pd.DataFrame( + [ + [0, 1, 2, 3], + [10, 11, 12, 13], + [20, 21, 22, 23], + ], + index=chooser_index, + ) + + monkeypatch.setattr( + interaction_sample.logit, "utils_to_probs", lambda *_a, **_k: probs_df + ) + + state = _DummyState(_DummyRngUtilityBased(rands_3d)) + out = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=n_alts, + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_repeat_alignment", + chunk_sizer=_DummyChunkSizer(), + ) + + chosen_flat = winners.reshape(-1) + chooser_repeat = np.repeat(np.arange(n_choosers), sample_size) + chooser_tile = np.tile(np.arange(n_choosers), sample_size) + + expected_prob_repeat = probs_df.to_numpy()[chooser_repeat, chosen_flat] + wrong_prob_tile = probs_df.to_numpy()[chooser_tile, chosen_flat] + + assert np.array_equal(out["prob"].to_numpy(), expected_prob_repeat) + assert not np.array_equal(out["prob"].to_numpy(), wrong_prob_tile) From 4e0c7e8dbdd58589b938e3278bdbda6cc7bf8c38 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 20 Mar 2026 14:08:37 +1000 Subject: [PATCH 151/298] make test clearer --- .../core/test/test_interaction_sample.py | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 73e1723553..b4bc3c77f2 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -234,54 +234,31 @@ def gumbel_for_df(self, _utilities, n): return self.rands_3d.reshape(-1) -def test_make_sample_choices_utility_based_repeat_alignment(monkeypatch): - # Construct a deterministic case where chooser/sample alignment is visible in the output. - # This is a regression test for a bug where the chooser/sample alignment was wrong, causing - # the wrong probabilities to be attached to chosen alternatives. - chooser_index = pd.Index([10, 20, 30], name="person_id") +def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_heterogeneity(): + # Edge case: utilities are close across alternatives but vary strongly by chooser. + # This is where wrong chooser/sample alignment can hide in aggregate checks. + chooser_index = pd.Index([101, 102, 103, 104, 105, 106], name="person_id") choosers = pd.DataFrame(index=chooser_index) - alternatives = pd.DataFrame(index=pd.Index([100, 101, 102, 103], name="alt_id")) + alternatives = pd.DataFrame(index=pd.Index([0, 1, 2, 3], name="alt_id")) n_choosers = len(choosers) n_alts = len(alternatives) - sample_size = 2 - - utilities = pd.DataFrame( - np.zeros((n_choosers, n_alts)), - index=chooser_index, - ) + sample_size = 3 - # Winner alternatives by chooser x sample. - winners = np.array( - [ - [0, 1], - [2, 3], - [1, 0], - ], - dtype=np.int64, - ) + # Very small alternative differences... + alt_signal = np.array([0.00, 0.01, 0.02, 0.03], dtype=np.float64) + # ...but very large chooser sensitivity differences. + chooser_scale = np.array([-500.0, -200.0, -50.0, 50.0, 200.0, 500.0]) - # Build gumbel draws so argmax along alternatives yields the winners above. - rands_3d = np.full((n_choosers, n_alts, sample_size), -1000.0) - for i in range(n_choosers): - for s in range(sample_size): - rands_3d[i, winners[i, s], s] = 1000.0 - - # Encode chooser/alt identity in probabilities so bad indexing is obvious. - probs_df = pd.DataFrame( - [ - [0, 1, 2, 3], - [10, 11, 12, 13], - [20, 21, 22, 23], - ], + utilities = pd.DataFrame( + chooser_scale[:, np.newaxis] * alt_signal[np.newaxis, :], index=chooser_index, ) - monkeypatch.setattr( - interaction_sample.logit, "utils_to_probs", lambda *_a, **_k: probs_df - ) - + # No random noise: chosen alternative is deterministic argmax of utilities. + rands_3d = np.zeros((n_choosers, n_alts, sample_size), dtype=np.float64) state = _DummyState(_DummyRngUtilityBased(rands_3d)) + out = interaction_sample.make_sample_choices_utility_based( state=state, choosers=choosers, @@ -291,16 +268,31 @@ def test_make_sample_choices_utility_based_repeat_alignment(monkeypatch): alternative_count=n_alts, alt_col_name="alt_id", allow_zero_probs=False, - trace_label="test_repeat_alignment", + trace_label="test_repeat_alignment_chooser_heterogeneity", chunk_sizer=_DummyChunkSizer(), ) - chosen_flat = winners.reshape(-1) + # Reconstruct expected indexing behavior. + chosen_2d = np.argmax( + rands_3d + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) + chosen_flat = chosen_2d.reshape(-1) + chooser_repeat = np.repeat(np.arange(n_choosers), sample_size) chooser_tile = np.tile(np.arange(n_choosers), sample_size) - expected_prob_repeat = probs_df.to_numpy()[chooser_repeat, chosen_flat] - wrong_prob_tile = probs_df.to_numpy()[chooser_tile, chosen_flat] + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_repeat_alignment_chooser_heterogeneity", + overflow_protection=True, + trace_choosers=choosers, + ).to_numpy() + + expected_prob_repeat = probs[chooser_repeat, chosen_flat] + wrong_prob_tile = probs[chooser_tile, chosen_flat] assert np.array_equal(out["prob"].to_numpy(), expected_prob_repeat) assert not np.array_equal(out["prob"].to_numpy(), wrong_prob_tile) From 1aa85ba4b8e3a0f06d34bf705c6f89b34a746deb Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 22 Mar 2026 16:36:23 +1000 Subject: [PATCH 152/298] reset rng offset on iterate_location_choice (shadow pricing) --- activitysim/abm/models/location_choice.py | 6 +++++- activitysim/core/random.py | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 7f032a8ae6..b76ecf83bb 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -15,10 +15,10 @@ TourLocationComponentSettings, TourModeComponentSettings, ) +from activitysim.core.exceptions import DuplicateWorkflowTableError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.util import reindex -from activitysim.core.exceptions import DuplicateWorkflowTableError """ The school/workplace location model predicts the zones in which various people will @@ -1019,6 +1019,10 @@ def iterate_location_choice( ) = None # initialize to None, will be populated in first iteration for iteration in range(1, max_iterations + 1): + # RESET RNG offsets to identical state on each iteration. This ensures that the same set of random numbers is + # used on each iteration. + state.get_rn_generator().reset_offsets_for_step(state.current_model_name) + persons_merged_df_ = persons_merged_df.copy() if spc.use_shadow_pricing and iteration > 1: diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 5541fcd41d..b47b2d22df 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -9,8 +9,8 @@ import numpy as np import pandas as pd -from activitysim.core.util import reindex from activitysim.core.exceptions import DuplicateLoadableObjectError, TableIndexError +from activitysim.core.util import reindex from .tracing import print_elapsed_time @@ -447,6 +447,21 @@ def get_channel_for_df(self, df): # step handling + def reset_offsets_for_step(self, step_name): + """ + Reset offsets for all channels for a new step + + Parameters + ---------- + step_name : str + pipeline step name for this step + """ + + assert self.step_name == step_name + + for c in self.channels: + self.channels[c].row_states["offset"] = 0 + def begin_step(self, step_name): """ Register that the pipeline has entered a new step and that global and channel streams From bd4211f6e638a364b5ae44c56374e6f1af7747b4 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Mon, 23 Mar 2026 16:38:56 +1000 Subject: [PATCH 153/298] Add tests for logit NL, ordering, and all models using EET --- activitysim/abm/models/util/test/test_cdap.py | 96 ++++++--- .../joint_tour_participation.csv | 2 + .../joint_tour_participation.yaml | 5 + .../joint_tour_participation_coefficients.csv | 2 + .../test_joint_tour_participation.py | 146 +++++++++++++ .../test_misc/test_trip_departure_choice.py | 65 +++++- activitysim/core/test/test_logit.py | 201 ++++++++++++++---- 7 files changed, 452 insertions(+), 65 deletions(-) create mode 100644 activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.csv create mode 100644 activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.yaml create mode 100644 activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation_coefficients.csv create mode 100644 activitysim/abm/test/test_misc/test_joint_tour_participation.py diff --git a/activitysim/abm/models/util/test/test_cdap.py b/activitysim/abm/models/util/test/test_cdap.py index 20dc6b2410..d7eeab8886 100644 --- a/activitysim/abm/models/util/test/test_cdap.py +++ b/activitysim/abm/models/util/test/test_cdap.py @@ -5,6 +5,7 @@ import os.path +import numpy as np import pandas as pd import pandas.testing as pdt import pytest @@ -59,20 +60,14 @@ def test_assign_cdap_rank(people, model_settings): with chunk.chunk_log(state, "test_assign_cdap_rank", base=True): cdap.assign_cdap_rank(state, people, person_type_map) - expected = pd.Series( - [1, 1, 1, 2, 2, 1, 3, 1, 2, 1, 3, 2, 1, 3, 2, 4, 1, 3, 4, 2], index=people.index - ) + expected = pd.Series([1, 1, 1, 2, 2, 1, 3, 1, 2, 1, 3, 2, 1, 3, 2, 4, 1, 3, 4, 2], index=people.index) - pdt.assert_series_equal( - people["cdap_rank"], expected, check_dtype=False, check_names=False - ) + pdt.assert_series_equal(people["cdap_rank"], expected, check_dtype=False, check_names=False) def test_individual_utilities(people, model_settings): state = workflow.State.make_default(__file__) - cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec( - file_name="cdap_indiv_and_hhsize1.csv" - ) + cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec(file_name="cdap_indiv_and_hhsize1.csv") person_type_map = model_settings.get("PERSON_TYPE_MAP", {}) @@ -115,31 +110,23 @@ def test_individual_utilities(people, model_settings): columns=cdap_indiv_and_hhsize1.columns, ) - pdt.assert_frame_equal( - individual_utils, expected, check_dtype=False, check_names=False - ) + pdt.assert_frame_equal(individual_utils, expected, check_dtype=False, check_names=False) def test_build_cdap_spec_hhsize2(people, model_settings): state = workflow.State.make_default(__file__) hhsize = 2 - cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec( - file_name="cdap_indiv_and_hhsize1.csv" - ) + cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec(file_name="cdap_indiv_and_hhsize1.csv") interaction_coefficients = pd.read_csv( state.filesystem.get_config_file_path("cdap_interaction_coefficients.csv"), comment="#", ) - interaction_coefficients = cdap.preprocess_interaction_coefficients( - interaction_coefficients - ) + interaction_coefficients = cdap.preprocess_interaction_coefficients(interaction_coefficients) person_type_map = model_settings.get("PERSON_TYPE_MAP", {}) - with chunk.chunk_log( - state, "test_build_cdap_spec_hhsize2", base=True - ) as chunk_sizer: + with chunk.chunk_log(state, "test_build_cdap_spec_hhsize2", base=True) as chunk_sizer: cdap.assign_cdap_rank(state, people, person_type_map) indiv_utils = cdap.individual_utilities( state, @@ -151,9 +138,7 @@ def test_build_cdap_spec_hhsize2(people, model_settings): choosers = cdap.hh_choosers(state, indiv_utils, hhsize=hhsize) - spec = cdap.build_cdap_spec( - state, interaction_coefficients, hhsize=hhsize, cache=False - ) + spec = cdap.build_cdap_spec(state, interaction_coefficients, hhsize=hhsize, cache=False) # pandas.dot depends on column names of expression_values matching spec index values # expressions should have been uniquified when spec was read @@ -176,3 +161,66 @@ def test_build_cdap_spec_hhsize2(people, model_settings): ).astype("float") pdt.assert_frame_equal(utils, expected, check_names=False) + + +def test_cdap_explicit_error_terms_parity(people, model_settings): + person_type_map = model_settings.get("PERSON_TYPE_MAP", {}) + + # Increase population to get more stable distribution for parity check + # We'll just duplicate the existing people a few times + large_people = pd.concat([people] * 500).reset_index(drop=True) + large_people.index.name = "person_id" + # Need to ensure household IDs are updated so they are distinct + large_people["household_id"] = large_people.groupby("household_id").cumcount() * 1000 + large_people["household_id"] + large_people = large_people.sort_values("household_id") + + # Run without explicit error terms + state_no_eet = workflow.State.make_default(__file__) + cdap_indiv_spec = state_no_eet.filesystem.read_model_spec(file_name="cdap_indiv_and_hhsize1.csv") + interaction_coefficients = pd.read_csv( + state_no_eet.filesystem.get_config_file_path("cdap_interaction_coefficients.csv"), + comment="#", + ) + interaction_coefficients = cdap.preprocess_interaction_coefficients(interaction_coefficients) + cdap_fixed_relative_proportions = pd.DataFrame({"activity": ["M", "N", "H"], "coefficient": [0.33, 0.33, 0.34]}) + + state_no_eet.settings.use_explicit_error_terms = False + state_no_eet.rng().set_base_seed(42) + state_no_eet.rng().begin_step("test_no_eet") + state_no_eet.rng().add_channel("person_id", large_people) + state_no_eet.rng().add_channel("household_id", large_people.drop_duplicates("household_id").set_index("household_id")) + + choices_no_eet = cdap.run_cdap( + state_no_eet, + large_people, + person_type_map, + cdap_indiv_spec, + interaction_coefficients, + cdap_fixed_relative_proportions, + locals_d=None, + ) + + # Run with explicit error terms + state_eet = workflow.State.make_default(__file__) + state_eet.settings.use_explicit_error_terms = True + state_eet.rng().set_base_seed(42) + state_eet.rng().begin_step("test_eet") + state_eet.rng().add_channel("person_id", large_people) + state_eet.rng().add_channel("household_id", large_people.drop_duplicates("household_id").set_index("household_id")) + + choices_eet = cdap.run_cdap( + state_eet, + large_people, + person_type_map, + cdap_indiv_spec, + interaction_coefficients, + cdap_fixed_relative_proportions, + locals_d=None, + ) + + # Compare distributions + dist_no_eet = choices_no_eet.value_counts(normalize=True).sort_index() + dist_eet = choices_eet.value_counts(normalize=True).sort_index() + + # Check that they are reasonably close + pdt.assert_series_equal(dist_no_eet, dist_eet, atol=0.05, check_names=False) diff --git a/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.csv b/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.csv new file mode 100644 index 0000000000..d81df1ab13 --- /dev/null +++ b/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.csv @@ -0,0 +1,2 @@ +Description,Expression,participate,not_participate +Adult participation,adult,0.5,-0.5 diff --git a/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.yaml b/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.yaml new file mode 100644 index 0000000000..8db2410c08 --- /dev/null +++ b/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation.yaml @@ -0,0 +1,5 @@ +SPEC: joint_tour_participation.csv +COEFFICIENTS: joint_tour_participation_coefficients.csv +participation_choice: participate +max_participation_choice_iterations: 100 +FORCE_PARTICIPATION: True diff --git a/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation_coefficients.csv b/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation_coefficients.csv new file mode 100644 index 0000000000..237d519172 --- /dev/null +++ b/activitysim/abm/test/test_misc/configs_test_misc/joint_tour_participation_coefficients.csv @@ -0,0 +1,2 @@ +expression,coefficient +adult,1.0 diff --git a/activitysim/abm/test/test_misc/test_joint_tour_participation.py b/activitysim/abm/test/test_misc/test_joint_tour_participation.py new file mode 100644 index 0000000000..9737934497 --- /dev/null +++ b/activitysim/abm/test/test_misc/test_joint_tour_participation.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import numpy as np +import pandas as pd +import pandas.testing as pdt +import pytest + +from activitysim.abm.models import joint_tour_participation +from activitysim.core import logit, workflow + +from .test_trip_departure_choice import add_canonical_dirs + + +@pytest.fixture +def candidates(): + # Create synthetic candidates for Joint Tour Participation + # JTP chooses whether each candidate participates in a joint tour. + # We include varied compositions and preschoolers to exercise the + # get_tour_satisfaction logic properly. + num_tours_per_comp = 500 + compositions = ["MIXED", "ADULTS", "CHILDREN"] + num_candidates_per_tour = 4 + + total_tours = num_tours_per_comp * len(compositions) + num_candidates = total_tours * num_candidates_per_tour + + # Ensure reproducibility + rng = np.random.default_rng(42) + + tour_ids = np.repeat(np.arange(total_tours), num_candidates_per_tour) + comp_values = np.repeat(compositions, num_tours_per_comp * num_candidates_per_tour) + + df = pd.DataFrame( + { + "tour_id": tour_ids, + "household_id": tour_ids, # simplified for mock + "person_id": np.arange(num_candidates), + "composition": comp_values, + }, + index=pd.Index(np.arange(num_candidates), name="participant_id"), + ) + + # Assign adult and preschooler status based on composition + # MIXED: at least one adult and one child + # ADULTS: all adults + # CHILDREN: all children + df["adult"] = False + df["person_is_preschool"] = False + + for i, comp in enumerate(compositions): + mask = df.composition == comp + indices = df[mask].index + + if comp == "ADULTS": + df.loc[indices, "adult"] = True + elif comp == "CHILDREN": + df.loc[indices, "adult"] = False + # Some children are preschoolers + df.loc[rng.choice(indices, len(indices) // 4, replace=False), "person_is_preschool"] = True + elif comp == "MIXED": + # For each tour, make the first person an adult, rest children + tour_start_indices = indices[::num_candidates_per_tour] + df.loc[tour_start_indices, "adult"] = True + # Other members are children, some might be preschoolers + other_indices = indices[~indices.isin(tour_start_indices)] + df.loc[rng.choice(other_indices, len(other_indices) // 3, replace=False), "person_is_preschool"] = True + + return df + + +@pytest.fixture +def model_spec(): + # Simple spec with two alternatives: 'participate' and 'not_participate' + return pd.DataFrame( + {"participate": [0.8, -0.2], "not_participate": [0.0, 0.0]}, + index=pd.Index(["adult", "person_is_preschool"], name="Expression"), + ) + + +def test_jtp_explicit_error_terms_parity(candidates, model_spec): + """ + Test that joint tour participation results are statistically similar + between MNL and Explicit Error Terms (EET) using realistic candidate scenarios. + """ + # Create random utilities for the candidates that vary by attribute + rng = np.random.default_rng(42) + + # Base utility + some noise + base_util = (candidates.adult * 0.5) - (candidates.person_is_preschool * 1.0) + utils = pd.DataFrame( + { + "participate": base_util + rng.standard_normal(len(candidates)), + "not_participate": 0, + }, + index=candidates.index, + ) + + # Run without EET (MNL) + state_no_eet = add_canonical_dirs("configs_test_misc").default_settings() + state_no_eet.settings.use_explicit_error_terms = False + state_no_eet.rng().set_base_seed(42) + state_no_eet.rng().begin_step("test_no_eet") + state_no_eet.rng().add_channel("participant_id", candidates) + + # MNL path expects probabilities + probs_no_eet = logit.utils_to_probs(state_no_eet, utils, trace_label="test_no_eet") + choices_no_eet, _ = joint_tour_participation.participants_chooser( + state_no_eet, + probs_no_eet, + candidates, + model_spec, + trace_label="test_no_eet", + ) + + # Run with EET + state_eet = add_canonical_dirs("configs_test_misc").default_settings() + state_eet.settings.use_explicit_error_terms = True + state_eet.rng().set_base_seed(42) + state_eet.rng().begin_step("test_eet") + state_eet.rng().add_channel("participant_id", candidates) + + # EET path expects raw utilities + choices_eet, _ = joint_tour_participation.participants_chooser( + state_eet, + utils.copy(), + candidates, + model_spec, + trace_label="test_eet", + ) + + # Compare distributions of number of participants per tour + # Choice 0 is 'participate' + no_eet_participation_counts = (choices_no_eet == 0).groupby(candidates.tour_id).sum() + eet_participation_counts = (choices_eet == 0).groupby(candidates.tour_id).sum() + + dist_no_eet = no_eet_participation_counts.value_counts(normalize=True).sort_index() + dist_eet = eet_participation_counts.value_counts(normalize=True).sort_index() + + # Check that the distribution of participation counts is close + pdt.assert_series_equal(dist_no_eet, dist_eet, atol=0.05, check_names=False) + + # Also check average participation by composition for deeper parity check + comp_parity_no_eet = no_eet_participation_counts.groupby(candidates.groupby("tour_id")["composition"].first()).mean() + comp_parity_eet = eet_participation_counts.groupby(candidates.groupby("tour_id")["composition"].first()).mean() + + pdt.assert_series_equal(comp_parity_no_eet, comp_parity_eet, atol=0.1, check_names=False) diff --git a/activitysim/abm/test/test_misc/test_trip_departure_choice.py b/activitysim/abm/test/test_misc/test_trip_departure_choice.py index 94d47f57ac..f23b67194c 100644 --- a/activitysim/abm/test/test_misc/test_trip_departure_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_departure_choice.py @@ -1,7 +1,10 @@ +from __future__ import annotations + +import os + import numpy as np import pandas as pd import pytest -import os import activitysim.abm.models.trip_departure_choice as tdc from activitysim.abm.models.util.trip import get_time_windows @@ -144,10 +147,7 @@ def test_build_patterns(trips): def test_get_tour_legs(trips): tour_legs = tdc.get_tour_legs(trips) assert tour_legs.index.name == tdc.TOUR_LEG_ID - assert ( - np.unique(tour_legs[tdc.TOUR_ID].values).shape[0] - == np.unique(trips[tdc.TOUR_ID].values).shape[0] - ) + assert np.unique(tour_legs[tdc.TOUR_ID].values).shape[0] == np.unique(trips[tdc.TOUR_ID].values).shape[0] def test_generate_alternative(trips): @@ -187,3 +187,58 @@ def test_apply_stage_two_model(model_spec, trips): pd.testing.assert_index_equal(departures.index, trips.index) departures = pd.concat([trips, departures], axis=1) + + +def test_tdc_explicit_error_terms_parity(model_spec, trips): + setup_dirs() + model_settings = tdc.TripDepartureChoiceSettings() + + # Increase population for statistical convergence + large_trips = pd.concat([trips] * 500).reset_index(drop=True) + large_trips.index.name = "trip_id" + # Ensure tour_ids are distinct for the expanded set + large_trips["tour_id"] = large_trips.groupby("tour_id").cumcount() * 1000 + large_trips["tour_id"] + + # Trip departure choice uses tour_leg_id as the random channel index + tour_legs = tdc.get_tour_legs(large_trips) + + # Run without explicit error terms + state_no_eet = add_canonical_dirs("configs_test_misc").default_settings() + state_no_eet.settings.use_explicit_error_terms = False + state_no_eet.rng().set_base_seed(42) + state_no_eet.rng().begin_step("test_no_eet") + state_no_eet.rng().add_channel("trip_id", large_trips) + state_no_eet.rng().add_channel("tour_leg_id", tour_legs) + + departures_no_eet = tdc.apply_stage_two_model( + state_no_eet, + model_spec, + large_trips, + 0, + "TEST Trip Departure No EET", + model_settings=model_settings, + ) + + # Run with explicit error terms + state_eet = add_canonical_dirs("configs_test_misc").default_settings() + state_eet.settings.use_explicit_error_terms = True + state_eet.rng().set_base_seed(42) + state_eet.rng().begin_step("test_eet") + state_eet.rng().add_channel("trip_id", large_trips) + state_eet.rng().add_channel("tour_leg_id", tour_legs) + + departures_eet = tdc.apply_stage_two_model( + state_eet, + model_spec, + large_trips, + 0, + "TEST Trip Departure EET", + model_settings=model_settings, + ) + + # Compare distributions + dist_no_eet = departures_no_eet.value_counts(normalize=True).sort_index() + dist_eet = departures_eet.value_counts(normalize=True).sort_index() + + # Check that they are reasonably close (within 5% for this sample size) + pd.testing.assert_series_equal(dist_no_eet, dist_eet, atol=0.05, check_names=False) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index a3cc36cbda..0f2dc9abf2 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -9,7 +9,7 @@ import pandas.testing as pdt import pytest -from activitysim.core import logit, workflow +from activitysim.core import logit, simulate, workflow from activitysim.core.exceptions import InvalidTravelError from activitysim.core.simulate import eval_variables @@ -26,9 +26,7 @@ def data_dir(): ( "fish.csv", "fish_choosers.csv", - pd.DataFrame( - [[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"] - ), + pd.DataFrame([[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"]), pd.DataFrame( [ [0.2849598, 0.2742482, 0.1605457, 0.2802463], @@ -71,6 +69,19 @@ def utilities(choosers, spec, test_data): ) +@pytest.fixture(scope="module") +def interaction_choosers(): + return pd.DataFrame({"attr": ["a", "b", "c", "b"]}, index=["w", "x", "y", "z"]) + + +@pytest.fixture(scope="module") +def interaction_alts(): + return pd.DataFrame({"prop": [10, 20, 30, 40]}, index=[1, 2, 3, 4]) + + +# +# Utility Validation Tests +# def test_validate_utils_replaces_unavailable_values(): state = workflow.State().default_settings() utils = pd.DataFrame([[0.0, logit.UTIL_MIN - 1.0], [1.0, 2.0]]) @@ -112,6 +123,9 @@ def test_validate_utils_does_not_mutate_input(): pdt.assert_frame_equal(utils, original) +# +# `utils_to_probs` Tests +# def test_utils_to_probs_logsums_with_overflow_protection(): state = workflow.State().default_settings() utils = pd.DataFrame( @@ -238,16 +252,15 @@ def test_utils_to_probs_raises(): assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0])) +# +# `make_choices` Tests +# def test_make_choices_only_one(): state = workflow.State().default_settings() - probs = pd.DataFrame( - [[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"] - ) + probs = pd.DataFrame([[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"]) choices, rands = logit.make_choices(state, probs) - pdt.assert_series_equal( - choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False - ) + pdt.assert_series_equal(choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False) def test_make_choices_real_probs(utilities): @@ -262,6 +275,43 @@ def test_make_choices_real_probs(utilities): ) +def test_different_order_make_choices(): + # check if, when we shuffle utilities, make_choices chooses the same alternatives + state = workflow.State().default_settings() + + # increase number of choosers and alternatives for realism + n_choosers = 100 + n_alts = 50 + data = np.random.rand(n_choosers, n_alts) + chooser_ids = np.arange(n_choosers) + alt_ids = [f"alt_{i}" for i in range(n_alts)] + + utilities = pd.DataFrame( + data, + index=pd.Index(chooser_ids, name="chooser_id"), + columns=alt_ids, + ) + + # We need a stable RNG that gives the same random numbers for the same chooser_id + # regardless of row order. ActivitySim's random.Random does this. + state.get_rn_generator().add_channel("chooser_id", utilities) + state.get_rn_generator().begin_step("test_step") + + probs = logit.utils_to_probs(state, utilities, trace_label=None) + choices, rands = logit.make_choices(state, probs) + + # shuffle utilities (rows) and make_choices again + # We must reset the step offset so the RNG produces the same sequence for the same IDs + state.get_rn_generator().end_step("test_step") + state.get_rn_generator().begin_step("test_step") + utilities_shuffled = utilities.sample(frac=1, random_state=42) + probs_shuffled = logit.utils_to_probs(state, utilities_shuffled, trace_label=None) + choices_shuffled, rands_shuffled = logit.make_choices(state, probs_shuffled) + + # sorting both to ensure comparison is on the same index order + pdt.assert_series_equal(choices.sort_index(), choices_shuffled.sort_index(), check_dtype=False) + + def test_make_choices_matches_random_draws(): class DummyRNG: def random_for_df(self, df, n=1): @@ -296,6 +346,9 @@ def get_rn_generator(): ) +# +# EV1 Random Tests +# def test_add_ev1_random(): class DummyRNG: def gumbel_for_df(self, df, n): @@ -340,6 +393,9 @@ def get_rn_generator(): ) +# +# Nested Logit Structure Tests +# def test_group_nest_names_by_level(): nest_spec = { "name": "root", @@ -371,9 +427,7 @@ def test_choose_from_tree_selects_leaf(): "motorized": ["car", "bus"], } - choice = logit.choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name - ) + choice = logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) assert choice == "car" @@ -388,11 +442,12 @@ def test_choose_from_tree_raises_on_missing_leaf(): } with pytest.raises(ValueError, match="no alternative found"): - logit.choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name - ) + logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) +# +# EET Choice Behavior Tests +# def test_make_choices_eet_mnl(monkeypatch): def fake_add_ev1_random(_state, _df): return pd.DataFrame( @@ -471,6 +526,9 @@ def fake_add_ev1_random(_state, df): pdt.assert_series_equal(rands, pd.Series([0, 0], index=[11, 12])) +# +# EET vs non-EET Choice Behavior Tests +# def test_make_choices_vs_eet_same_distribution(): """With many draws, make_choices (probability-based) and make_choices_explicit_error_term_mnl (EET) should produce roughly the @@ -496,9 +554,7 @@ class MCDummyState: def get_rn_generator(): return MCDummyRNG() - probs = logit.utils_to_probs( - MCDummyState(), utils, trace_label=None, overflow_protection=True - ) + probs = logit.utils_to_probs(MCDummyState(), utils, trace_label=None, overflow_protection=True) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) # Explicit-error-term (EET) path — independent RNG @@ -513,32 +569,107 @@ class EETDummyState: def get_rn_generator(): return EETDummyRNG() - choices_eet = logit.make_choices_explicit_error_term_mnl( - EETDummyState(), utils, trace_label=None - ) + choices_eet = logit.make_choices_explicit_error_term_mnl(EETDummyState(), utils, trace_label=None) mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=n_alts) / n_draws eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=n_alts) / n_draws np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol, rtol=r_tol) - np.testing.assert_allclose( - mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol - ) - np.testing.assert_allclose( - eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol + np.testing.assert_allclose(mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) + np.testing.assert_allclose(eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) + + +def test_make_choices_vs_eet_nl_same_distribution(): + """With many draws, nested logit choices via probabilities and + nested logit choices via EET should produce the same empirical distribution.""" + n_draws = 100_000 + a_tol = 0.01 + + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "motorized", "coefficient": 0.5, "alternatives": ["car", "bus"]}, + "walk", + ], + } + # Utilities for car, bus, walk + # For NL, we need utilities for all nodes in the tree for EET, + # but for probability-based choice we usually use the flattened/logsummed probabilities. + # To compare them fairly, we use the same base utilities. + # car=0.5, bus=0.2, walk=0.4 + utils_df = pd.DataFrame( + [[0.5, 0.2, 0.4, 0.0, 0.0]], + columns=["car", "bus", "walk", "motorized", "root"], ) + utils_df = pd.concat([utils_df] * n_draws, ignore_index=True) + alt_order_array = np.array(["car", "bus", "walk"]) + # 1. Probability-based Nested Logit choices + mc_rng = np.random.default_rng(42) -@pytest.fixture(scope="module") -def interaction_choosers(): - return pd.DataFrame({"attr": ["a", "b", "c", "b"]}, index=["w", "x", "y", "z"]) + class MCDummyRNG: + def random_for_df(self, df, n=1): + return mc_rng.random((len(df), n)) + class MCDummyState: + @staticmethod + def get_rn_generator(): + return MCDummyRNG() -@pytest.fixture(scope="module") -def interaction_alts(): - return pd.DataFrame({"prop": [10, 20, 30, 40]}, index=[1, 2, 3, 4]) + def default_settings(self): + return self + + # Compute probabilities for NL using simulation logic + nested_exp_utilities = simulate.compute_nested_exp_utilities(utils_df[["car", "bus", "walk"]], nest_spec) + nested_probabilities = simulate.compute_nested_probabilities(MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None) + probs = simulate.compute_base_probabilities(nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]]) + choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) + + # 2. EET-based Nested Logit choices + eet_rng = np.random.default_rng(123) + + class EETDummyRNG: + def gumbel_for_df(self, df, n): + return eet_rng.gumbel(size=(len(df), n)) + + class EETDummyState: + @staticmethod + def get_rn_generator(): + return EETDummyRNG() + + def default_settings(self): + return self + + @property + def tracing(self): + import activitysim.core.tracing as tracing + + return tracing + # For EET NL, we provide the utilities for all nodes. + # compute_nested_utilities handles the division by nesting coefficients for leaves + # and the logsum * coefficient for internal nodes. + nested_utilities = simulate.compute_nested_utilities(utils_df[["car", "bus", "walk"]], nest_spec) + choices_eet = logit.make_choices_explicit_error_term_nl( + EETDummyState(), + nested_utilities, + alt_order_array, + nest_spec, + trace_label=None, + ) + + mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=3) / n_draws + eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=3) / n_draws + + # They should be close + np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol) + + +# +# Interaction Dataset Tests +# def test_interaction_dataset_no_sample(interaction_choosers, interaction_alts): expected = pd.DataFrame( { @@ -548,9 +679,7 @@ def test_interaction_dataset_no_sample(interaction_choosers, interaction_alts): index=[1, 2, 3, 4] * 4, ) - interacted = logit.interaction_dataset( - workflow.State().default_settings(), interaction_choosers, interaction_alts - ) + interacted = logit.interaction_dataset(workflow.State().default_settings(), interaction_choosers, interaction_alts) interacted, expected = interacted.align(expected, axis=1) pdt.assert_frame_equal(interacted, expected) From 76cacb2242e7d18fb4b8b6d720919e21aab139f2 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Mon, 23 Mar 2026 17:19:37 +1000 Subject: [PATCH 154/298] Add basic docs for EET --- docs/core.rst | 10 ++++++++++ docs/users-guide/ways_to_run.rst | 17 ++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/core.rst b/docs/core.rst index 687e8f9565..e5a2c2c293 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -323,6 +323,16 @@ To specify and solve an NL model: * specify the nesting structure via the NESTS setting in the model configuration YAML file. An example nested logit NESTS entry can be found in ``example/configs/tour_mode_choice.yaml`` * call ``simulate.simple_simulate()``. The ``simulate.interaction_simulate()`` functionality is not yet supported for NL. +Explicit Error Terms +^^^^^^^^^^^^^^^^^^^^ + +By default, ActivitySim uses analytical probabilities to make choices. Alternatively, users can enable +``use_explicit_error_terms: True`` in the model settings. In this mode, unobserved utility components +are drawn directly from the Gumbel distribution (EV1) and added to the systematic utilities. The +alternative with the highest total utility is then selected. This approach can be useful for +reducing simulation noise and improving consistency, particularly when comparing scenarios where +only a subset of alternatives has changed. + API ^^^ diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 1b21221078..3e5c159b12 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -80,7 +80,7 @@ Refer to the :ref:`Run the Primary Example` section to learn how to run the prim Using Jupyter Notebook ______________________ -ActivitySim includes a `Jupyter Notebook `__ recipe book with interactive examples. +ActivitySim includes a `Jupyter Notebook `__ recipe book with interactive examples. * To start JupyterLab, from the ActivitySim project directory run ``uv run jupyter lab``. This will start the JupyterLab server and pop up a browser window with the interactive development environment. * Navigate to the ``examples/prototype_mtc/notebooks`` folder and select a notebook to learn more: @@ -283,3 +283,18 @@ With the set of output CSV files, the user can trace ActivitySim calculations in help debug data and/or logic errors. Refer to :ref:`trace` for more details on configuring tracing and the various output files. + +.. _explicit_error_terms_ways_to_run : + +Explicit Error Terms +____________________ + +By default, ActivitySim makes choices using analytical probabilities derived from systematic utilities. +Alternatively, users can enable Explicit Error Terms (EET) by setting ``use_explicit_error_terms: True`` +in the global or model-specific settings. + +In EET mode, unobserved utility components are drawn directly from the Gumbel distribution (EV1) and added +to the systematic utilities. The alternative with the highest total utility is then selected. This approach +is particularly useful for reducing simulation noise and improving consistency when comparing scenarios +where only a subset of alternatives has changed, as it maintains the same unobserved error draws for +alternatives that remain constant. From f628505215353849351534f9f1c77fb9cac4bbb2 Mon Sep 17 00:00:00 2001 From: Tyler Pearn Date: Mon, 23 Mar 2026 17:02:46 +1000 Subject: [PATCH 155/298] Add tests checking that choices made using eet and from probabilities are the same for eval_mnl and eval_nl --- activitysim/core/test/test_simulate.py | 164 +++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 7 deletions(-) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 17d4ba2cd6..9b7c44f0b4 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -3,6 +3,7 @@ from __future__ import annotations import os.path +from pathlib import Path import numpy as np import numpy.testing as npt @@ -10,7 +11,7 @@ import pandas.testing as pdt import pytest -from activitysim.core import simulate, workflow +from activitysim.core import simulate, workflow, chunk @pytest.fixture @@ -26,9 +27,7 @@ def spec_name(data_dir): @pytest.fixture def state(data_dir) -> workflow.State: state = workflow.State() - state.initialize_filesystem( - working_dir=os.path.dirname(__file__), data_dir=(data_dir,) - ).default_settings() + state.initialize_filesystem(working_dir=os.path.dirname(__file__), data_dir=(data_dir,)).default_settings() return state @@ -54,9 +53,7 @@ def test_read_model_spec(state, spec_name): def test_eval_variables(state, spec, data): result = simulate.eval_variables(state, spec.index, data) - expected = pd.DataFrame( - [[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index - ) + expected = pd.DataFrame([[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index) expected[expected.columns[0]] = expected[expected.columns[0]].astype(np.int8) expected[expected.columns[1]] = expected[expected.columns[1]].astype(np.int8) @@ -88,3 +85,156 @@ def test_simple_simulate_chunked(state, data, spec): ) expected = pd.Series([1, 1, 1], index=data.index) pdt.assert_series_equal(choices, expected, check_dtype=False) + + +def test_eval_mnl_eet(data_dir): + """ + Check that the same probabilities are gotten when using EET and calculating + probabilities directly for eval_mnl + """ + # using eet state + eet_state = workflow.State() + eet_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() + eet_state.settings.use_explicit_error_terms = True + + num_choosers = 100_000 + + np.random.seed(42) + data2 = pd.DataFrame( + { + "chooser_attr": np.random.rand(num_choosers), + }, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + spec2 = pd.DataFrame( + {"alt0": [1.0], "alt1": [2.0]}, + index=pd.Index(["chooser_attr"], name="Expression"), + ) + + eet_state.rng().set_base_seed(42) + eet_state.rng().add_channel("person_id", data2) + eet_state.rng().begin_step("test_step_mnl") + + chunk_sizer = chunk.ChunkSizer(eet_state, "", "", num_choosers) + + choices_eet = simulate.eval_mnl( + state=eet_state, + choosers=data2, + spec=spec2, + locals_d=None, + custom_chooser=None, + estimator=None, + chunk_sizer=chunk_sizer, + ) + + # calculating probabilties from utility + prob_state = workflow.State() + prob_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() + prob_state.settings.use_explicit_error_terms = False + + prob_state.rng().set_base_seed(42) + prob_state.rng().add_channel("person_id", data2) + prob_state.rng().begin_step("test_step_mnl") + + choices_mnl = simulate.eval_mnl( + state=prob_state, + choosers=data2, + spec=spec2, + locals_d=None, + custom_chooser=None, + estimator=None, + chunk_sizer=chunk_sizer, + ) + + mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() + explicit_counts = choices_eet.value_counts(normalize=True).sort_index() + + # Check that they are similar + for i, alt in enumerate(["alt0", "alt2"]): + share_mnl = mnl_counts.get(i, 0) + share_explicit = explicit_counts.get(i, 0) + assert abs(share_mnl - share_explicit) < 0.01, f"Large discrepancy at alt {alt}: {share_mnl} vs {share_explicit}" + + +def test_eval_nl_eet(data_dir): + """ + Check that the same probabilities are gotten when using EET and calculating + probabilities directly for eval_nl + """ + # using eet state + eet_state = workflow.State() + eet_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() + eet_state.settings.use_explicit_error_terms = True + + num_choosers = 100_000 + + np.random.seed(42) + data2 = pd.DataFrame( + { + "chooser_attr": np.random.rand(num_choosers), + }, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + spec2 = pd.DataFrame( + {"alt1": [2.0], "alt0.0": [0.5], "alt0.1": [0.2]}, + index=pd.Index(["chooser_attr"], name="Expression"), + ) + + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "alt0", "coefficient": 0.5, "alternatives": ["alt0.0", "alt0.1"]}, + "alt1", + ], + } + + eet_state.rng().set_base_seed(42) # Set seed BEFORE adding channels or steps + eet_state.rng().add_channel("person_id", data2) + eet_state.rng().begin_step("test_step_mnl") + + chunk_sizer = chunk.ChunkSizer(eet_state, "", "", num_choosers) + + choices_eet = simulate.eval_nl( + state=eet_state, + choosers=data2, + spec=spec2, + nest_spec=nest_spec, + locals_d={}, + custom_chooser=None, + estimator=None, + trace_label="test", + chunk_sizer=chunk_sizer, + ) + + # calculating probabilties from utility + prob_state = workflow.State() + prob_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() + prob_state.settings.use_explicit_error_terms = False + + prob_state.rng().set_base_seed(42) + prob_state.rng().add_channel("person_id", data2) + prob_state.rng().begin_step("test_step_mnl") + + choices_mnl = simulate.eval_nl( + state=prob_state, + choosers=data2, + spec=spec2, + nest_spec=nest_spec, + locals_d={}, + custom_chooser=None, + trace_label="test", + estimator=None, + chunk_sizer=chunk_sizer, + ) + + mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() + explicit_counts = choices_eet.value_counts(normalize=True).sort_index() + + # Check that they are similar + for i, alt in enumerate(["alt0", "alt2"]): + share_mnl = mnl_counts.get(i, 0) + share_explicit = explicit_counts.get(i, 0) + assert abs(share_mnl - share_explicit) < 0.01, f"Large discrepancy at alt {alt}: {share_mnl} vs {share_explicit}" From 362eef0884c266aa7e09c6ac4c6c4200ad4c8ba6 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 24 Mar 2026 09:37:18 +1000 Subject: [PATCH 156/298] Linting, minor changes to test_simulate.py --- activitysim/abm/models/util/test/test_cdap.py | 63 +++++++--- .../test_joint_tour_participation.py | 28 +++-- .../test_misc/test_trip_departure_choice.py | 9 +- activitysim/core/test/test_logit.py | 60 ++++++--- activitysim/core/test/test_simulate.py | 118 ++++++++---------- 5 files changed, 173 insertions(+), 105 deletions(-) diff --git a/activitysim/abm/models/util/test/test_cdap.py b/activitysim/abm/models/util/test/test_cdap.py index d7eeab8886..3e65dbd648 100644 --- a/activitysim/abm/models/util/test/test_cdap.py +++ b/activitysim/abm/models/util/test/test_cdap.py @@ -60,14 +60,20 @@ def test_assign_cdap_rank(people, model_settings): with chunk.chunk_log(state, "test_assign_cdap_rank", base=True): cdap.assign_cdap_rank(state, people, person_type_map) - expected = pd.Series([1, 1, 1, 2, 2, 1, 3, 1, 2, 1, 3, 2, 1, 3, 2, 4, 1, 3, 4, 2], index=people.index) + expected = pd.Series( + [1, 1, 1, 2, 2, 1, 3, 1, 2, 1, 3, 2, 1, 3, 2, 4, 1, 3, 4, 2], index=people.index + ) - pdt.assert_series_equal(people["cdap_rank"], expected, check_dtype=False, check_names=False) + pdt.assert_series_equal( + people["cdap_rank"], expected, check_dtype=False, check_names=False + ) def test_individual_utilities(people, model_settings): state = workflow.State.make_default(__file__) - cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec(file_name="cdap_indiv_and_hhsize1.csv") + cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec( + file_name="cdap_indiv_and_hhsize1.csv" + ) person_type_map = model_settings.get("PERSON_TYPE_MAP", {}) @@ -110,23 +116,31 @@ def test_individual_utilities(people, model_settings): columns=cdap_indiv_and_hhsize1.columns, ) - pdt.assert_frame_equal(individual_utils, expected, check_dtype=False, check_names=False) + pdt.assert_frame_equal( + individual_utils, expected, check_dtype=False, check_names=False + ) def test_build_cdap_spec_hhsize2(people, model_settings): state = workflow.State.make_default(__file__) hhsize = 2 - cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec(file_name="cdap_indiv_and_hhsize1.csv") + cdap_indiv_and_hhsize1 = state.filesystem.read_model_spec( + file_name="cdap_indiv_and_hhsize1.csv" + ) interaction_coefficients = pd.read_csv( state.filesystem.get_config_file_path("cdap_interaction_coefficients.csv"), comment="#", ) - interaction_coefficients = cdap.preprocess_interaction_coefficients(interaction_coefficients) + interaction_coefficients = cdap.preprocess_interaction_coefficients( + interaction_coefficients + ) person_type_map = model_settings.get("PERSON_TYPE_MAP", {}) - with chunk.chunk_log(state, "test_build_cdap_spec_hhsize2", base=True) as chunk_sizer: + with chunk.chunk_log( + state, "test_build_cdap_spec_hhsize2", base=True + ) as chunk_sizer: cdap.assign_cdap_rank(state, people, person_type_map) indiv_utils = cdap.individual_utilities( state, @@ -138,7 +152,9 @@ def test_build_cdap_spec_hhsize2(people, model_settings): choosers = cdap.hh_choosers(state, indiv_utils, hhsize=hhsize) - spec = cdap.build_cdap_spec(state, interaction_coefficients, hhsize=hhsize, cache=False) + spec = cdap.build_cdap_spec( + state, interaction_coefficients, hhsize=hhsize, cache=False + ) # pandas.dot depends on column names of expression_values matching spec index values # expressions should have been uniquified when spec was read @@ -171,24 +187,38 @@ def test_cdap_explicit_error_terms_parity(people, model_settings): large_people = pd.concat([people] * 500).reset_index(drop=True) large_people.index.name = "person_id" # Need to ensure household IDs are updated so they are distinct - large_people["household_id"] = large_people.groupby("household_id").cumcount() * 1000 + large_people["household_id"] + large_people["household_id"] = ( + large_people.groupby("household_id").cumcount() * 1000 + + large_people["household_id"] + ) large_people = large_people.sort_values("household_id") # Run without explicit error terms state_no_eet = workflow.State.make_default(__file__) - cdap_indiv_spec = state_no_eet.filesystem.read_model_spec(file_name="cdap_indiv_and_hhsize1.csv") + cdap_indiv_spec = state_no_eet.filesystem.read_model_spec( + file_name="cdap_indiv_and_hhsize1.csv" + ) interaction_coefficients = pd.read_csv( - state_no_eet.filesystem.get_config_file_path("cdap_interaction_coefficients.csv"), + state_no_eet.filesystem.get_config_file_path( + "cdap_interaction_coefficients.csv" + ), comment="#", ) - interaction_coefficients = cdap.preprocess_interaction_coefficients(interaction_coefficients) - cdap_fixed_relative_proportions = pd.DataFrame({"activity": ["M", "N", "H"], "coefficient": [0.33, 0.33, 0.34]}) + interaction_coefficients = cdap.preprocess_interaction_coefficients( + interaction_coefficients + ) + cdap_fixed_relative_proportions = pd.DataFrame( + {"activity": ["M", "N", "H"], "coefficient": [0.33, 0.33, 0.34]} + ) state_no_eet.settings.use_explicit_error_terms = False state_no_eet.rng().set_base_seed(42) state_no_eet.rng().begin_step("test_no_eet") state_no_eet.rng().add_channel("person_id", large_people) - state_no_eet.rng().add_channel("household_id", large_people.drop_duplicates("household_id").set_index("household_id")) + state_no_eet.rng().add_channel( + "household_id", + large_people.drop_duplicates("household_id").set_index("household_id"), + ) choices_no_eet = cdap.run_cdap( state_no_eet, @@ -206,7 +236,10 @@ def test_cdap_explicit_error_terms_parity(people, model_settings): state_eet.rng().set_base_seed(42) state_eet.rng().begin_step("test_eet") state_eet.rng().add_channel("person_id", large_people) - state_eet.rng().add_channel("household_id", large_people.drop_duplicates("household_id").set_index("household_id")) + state_eet.rng().add_channel( + "household_id", + large_people.drop_duplicates("household_id").set_index("household_id"), + ) choices_eet = cdap.run_cdap( state_eet, diff --git a/activitysim/abm/test/test_misc/test_joint_tour_participation.py b/activitysim/abm/test/test_misc/test_joint_tour_participation.py index 9737934497..18905ef107 100644 --- a/activitysim/abm/test/test_misc/test_joint_tour_participation.py +++ b/activitysim/abm/test/test_misc/test_joint_tour_participation.py @@ -56,14 +56,20 @@ def candidates(): elif comp == "CHILDREN": df.loc[indices, "adult"] = False # Some children are preschoolers - df.loc[rng.choice(indices, len(indices) // 4, replace=False), "person_is_preschool"] = True + df.loc[ + rng.choice(indices, len(indices) // 4, replace=False), + "person_is_preschool", + ] = True elif comp == "MIXED": # For each tour, make the first person an adult, rest children tour_start_indices = indices[::num_candidates_per_tour] df.loc[tour_start_indices, "adult"] = True # Other members are children, some might be preschoolers other_indices = indices[~indices.isin(tour_start_indices)] - df.loc[rng.choice(other_indices, len(other_indices) // 3, replace=False), "person_is_preschool"] = True + df.loc[ + rng.choice(other_indices, len(other_indices) // 3, replace=False), + "person_is_preschool", + ] = True return df @@ -130,7 +136,9 @@ def test_jtp_explicit_error_terms_parity(candidates, model_spec): # Compare distributions of number of participants per tour # Choice 0 is 'participate' - no_eet_participation_counts = (choices_no_eet == 0).groupby(candidates.tour_id).sum() + no_eet_participation_counts = ( + (choices_no_eet == 0).groupby(candidates.tour_id).sum() + ) eet_participation_counts = (choices_eet == 0).groupby(candidates.tour_id).sum() dist_no_eet = no_eet_participation_counts.value_counts(normalize=True).sort_index() @@ -140,7 +148,13 @@ def test_jtp_explicit_error_terms_parity(candidates, model_spec): pdt.assert_series_equal(dist_no_eet, dist_eet, atol=0.05, check_names=False) # Also check average participation by composition for deeper parity check - comp_parity_no_eet = no_eet_participation_counts.groupby(candidates.groupby("tour_id")["composition"].first()).mean() - comp_parity_eet = eet_participation_counts.groupby(candidates.groupby("tour_id")["composition"].first()).mean() - - pdt.assert_series_equal(comp_parity_no_eet, comp_parity_eet, atol=0.1, check_names=False) + comp_parity_no_eet = no_eet_participation_counts.groupby( + candidates.groupby("tour_id")["composition"].first() + ).mean() + comp_parity_eet = eet_participation_counts.groupby( + candidates.groupby("tour_id")["composition"].first() + ).mean() + + pdt.assert_series_equal( + comp_parity_no_eet, comp_parity_eet, atol=0.1, check_names=False + ) diff --git a/activitysim/abm/test/test_misc/test_trip_departure_choice.py b/activitysim/abm/test/test_misc/test_trip_departure_choice.py index f23b67194c..85e0732f94 100644 --- a/activitysim/abm/test/test_misc/test_trip_departure_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_departure_choice.py @@ -147,7 +147,10 @@ def test_build_patterns(trips): def test_get_tour_legs(trips): tour_legs = tdc.get_tour_legs(trips) assert tour_legs.index.name == tdc.TOUR_LEG_ID - assert np.unique(tour_legs[tdc.TOUR_ID].values).shape[0] == np.unique(trips[tdc.TOUR_ID].values).shape[0] + assert ( + np.unique(tour_legs[tdc.TOUR_ID].values).shape[0] + == np.unique(trips[tdc.TOUR_ID].values).shape[0] + ) def test_generate_alternative(trips): @@ -197,7 +200,9 @@ def test_tdc_explicit_error_terms_parity(model_spec, trips): large_trips = pd.concat([trips] * 500).reset_index(drop=True) large_trips.index.name = "trip_id" # Ensure tour_ids are distinct for the expanded set - large_trips["tour_id"] = large_trips.groupby("tour_id").cumcount() * 1000 + large_trips["tour_id"] + large_trips["tour_id"] = ( + large_trips.groupby("tour_id").cumcount() * 1000 + large_trips["tour_id"] + ) # Trip departure choice uses tour_leg_id as the random channel index tour_legs = tdc.get_tour_legs(large_trips) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 0f2dc9abf2..1ad8c52a90 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -26,7 +26,9 @@ def data_dir(): ( "fish.csv", "fish_choosers.csv", - pd.DataFrame([[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"]), + pd.DataFrame( + [[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"] + ), pd.DataFrame( [ [0.2849598, 0.2742482, 0.1605457, 0.2802463], @@ -257,10 +259,14 @@ def test_utils_to_probs_raises(): # def test_make_choices_only_one(): state = workflow.State().default_settings() - probs = pd.DataFrame([[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"]) + probs = pd.DataFrame( + [[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"] + ) choices, rands = logit.make_choices(state, probs) - pdt.assert_series_equal(choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False) + pdt.assert_series_equal( + choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False + ) def test_make_choices_real_probs(utilities): @@ -309,7 +315,9 @@ def test_different_order_make_choices(): choices_shuffled, rands_shuffled = logit.make_choices(state, probs_shuffled) # sorting both to ensure comparison is on the same index order - pdt.assert_series_equal(choices.sort_index(), choices_shuffled.sort_index(), check_dtype=False) + pdt.assert_series_equal( + choices.sort_index(), choices_shuffled.sort_index(), check_dtype=False + ) def test_make_choices_matches_random_draws(): @@ -427,7 +435,9 @@ def test_choose_from_tree_selects_leaf(): "motorized": ["car", "bus"], } - choice = logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) + choice = logit.choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ) assert choice == "car" @@ -442,7 +452,9 @@ def test_choose_from_tree_raises_on_missing_leaf(): } with pytest.raises(ValueError, match="no alternative found"): - logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) + logit.choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ) # @@ -554,7 +566,9 @@ class MCDummyState: def get_rn_generator(): return MCDummyRNG() - probs = logit.utils_to_probs(MCDummyState(), utils, trace_label=None, overflow_protection=True) + probs = logit.utils_to_probs( + MCDummyState(), utils, trace_label=None, overflow_protection=True + ) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) # Explicit-error-term (EET) path — independent RNG @@ -569,14 +583,20 @@ class EETDummyState: def get_rn_generator(): return EETDummyRNG() - choices_eet = logit.make_choices_explicit_error_term_mnl(EETDummyState(), utils, trace_label=None) + choices_eet = logit.make_choices_explicit_error_term_mnl( + EETDummyState(), utils, trace_label=None + ) mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=n_alts) / n_draws eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=n_alts) / n_draws np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol, rtol=r_tol) - np.testing.assert_allclose(mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) - np.testing.assert_allclose(eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) + np.testing.assert_allclose( + mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol + ) + np.testing.assert_allclose( + eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol + ) def test_make_choices_vs_eet_nl_same_distribution(): @@ -621,9 +641,15 @@ def default_settings(self): return self # Compute probabilities for NL using simulation logic - nested_exp_utilities = simulate.compute_nested_exp_utilities(utils_df[["car", "bus", "walk"]], nest_spec) - nested_probabilities = simulate.compute_nested_probabilities(MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None) - probs = simulate.compute_base_probabilities(nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]]) + nested_exp_utilities = simulate.compute_nested_exp_utilities( + utils_df[["car", "bus", "walk"]], nest_spec + ) + nested_probabilities = simulate.compute_nested_probabilities( + MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None + ) + probs = simulate.compute_base_probabilities( + nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]] + ) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) # 2. EET-based Nested Logit choices @@ -650,7 +676,9 @@ def tracing(self): # For EET NL, we provide the utilities for all nodes. # compute_nested_utilities handles the division by nesting coefficients for leaves # and the logsum * coefficient for internal nodes. - nested_utilities = simulate.compute_nested_utilities(utils_df[["car", "bus", "walk"]], nest_spec) + nested_utilities = simulate.compute_nested_utilities( + utils_df[["car", "bus", "walk"]], nest_spec + ) choices_eet = logit.make_choices_explicit_error_term_nl( EETDummyState(), @@ -679,7 +707,9 @@ def test_interaction_dataset_no_sample(interaction_choosers, interaction_alts): index=[1, 2, 3, 4] * 4, ) - interacted = logit.interaction_dataset(workflow.State().default_settings(), interaction_choosers, interaction_alts) + interacted = logit.interaction_dataset( + workflow.State().default_settings(), interaction_choosers, interaction_alts + ) interacted, expected = interacted.align(expected, axis=1) pdt.assert_frame_equal(interacted, expected) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 9b7c44f0b4..e7a91e5924 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -11,12 +11,12 @@ import pandas.testing as pdt import pytest -from activitysim.core import simulate, workflow, chunk +from activitysim.core import chunk, simulate, workflow @pytest.fixture def data_dir(): - return os.path.join(os.path.dirname(__file__), "data") + return Path(__file__).parent / "data" @pytest.fixture @@ -26,8 +26,8 @@ def spec_name(data_dir): @pytest.fixture def state(data_dir) -> workflow.State: - state = workflow.State() - state.initialize_filesystem(working_dir=os.path.dirname(__file__), data_dir=(data_dir,)).default_settings() + state = workflow.State().default_settings() + state.initialize_filesystem(working_dir=Path(__file__).parent, data_dir=(data_dir,)) return state @@ -41,7 +41,9 @@ def data(data_dir): return pd.read_csv(os.path.join(data_dir, "data.csv")) -def test_read_model_spec(state, spec_name): +def test_read_model_spec( + state, spec_name +): # NOTE: this tests code not directly related to simulate spec = state.filesystem.read_model_spec(file_name=spec_name) assert len(spec) == 4 @@ -53,8 +55,14 @@ def test_read_model_spec(state, spec_name): def test_eval_variables(state, spec, data): result = simulate.eval_variables(state, spec.index, data) - expected = pd.DataFrame([[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index) + expected_result = [ + [1, 0, 4, 1], + [0, 1, 4, 1], + [0, 1, 5, 1], + ] + expected = pd.DataFrame(expected_result, index=data.index, columns=spec.index) + # type-cast to match the expected result dtypes expected[expected.columns[0]] = expected[expected.columns[0]].astype(np.int8) expected[expected.columns[1]] = expected[expected.columns[1]].astype(np.int8) expected[expected.columns[2]] = expected[expected.columns[2]].astype(np.int64) @@ -87,15 +95,8 @@ def test_simple_simulate_chunked(state, data, spec): pdt.assert_series_equal(choices, expected, check_dtype=False) -def test_eval_mnl_eet(data_dir): - """ - Check that the same probabilities are gotten when using EET and calculating - probabilities directly for eval_mnl - """ - # using eet state - eet_state = workflow.State() - eet_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() - eet_state.settings.use_explicit_error_terms = True +def test_eval_mnl_eet(state): + # Check that the same counts are returned by eval_mnl when using EET and when not. num_choosers = 100_000 @@ -112,14 +113,17 @@ def test_eval_mnl_eet(data_dir): index=pd.Index(["chooser_attr"], name="Expression"), ) - eet_state.rng().set_base_seed(42) - eet_state.rng().add_channel("person_id", data2) - eet_state.rng().begin_step("test_step_mnl") + # Set up a state with EET enabled + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", data2) + state.rng().begin_step("test_step_mnl") - chunk_sizer = chunk.ChunkSizer(eet_state, "", "", num_choosers) + chunk_sizer = chunk.ChunkSizer(state, "", "", num_choosers) + # run eval_mnl with EET enabled choices_eet = simulate.eval_mnl( - state=eet_state, + state=state, choosers=data2, spec=spec2, locals_d=None, @@ -128,17 +132,14 @@ def test_eval_mnl_eet(data_dir): chunk_sizer=chunk_sizer, ) - # calculating probabilties from utility - prob_state = workflow.State() - prob_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() - prob_state.settings.use_explicit_error_terms = False + # Reset the state, without EET enabled + state.settings.use_explicit_error_terms = False - prob_state.rng().set_base_seed(42) - prob_state.rng().add_channel("person_id", data2) - prob_state.rng().begin_step("test_step_mnl") + state.rng().end_step("test_step_mnl") + state.rng().begin_step("test_step_mnl") choices_mnl = simulate.eval_mnl( - state=prob_state, + state=state, choosers=data2, spec=spec2, locals_d=None, @@ -147,25 +148,14 @@ def test_eval_mnl_eet(data_dir): chunk_sizer=chunk_sizer, ) - mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() - explicit_counts = choices_eet.value_counts(normalize=True).sort_index() + # Compare counts + mnl_counts = choices_mnl.value_counts(normalize=True) + explicit_counts = choices_eet.value_counts(normalize=True) + assert np.allclose(mnl_counts, explicit_counts, atol=0.01) - # Check that they are similar - for i, alt in enumerate(["alt0", "alt2"]): - share_mnl = mnl_counts.get(i, 0) - share_explicit = explicit_counts.get(i, 0) - assert abs(share_mnl - share_explicit) < 0.01, f"Large discrepancy at alt {alt}: {share_mnl} vs {share_explicit}" - -def test_eval_nl_eet(data_dir): - """ - Check that the same probabilities are gotten when using EET and calculating - probabilities directly for eval_nl - """ - # using eet state - eet_state = workflow.State() - eet_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() - eet_state.settings.use_explicit_error_terms = True +def test_eval_nl_eet(state): + # Check that the same counts are returned by eval_nl when using EET and when not. num_choosers = 100_000 @@ -191,14 +181,17 @@ def test_eval_nl_eet(data_dir): ], } - eet_state.rng().set_base_seed(42) # Set seed BEFORE adding channels or steps - eet_state.rng().add_channel("person_id", data2) - eet_state.rng().begin_step("test_step_mnl") + # Set up a state with EET enabled + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", data2) + state.rng().begin_step("test_step_mnl") - chunk_sizer = chunk.ChunkSizer(eet_state, "", "", num_choosers) + chunk_sizer = chunk.ChunkSizer(state, "", "", num_choosers) + # run eval_nl with EET enabled choices_eet = simulate.eval_nl( - state=eet_state, + state=state, choosers=data2, spec=spec2, nest_spec=nest_spec, @@ -209,17 +202,14 @@ def test_eval_nl_eet(data_dir): chunk_sizer=chunk_sizer, ) - # calculating probabilties from utility - prob_state = workflow.State() - prob_state.initialize_filesystem(working_dir=Path(os.path.dirname(__file__)), data_dir=(data_dir,)).default_settings() - prob_state.settings.use_explicit_error_terms = False + # Reset the state, without EET enabled + state.settings.use_explicit_error_terms = False - prob_state.rng().set_base_seed(42) - prob_state.rng().add_channel("person_id", data2) - prob_state.rng().begin_step("test_step_mnl") + state.rng().end_step("test_step_mnl") + state.rng().begin_step("test_step_mnl") choices_mnl = simulate.eval_nl( - state=prob_state, + state=state, choosers=data2, spec=spec2, nest_spec=nest_spec, @@ -230,11 +220,7 @@ def test_eval_nl_eet(data_dir): chunk_sizer=chunk_sizer, ) - mnl_counts = choices_mnl.value_counts(normalize=True).sort_index() - explicit_counts = choices_eet.value_counts(normalize=True).sort_index() - - # Check that they are similar - for i, alt in enumerate(["alt0", "alt2"]): - share_mnl = mnl_counts.get(i, 0) - share_explicit = explicit_counts.get(i, 0) - assert abs(share_mnl - share_explicit) < 0.01, f"Large discrepancy at alt {alt}: {share_mnl} vs {share_explicit}" + # Compare counts + mnl_counts = choices_mnl.value_counts(normalize=True) + explicit_counts = choices_eet.value_counts(normalize=True) + assert np.allclose(mnl_counts, explicit_counts, atol=0.01) From d0a64296fc5fe42221c337d371395a47c879d08d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 26 Mar 2026 20:09:33 +1000 Subject: [PATCH 157/298] roll back changes to core tests to minimize noise --- activitysim/core/test/test_simulate.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index e7a91e5924..42ff9f11d9 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -3,7 +3,6 @@ from __future__ import annotations import os.path -from pathlib import Path import numpy as np import numpy.testing as npt @@ -16,7 +15,7 @@ @pytest.fixture def data_dir(): - return Path(__file__).parent / "data" + return os.path.join(os.path.dirname(__file__), "data") @pytest.fixture @@ -26,8 +25,10 @@ def spec_name(data_dir): @pytest.fixture def state(data_dir) -> workflow.State: - state = workflow.State().default_settings() - state.initialize_filesystem(working_dir=Path(__file__).parent, data_dir=(data_dir,)) + state = workflow.State() + state.initialize_filesystem( + working_dir=os.path.dirname(__file__), data_dir=(data_dir,) + ).default_settings() return state @@ -41,9 +42,7 @@ def data(data_dir): return pd.read_csv(os.path.join(data_dir, "data.csv")) -def test_read_model_spec( - state, spec_name -): # NOTE: this tests code not directly related to simulate +def test_read_model_spec(state, spec_name): spec = state.filesystem.read_model_spec(file_name=spec_name) assert len(spec) == 4 @@ -55,14 +54,10 @@ def test_read_model_spec( def test_eval_variables(state, spec, data): result = simulate.eval_variables(state, spec.index, data) - expected_result = [ - [1, 0, 4, 1], - [0, 1, 4, 1], - [0, 1, 5, 1], - ] - expected = pd.DataFrame(expected_result, index=data.index, columns=spec.index) + expected = pd.DataFrame( + [[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index + ) - # type-cast to match the expected result dtypes expected[expected.columns[0]] = expected[expected.columns[0]].astype(np.int8) expected[expected.columns[1]] = expected[expected.columns[1]].astype(np.int8) expected[expected.columns[2]] = expected[expected.columns[2]].astype(np.int64) From 65fd171ca9acc3b11fa46faf60cf284dbf09c7bf Mon Sep 17 00:00:00 2001 From: Tyler Pearn Date: Tue, 31 Mar 2026 08:50:37 +1000 Subject: [PATCH 158/298] Implement Jan's suggestion of how to calculate household ids for testing cdap eet parity --- activitysim/abm/models/util/test/test_cdap.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/activitysim/abm/models/util/test/test_cdap.py b/activitysim/abm/models/util/test/test_cdap.py index 3e65dbd648..20d68f2dd9 100644 --- a/activitysim/abm/models/util/test/test_cdap.py +++ b/activitysim/abm/models/util/test/test_cdap.py @@ -186,12 +186,13 @@ def test_cdap_explicit_error_terms_parity(people, model_settings): # We'll just duplicate the existing people a few times large_people = pd.concat([people] * 500).reset_index(drop=True) large_people.index.name = "person_id" - # Need to ensure household IDs are updated so they are distinct - large_people["household_id"] = ( - large_people.groupby("household_id").cumcount() * 1000 - + large_people["household_id"] - ) - large_people = large_people.sort_values("household_id") + + assert people.household_id.is_monotonic_increasing + large_people["hhid_diff"] = large_people.household_id.diff().fillna(0).astype(int) + large_people.loc[large_people["hhid_diff"] < 0, "hhid_diff"] = 1 + large_people["household_id"] = large_people.hhid_diff.cumsum() + + assert large_people["household_id"].is_monotonic_increasing # Run without explicit error terms state_no_eet = workflow.State.make_default(__file__) From f55b2cdce728aa429b6b15b69595b72b02e89a42 Mon Sep 17 00:00:00 2001 From: Tyler Pearn Date: Tue, 31 Mar 2026 10:59:30 +1000 Subject: [PATCH 159/298] Add test for compute_nested_utilities. Computes nested utilities on a toy example and compares the result --- activitysim/core/test/test_simulate.py | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 42ff9f11d9..1e9f836c0f 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -219,3 +219,43 @@ def test_eval_nl_eet(state): mnl_counts = choices_mnl.value_counts(normalize=True) explicit_counts = choices_eet.value_counts(normalize=True) assert np.allclose(mnl_counts, explicit_counts, atol=0.01) + +def test_compute_nested_utilities(): + # computes nested utilities manually and using the function and checks that + # the utilities are the same + + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "alt0", "coefficient": 0.5, "alternatives": ["alt0.0", "alt0.1"]}, + "alt1", + ], + } + + num_choosers = 2 + raw_utilities = pd.DataFrame( + {"alt1": [1,10], "alt0.0": [2,3], "alt0.1": [4,5]}, + index=pd.Index(range(num_choosers)) + ) + + nested_utilities = simulate.compute_nested_utilities(raw_utilities, nest_spec) + + # these are from the definition of nest_spec + nest_coefficients = pd.DataFrame( + {"alt1": [1.0], "alt0.0": [0.5], "alt0.1": [0.5]}, index=[0] + ) + leaf_utilities = raw_utilities / nest_coefficients.iloc[0] + + constructed_nested_utilities = pd.DataFrame(index=raw_utilities.index) + + constructed_nested_utilities[leaf_utilities.columns] = leaf_utilities + constructed_nested_utilities["alt0"] = 0.5 * np.log( + np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1) + ) + constructed_nested_utilities["root"] = 1 * np.log( + np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1) + ) + print(constructed_nested_utilities) + + assert np.allclose(nested_utilities, constructed_nested_utilities) From 4debaadb9c8095ac17d64e52ad1b6216b242f239 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 31 Mar 2026 11:17:03 +1000 Subject: [PATCH 160/298] Linting --- activitysim/core/test/test_simulate.py | 29 +++++++------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 1e9f836c0f..b635e383f6 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -26,9 +26,7 @@ def spec_name(data_dir): @pytest.fixture def state(data_dir) -> workflow.State: state = workflow.State() - state.initialize_filesystem( - working_dir=os.path.dirname(__file__), data_dir=(data_dir,) - ).default_settings() + state.initialize_filesystem(working_dir=os.path.dirname(__file__), data_dir=(data_dir,)).default_settings() return state @@ -54,9 +52,7 @@ def test_read_model_spec(state, spec_name): def test_eval_variables(state, spec, data): result = simulate.eval_variables(state, spec.index, data) - expected = pd.DataFrame( - [[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index - ) + expected = pd.DataFrame([[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index) expected[expected.columns[0]] = expected[expected.columns[0]].astype(np.int8) expected[expected.columns[1]] = expected[expected.columns[1]].astype(np.int8) @@ -220,6 +216,7 @@ def test_eval_nl_eet(state): explicit_counts = choices_eet.value_counts(normalize=True) assert np.allclose(mnl_counts, explicit_counts, atol=0.01) + def test_compute_nested_utilities(): # computes nested utilities manually and using the function and checks that # the utilities are the same @@ -234,28 +231,18 @@ def test_compute_nested_utilities(): } num_choosers = 2 - raw_utilities = pd.DataFrame( - {"alt1": [1,10], "alt0.0": [2,3], "alt0.1": [4,5]}, - index=pd.Index(range(num_choosers)) - ) + raw_utilities = pd.DataFrame({"alt1": [1, 10], "alt0.0": [2, 3], "alt0.1": [4, 5]}, index=pd.Index(range(num_choosers))) nested_utilities = simulate.compute_nested_utilities(raw_utilities, nest_spec) # these are from the definition of nest_spec - nest_coefficients = pd.DataFrame( - {"alt1": [1.0], "alt0.0": [0.5], "alt0.1": [0.5]}, index=[0] - ) + nest_coefficients = pd.DataFrame({"alt1": [1.0], "alt0.0": [0.5], "alt0.1": [0.5]}, index=[0]) leaf_utilities = raw_utilities / nest_coefficients.iloc[0] constructed_nested_utilities = pd.DataFrame(index=raw_utilities.index) constructed_nested_utilities[leaf_utilities.columns] = leaf_utilities - constructed_nested_utilities["alt0"] = 0.5 * np.log( - np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1) - ) - constructed_nested_utilities["root"] = 1 * np.log( - np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1) - ) - print(constructed_nested_utilities) + constructed_nested_utilities["alt0"] = 0.5 * np.log(np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1)) + constructed_nested_utilities["root"] = 1 * np.log(np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1)) - assert np.allclose(nested_utilities, constructed_nested_utilities) + assert np.allclose(nested_utilities, constructed_nested_utilities), "Mismatch in nested utilities" From 743c56fd93fd4ec4a38d7df0633df35bdece1fcb Mon Sep 17 00:00:00 2001 From: Tyler Pearn Date: Tue, 31 Mar 2026 11:20:25 +1000 Subject: [PATCH 161/298] Move nest_spec to a fixture in simulate.py tests --- activitysim/core/test/test_simulate.py | 44 +++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 1e9f836c0f..a0cc76ac19 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -42,6 +42,19 @@ def data(data_dir): return pd.read_csv(os.path.join(data_dir, "data.csv")) +@pytest.fixture +def nest_spec(): + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "alt0", "coefficient": 0.5, "alternatives": ["alt0.0", "alt0.1"]}, + "alt1", + ], + } + return nest_spec + + def test_read_model_spec(state, spec_name): spec = state.filesystem.read_model_spec(file_name=spec_name) @@ -149,7 +162,7 @@ def test_eval_mnl_eet(state): assert np.allclose(mnl_counts, explicit_counts, atol=0.01) -def test_eval_nl_eet(state): +def test_eval_nl_eet(state, nest_spec): # Check that the same counts are returned by eval_nl when using EET and when not. num_choosers = 100_000 @@ -167,15 +180,6 @@ def test_eval_nl_eet(state): index=pd.Index(["chooser_attr"], name="Expression"), ) - nest_spec = { - "name": "root", - "coefficient": 1.0, - "alternatives": [ - {"name": "alt0", "coefficient": 0.5, "alternatives": ["alt0.0", "alt0.1"]}, - "alt1", - ], - } - # Set up a state with EET enabled state.settings.use_explicit_error_terms = True state.rng().set_base_seed(42) @@ -220,7 +224,8 @@ def test_eval_nl_eet(state): explicit_counts = choices_eet.value_counts(normalize=True) assert np.allclose(mnl_counts, explicit_counts, atol=0.01) -def test_compute_nested_utilities(): + +def test_compute_nested_utilities(nest_spec): # computes nested utilities manually and using the function and checks that # the utilities are the same @@ -242,20 +247,23 @@ def test_compute_nested_utilities(): nested_utilities = simulate.compute_nested_utilities(raw_utilities, nest_spec) # these are from the definition of nest_spec - nest_coefficients = pd.DataFrame( - {"alt1": [1.0], "alt0.0": [0.5], "alt0.1": [0.5]}, index=[0] + alt0_nest_coefficient = nest_spec["alternatives"][0]["coefficient"] + alt0_leaf_product_of_coefficients = nest_spec["coefficient"] * alt0_nest_coefficient + assert alt0_leaf_product_of_coefficients == 0.5 # 1 * 0.5 + + product_of_coefficientss = pd.DataFrame( + {"alt1": [nest_spec["coefficient"]], "alt0.0": [alt0_leaf_product_of_coefficients], "alt0.1": [alt0_leaf_product_of_coefficients]}, index=[0] ) - leaf_utilities = raw_utilities / nest_coefficients.iloc[0] + leaf_utilities = raw_utilities / product_of_coefficientss.iloc[0] constructed_nested_utilities = pd.DataFrame(index=raw_utilities.index) constructed_nested_utilities[leaf_utilities.columns] = leaf_utilities - constructed_nested_utilities["alt0"] = 0.5 * np.log( + constructed_nested_utilities["alt0"] = alt0_nest_coefficient * np.log( np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1) ) - constructed_nested_utilities["root"] = 1 * np.log( + constructed_nested_utilities["root"] = nest_spec["coefficient"] * np.log( np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1) ) - print(constructed_nested_utilities) - assert np.allclose(nested_utilities, constructed_nested_utilities) + assert np.allclose(nested_utilities, constructed_nested_utilities[nested_utilities.columns]) From b021cee2bbd214c655952034f37c3c4249e1ebaa Mon Sep 17 00:00:00 2001 From: Tyler Pearn Date: Tue, 31 Mar 2026 11:29:12 +1000 Subject: [PATCH 162/298] Finish removing nest_spec definition in simulate.py tests --- activitysim/core/test/test_simulate.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index f31110b90e..0ea3833fdd 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -225,15 +225,6 @@ def test_compute_nested_utilities(nest_spec): # computes nested utilities manually and using the function and checks that # the utilities are the same - nest_spec = { - "name": "root", - "coefficient": 1.0, - "alternatives": [ - {"name": "alt0", "coefficient": 0.5, "alternatives": ["alt0.0", "alt0.1"]}, - "alt1", - ], - } - num_choosers = 2 raw_utilities = pd.DataFrame({"alt1": [1, 10], "alt0.0": [2, 3], "alt0.1": [4, 5]}, index=pd.Index(range(num_choosers))) From ae714330244aa529eb49c0c3d5275beb9dd9ad27 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 31 Mar 2026 13:14:09 +1000 Subject: [PATCH 163/298] lint --- activitysim/core/test/test_simulate.py | 32 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 0ea3833fdd..38e2be237a 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -26,7 +26,9 @@ def spec_name(data_dir): @pytest.fixture def state(data_dir) -> workflow.State: state = workflow.State() - state.initialize_filesystem(working_dir=os.path.dirname(__file__), data_dir=(data_dir,)).default_settings() + state.initialize_filesystem( + working_dir=os.path.dirname(__file__), data_dir=(data_dir,) + ).default_settings() return state @@ -65,7 +67,9 @@ def test_read_model_spec(state, spec_name): def test_eval_variables(state, spec, data): result = simulate.eval_variables(state, spec.index, data) - expected = pd.DataFrame([[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index) + expected = pd.DataFrame( + [[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index + ) expected[expected.columns[0]] = expected[expected.columns[0]].astype(np.int8) expected[expected.columns[1]] = expected[expected.columns[1]].astype(np.int8) @@ -226,17 +230,25 @@ def test_compute_nested_utilities(nest_spec): # the utilities are the same num_choosers = 2 - raw_utilities = pd.DataFrame({"alt1": [1, 10], "alt0.0": [2, 3], "alt0.1": [4, 5]}, index=pd.Index(range(num_choosers))) + raw_utilities = pd.DataFrame( + {"alt1": [1, 10], "alt0.0": [2, 3], "alt0.1": [4, 5]}, + index=pd.Index(range(num_choosers)), + ) nested_utilities = simulate.compute_nested_utilities(raw_utilities, nest_spec) # these are from the definition of nest_spec alt0_nest_coefficient = nest_spec["alternatives"][0]["coefficient"] alt0_leaf_product_of_coefficients = nest_spec["coefficient"] * alt0_nest_coefficient - assert alt0_leaf_product_of_coefficients == 0.5 # 1 * 0.5 - + assert alt0_leaf_product_of_coefficients == 0.5 # 1 * 0.5 + product_of_coefficientss = pd.DataFrame( - {"alt1": [nest_spec["coefficient"]], "alt0.0": [alt0_leaf_product_of_coefficients], "alt0.1": [alt0_leaf_product_of_coefficients]}, index=[0] + { + "alt1": [nest_spec["coefficient"]], + "alt0.0": [alt0_leaf_product_of_coefficients], + "alt0.1": [alt0_leaf_product_of_coefficients], + }, + index=[0], ) leaf_utilities = raw_utilities / product_of_coefficientss.iloc[0] @@ -244,10 +256,12 @@ def test_compute_nested_utilities(nest_spec): constructed_nested_utilities[leaf_utilities.columns] = leaf_utilities constructed_nested_utilities["alt0"] = alt0_nest_coefficient * np.log( - np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1) + np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1) ) constructed_nested_utilities["root"] = nest_spec["coefficient"] * np.log( - np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1) + np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1) ) - assert np.allclose(nested_utilities, constructed_nested_utilities[nested_utilities.columns]), "Mismatch in nested utilities" + assert np.allclose( + nested_utilities, constructed_nested_utilities[nested_utilities.columns] + ), "Mismatch in nested utilities" From 6ef25e12d6c674aa2143c99e7c0dfa3e6a7fdd4d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 31 Mar 2026 13:40:26 +1000 Subject: [PATCH 164/298] clean up --- activitysim/abm/models/joint_tour_participation.py | 7 +++---- activitysim/core/logit.py | 1 - activitysim/core/simulate.py | 5 +++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 68a8a3b9b4..0fb562c189 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -20,8 +20,8 @@ ) from activitysim.core.configuration.base import ComputeSettings, PreprocessorSettings from activitysim.core.configuration.logit import LogitComponentSettings -from activitysim.core.util import assign_in_place, reindex from activitysim.core.exceptions import InvalidTravelError +from activitysim.core.util import assign_in_place, reindex logger = logging.getLogger(__name__) @@ -429,9 +429,8 @@ def joint_tour_participation( if i not in model_settings.compute_settings.protect_columns: model_settings.compute_settings.protect_columns.append(i) - # TODO EET: this is related to the difference in nested logit and logit choice as per comment in - # make_choices_utility_based. As soon as alt_order_array is removed from arguments to - # make_choices_explicit_error_term_nl this guard can be removed + # This is related to the difference in nested logit and logit choice. As soon as alt_order_array + # is removed from arguments to make_choices_explicit_error_term_nl this guard can be removed. if state.settings.use_explicit_error_terms: assert ( nest_spec is None diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index bd96c38f4e..5a58a87076 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -22,7 +22,6 @@ EXP_UTIL_MIN = 1e-300 EXP_UTIL_MAX = np.inf -# TODO-EET: Figure out what type we want UTIL_MIN to be, currently np.float64 UTIL_MIN = np.log(EXP_UTIL_MIN, dtype=np.float64) UTIL_UNAVAILABLE = 1000.0 * (UTIL_MIN - 1.0) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 54609f4065..6872df51db 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1527,8 +1527,9 @@ def eval_nl( logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) chunk_sizer.log_df(trace_label, "logsums", logsums) - # TODO-EET: index of choices for nested utilities is different than unnested - this needs to be consistent for - # turning indexes into alternative names to keep code changes to minimum for now + # Index of choices for nested utilities is different than unnested - this needs to be consistent for + # turning indexes into alternative names to keep code changes to minimum for now. Might want to look + # into changing this in the future when revisiting nested logit EET code. name_mapping = raw_utilities.columns.values del raw_utilities From cc8fda0f190921f662d7536f28cbdb7d92a6dbc0 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 31 Mar 2026 15:19:41 +1000 Subject: [PATCH 165/298] reset rng only for eet for now. discuss in engineering meeting --- activitysim/abm/models/location_choice.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index b76ecf83bb..5ea669aad0 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -1019,9 +1019,14 @@ def iterate_location_choice( ) = None # initialize to None, will be populated in first iteration for iteration in range(1, max_iterations + 1): - # RESET RNG offsets to identical state on each iteration. This ensures that the same set of random numbers is - # used on each iteration. - state.get_rn_generator().reset_offsets_for_step(state.current_model_name) + # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is + # used on each iteration. Only applying when using EET for now because this will need changes to integration + # tests, but we will probably want this for MC simulation as well. + if state.settings.use_explicit_error_terms and iteration > 1: + logger.debug( + f"{trace_label} resetting random number generator offsets for iteration {iteration}" + ) + state.get_rn_generator().reset_offsets_for_step(state.current_model_name) persons_merged_df_ = persons_merged_df.copy() From c1806fc619b981f70a217fec2a5339ed0b1a6ffb Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 31 Mar 2026 15:56:31 +1000 Subject: [PATCH 166/298] Close out remaining TODO-EETs --- activitysim/core/logit.py | 81 ++----- activitysim/core/simulate.py | 283 +++++++------------------ activitysim/core/test/test_logit.py | 60 ++---- activitysim/core/test/test_simulate.py | 81 +++++-- 4 files changed, 179 insertions(+), 326 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 5a58a87076..0354ccb2e5 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -258,18 +258,12 @@ def utils_to_probs( if allow_zero_probs: if overflow_protection: - warnings.warn( - "cannot set overflow_protection with allow_zero_probs", stacklevel=2 - ) + warnings.warn("cannot set overflow_protection with allow_zero_probs", stacklevel=2) overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85 if overflow_protection: - raise ValueError( - "cannot prevent expected overflow with allow_zero_probs" - ) + raise ValueError("cannot prevent expected overflow with allow_zero_probs") else: - overflow_protection = overflow_protection or ( - utils_arr.dtype == np.float32 and utils_arr.max() > 85 - ) + overflow_protection = overflow_protection or (utils_arr.dtype == np.float32 and utils_arr.max() > 85) if overflow_protection: # exponentiated utils will overflow, downshift them @@ -359,15 +353,11 @@ def add_ev1_random(state: workflow.State, df: pd.DataFrame): Utilities with EV1 errors added. """ nest_utils_for_choice = df.copy() - nest_utils_for_choice += state.get_rn_generator().gumbel_for_df( - nest_utils_for_choice, n=nest_utils_for_choice.shape[1] - ) + nest_utils_for_choice += state.get_rn_generator().gumbel_for_df(nest_utils_for_choice, n=nest_utils_for_choice.shape[1]) return nest_utils_for_choice -def choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name -): +def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name): for level, nest_names in logit_nest_groups.items(): if level == 1: next_level_alts = nest_alternatives_by_name[nest_names[0]] @@ -409,9 +399,7 @@ def make_choices_explicit_error_term_nl( Choice indices aligned to `alt_order_array`. """ if trace_label: - state.tracing.trace_df( - nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils") - ) + state.tracing.trace_df(nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils")) nest_utils_for_choice = add_ev1_random(state, nested_utilities) all_alternatives = set(nest.name for nest in each_nest(nest_spec, type="leaf")) @@ -424,9 +412,7 @@ def make_choices_explicit_error_term_nl( # to zero. Once the tree is walked (all alternatives have been processed), take the product of the alternatives in # each leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. choices = nest_utils_for_choice.apply( - lambda x: choose_from_tree( - x, all_alternatives, logit_nest_groups, nest_alternatives_by_name - ), + lambda x: choose_from_tree(x, all_alternatives, logit_nest_groups, nest_alternatives_by_name), axis=1, ) missing_choices = choices.isnull() # TODO: should we check for infs here too? @@ -449,9 +435,7 @@ def make_choices_explicit_error_term_nl( return choices -def make_choices_explicit_error_term_mnl( - state, utilities, trace_label, trace_choosers=None, allow_bad_utils=False -) -> pd.Series: +def make_choices_explicit_error_term_mnl(state, utilities, trace_label, trace_choosers=None, allow_bad_utils=False) -> pd.Series: """ Make EET choices for a multinomial logit model by adding EV1 errors. @@ -469,9 +453,7 @@ def make_choices_explicit_error_term_mnl( Choice indices aligned to the utilities columns order. """ if trace_label: - state.tracing.trace_df( - utilities, tracing.extend_trace_label(trace_label, "utilities") - ) + state.tracing.trace_df(utilities, tracing.extend_trace_label(trace_label, "utilities")) utilities_incl_unobs = add_ev1_random(state, utilities) if trace_label: state.tracing.trace_df( @@ -506,9 +488,7 @@ def make_choices_explicit_error_term( ) -> pd.Series: trace_label = tracing.extend_trace_label(trace_label, "make_choices_eet") if nest_spec is None: - choices = make_choices_explicit_error_term_mnl( - state, utilities, trace_label, trace_choosers, allow_bad_utils - ) + choices = make_choices_explicit_error_term_mnl(state, utilities, trace_label, trace_choosers, allow_bad_utils) else: choices = make_choices_explicit_error_term_nl( state, @@ -582,9 +562,7 @@ def make_choices( # probs should sum to 1 across each row BAD_PROB_THRESHOLD = 0.001 - bad_probs = probs.sum(axis=1).sub( - np.ones(len(probs.index)) - ).abs() > BAD_PROB_THRESHOLD * np.ones(len(probs.index)) + bad_probs = probs.sum(axis=1).sub(np.ones(len(probs.index))).abs() > BAD_PROB_THRESHOLD * np.ones(len(probs.index)) if bad_probs.any() and not allow_bad_probs: report_bad_choices( @@ -635,13 +613,9 @@ def interaction_dataset( """ if not choosers.index.is_unique: - raise TableIndexError( - "ERROR: choosers index is not unique, " "sample will not work correctly" - ) + raise TableIndexError("ERROR: choosers index is not unique, sample will not work correctly") if not alternatives.index.is_unique: - raise TableIndexError( - "ERROR: alternatives index is not unique, " "sample will not work correctly" - ) + raise TableIndexError("ERROR: alternatives index is not unique, sample will not work correctly") numchoosers = len(choosers) numalts = len(alternatives) @@ -651,9 +625,7 @@ def interaction_dataset( alts_idx = np.arange(numalts) if sample_size < numalts: - sample = state.get_rn_generator().choice_for_df( - choosers, alts_idx, sample_size, replace=False - ) + sample = state.get_rn_generator().choice_for_df(choosers, alts_idx, sample_size, replace=False) else: sample = np.tile(alts_idx, numchoosers) @@ -665,8 +637,7 @@ def interaction_dataset( alts_sample[alt_index_id] = alts_sample.index logger.debug( - "interaction_dataset pre-merge choosers %s alternatives %s alts_sample %s" - % (choosers.shape, alternatives.shape, alts_sample.shape) + "interaction_dataset pre-merge choosers %s alternatives %s alts_sample %s" % (choosers.shape, alternatives.shape, alts_sample.shape) ) # no need to do an expensive merge of alts and choosers @@ -735,18 +706,14 @@ def validate_nest_spec(nest_spec: dict | LogitNestSpec, trace_label: str): duplicates = [] for nest in each_nest(nest_spec): if nest.name in keys: - logger.error( - f"validate_nest_spec:duplicate nest key '{nest.name}' in nest spec - {trace_label}" - ) + logger.error(f"validate_nest_spec:duplicate nest key '{nest.name}' in nest spec - {trace_label}") duplicates.append(nest.name) keys.append(nest.name) # nest.print() if duplicates: - raise ModelConfigurationError( - f"validate_nest_spec:duplicate nest key/s '{duplicates}' in nest spec - {trace_label}" - ) + raise ModelConfigurationError(f"validate_nest_spec:duplicate nest key/s '{duplicates}' in nest spec - {trace_label}") def _each_nest(spec: LogitNestSpec, parent_nest, post_order): @@ -779,9 +746,7 @@ def _each_nest(spec: LogitNestSpec, parent_nest, post_order): if isinstance(spec, LogitNestSpec): name = spec.name coefficient = spec.coefficient - assert isinstance( - coefficient, int | float - ), f"Coefficient '{name}' ({coefficient}) not a number" # forgot to eval coefficient? + assert isinstance(coefficient, int | float), f"Coefficient '{name}' ({coefficient}) not a number" # forgot to eval coefficient? alternatives = [] for a in spec.alternatives: if isinstance(a, dict): @@ -844,9 +809,7 @@ def each_nest(nest_spec: dict | LogitNestSpec, type=None, post_order=False): Nest object with info about the current node (nest or leaf) """ if type is not None and type not in Nest.nest_types(): - raise ModelConfigurationError( - "Unknown nest type '%s' in call to each_nest" % type - ) + raise ModelConfigurationError("Unknown nest type '%s' in call to each_nest" % type) if isinstance(nest_spec, dict): nest_spec = LogitNestSpec.model_validate(nest_spec) @@ -863,11 +826,7 @@ def count_nests(nest_spec): def count_each_nest(spec, count): if isinstance(spec, dict): - return ( - count - + 1 - + sum([count_each_nest(alt, count) for alt in spec["alternatives"]]) - ) + return count + 1 + sum([count_each_nest(alt, count) for alt in spec["alternatives"]]) else: assert isinstance(spec, str) return 1 diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 6872df51db..f388568138 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -84,15 +84,11 @@ def read_model_alts(state: workflow.State, file_name, set_index=None): if "Alt" in df.columns: # Handle deprecated ALTS index warnings.warn( - "Support for 'Alt' column name in alternatives files will be removed." - " Use 'alt' (lowercase) instead.", + "Support for 'Alt' column name in alternatives files will be removed. Use 'alt' (lowercase) instead.", DeprecationWarning, ) # warning above does not actually output to logger, so also log it - logger.warning( - "Support for 'Alt' column name in alternatives files will be removed." - " Use 'alt' (lowercase) instead." - ) + logger.warning("Support for 'Alt' column name in alternatives files will be removed. Use 'alt' (lowercase) instead.") df.rename(columns={"Alt": "alt"}, inplace=True) df.set_index(set_index, inplace=True) @@ -180,14 +176,11 @@ def read_model_coefficients( else: assert file_name is None if isinstance(model_settings, BaseLogitComponentSettings) or ( - isinstance(model_settings, PydanticBase) - and hasattr(model_settings, "COEFFICIENTS") + isinstance(model_settings, PydanticBase) and hasattr(model_settings, "COEFFICIENTS") ): file_name = model_settings.COEFFICIENTS else: - assert ( - "COEFFICIENTS" in model_settings - ), "'COEFFICIENTS' tag not in model_settings in %s" % model_settings.get( + assert "COEFFICIENTS" in model_settings, "'COEFFICIENTS' tag not in model_settings in %s" % model_settings.get( "source_file_paths" ) file_name = model_settings["COEFFICIENTS"] @@ -201,16 +194,11 @@ def read_model_coefficients( raise if coefficients.index.duplicated().any(): - logger.warning( - f"duplicate coefficients in {file_path}\n" - f"{coefficients[coefficients.index.duplicated(keep=False)]}" - ) + logger.warning(f"duplicate coefficients in {file_path}\n{coefficients[coefficients.index.duplicated(keep=False)]}") raise ModelConfigurationError(f"duplicate coefficients in {file_path}") if coefficients.value.isnull().any(): - logger.warning( - f"null coefficients in {file_path}\n{coefficients[coefficients.value.isnull()]}" - ) + logger.warning(f"null coefficients in {file_path}\n{coefficients[coefficients.value.isnull()]}") raise ModelConfigurationError(f"null coefficients in {file_path}") return coefficients @@ -254,30 +242,21 @@ def spec_for_segment( # doesn't really matter what it is called, but this may catch errors assert spec.columns[0] in ["coefficient", segment_name] - if ( - coefficients_file_name is None - and isinstance(model_settings, dict) - and "COEFFICIENTS" in model_settings - ): + if coefficients_file_name is None and isinstance(model_settings, dict) and "COEFFICIENTS" in model_settings: coefficients_file_name = model_settings["COEFFICIENTS"] if coefficients_file_name is None: - logger.warning( - f"no coefficient file specified in model_settings for {spec_file_name}" - ) + logger.warning(f"no coefficient file specified in model_settings for {spec_file_name}") try: assert (spec.astype(float) == spec).all(axis=None) except (ValueError, AssertionError): raise ModelConfigurationError( - f"No coefficient file specified for {spec_file_name} " - f"but not all spec column values are numeric" + f"No coefficient file specified for {spec_file_name} but not all spec column values are numeric" ) from None return spec - coefficients = read_model_coefficients( - state.filesystem, file_name=coefficients_file_name - ) + coefficients = read_model_coefficients(state.filesystem, file_name=coefficients_file_name) spec = eval_coefficients(state, spec, coefficients, estimator) @@ -293,9 +272,7 @@ def read_model_coefficient_template( """ if isinstance(model_settings, dict): - assert ( - "COEFFICIENT_TEMPLATE" in model_settings - ), "'COEFFICIENT_TEMPLATE' not in model_settings in %s" % model_settings.get( + assert "COEFFICIENT_TEMPLATE" in model_settings, "'COEFFICIENT_TEMPLATE' not in model_settings in %s" % model_settings.get( "source_file_paths" ) coefficients_file_name = model_settings["COEFFICIENT_TEMPLATE"] @@ -321,9 +298,7 @@ def read_model_coefficient_template( if template.index.duplicated().any(): dupes = template[template.index.duplicated(keep=False)].sort_index() - logger.warning( - f"duplicate coefficient names in {coefficients_file_name}:\n{dupes}" - ) + logger.warning(f"duplicate coefficient names in {coefficients_file_name}:\n{dupes}") assert not template.index.duplicated().any() return template @@ -396,9 +371,7 @@ def get_segment_coefficients( and model_settings["COEFFICIENT_TEMPLATE"] is not None ): legacy = False - elif ( - "COEFFICIENTS" in model_settings and model_settings["COEFFICIENTS"] is not None - ): + elif "COEFFICIENTS" in model_settings and model_settings["COEFFICIENTS"] is not None: legacy = "COEFFICIENTS" warnings.warn( "Support for COEFFICIENTS without COEFFICIENT_TEMPLATE in model settings file will be removed." @@ -417,12 +390,8 @@ def get_segment_coefficients( if legacy: constants = config.get_model_constants(model_settings) - legacy_coeffs_file_path = filesystem.get_config_file_path( - model_settings[legacy] - ) - omnibus_coefficients = pd.read_csv( - legacy_coeffs_file_path, comment="#", index_col="coefficient_name" - ) + legacy_coeffs_file_path = filesystem.get_config_file_path(model_settings[legacy]) + omnibus_coefficients = pd.read_csv(legacy_coeffs_file_path, comment="#", index_col="coefficient_name") try: omnibus_coefficients_segment_name = omnibus_coefficients[segment_name] except KeyError: @@ -430,22 +399,17 @@ def get_segment_coefficients( possible_keys = "\n- ".join(omnibus_coefficients.keys()) logger.error(f"possible keys include: \n- {possible_keys}") raise - coefficients_dict = assign.evaluate_constants( - omnibus_coefficients_segment_name, constants=constants - ) + coefficients_dict = assign.evaluate_constants(omnibus_coefficients_segment_name, constants=constants) else: coefficients_df = filesystem.read_model_coefficients(model_settings) template_df = read_model_coefficient_template(filesystem, model_settings) - coefficients_col = ( - template_df[segment_name].map(coefficients_df.value).astype(float) - ) + coefficients_col = template_df[segment_name].map(coefficients_df.value).astype(float) if coefficients_col.isnull().any(): # show them the offending lines from interaction_coefficients_file logger.warning( - f"bad coefficients in COEFFICIENTS {model_settings['COEFFICIENTS']}\n" - f"{coefficients_col[coefficients_col.isnull()]}" + f"bad coefficients in COEFFICIENTS {model_settings['COEFFICIENTS']}\n{coefficients_col[coefficients_col.isnull()]}" ) assert not coefficients_col.isnull().any() @@ -454,17 +418,13 @@ def get_segment_coefficients( return coefficients_dict -def eval_nest_coefficients( - nest_spec: LogitNestSpec | dict, coefficients: dict, trace_label: str -) -> LogitNestSpec: +def eval_nest_coefficients(nest_spec: LogitNestSpec | dict, coefficients: dict, trace_label: str) -> LogitNestSpec: def replace_coefficients(nest: LogitNestSpec): if isinstance(nest, dict): assert "coefficient" in nest coefficient_name = nest["coefficient"] if isinstance(coefficient_name, str): - assert ( - coefficient_name in coefficients - ), f"{coefficient_name} not in nest coefficients" + assert coefficient_name in coefficients, f"{coefficient_name} not in nest coefficients" nest["coefficient"] = coefficients[coefficient_name] assert "alternatives" in nest @@ -473,9 +433,7 @@ def replace_coefficients(nest: LogitNestSpec): replace_coefficients(alternative) elif isinstance(nest, LogitNestSpec): if isinstance(nest.coefficient, str): - assert ( - nest.coefficient in coefficients - ), f"{nest.coefficient} not in nest coefficients" + assert nest.coefficient in coefficients, f"{nest.coefficient} not in nest coefficients" nest.coefficient = coefficients[nest.coefficient] for alternative in nest.alternatives: @@ -508,16 +466,12 @@ def eval_coefficients( assert "value" in coefficients.columns coefficients = coefficients["value"].to_dict() - assert isinstance( - coefficients, dict - ), "eval_coefficients doesn't grok type of coefficients: %s" % (type(coefficients)) + assert isinstance(coefficients, dict), "eval_coefficients doesn't grok type of coefficients: %s" % (type(coefficients)) for c in spec.columns: if c == SPEC_LABEL_NAME: continue - spec[c] = ( - spec[c].apply(lambda x: eval(str(x), {}, coefficients)).astype(np.float32) - ) + spec[c] = spec[c].apply(lambda x: eval(str(x), {}, coefficients)).astype(np.float32) sharrow_enabled = state.settings.sharrow if sharrow_enabled: @@ -647,15 +601,9 @@ def eval_utilities( if utilities is None or estimator or sharrow_enabled == "test": trace_label = tracing.extend_trace_label(trace_label, "eval_utils") - if ( - state.settings.expression_profile - and compute_settings.performance_log is None - ): + if state.settings.expression_profile and compute_settings.performance_log is None: perf_log_file = Path(trace_label + ".log") - elif ( - state.settings.expression_profile is False - or compute_settings.performance_log is False - ): + elif state.settings.expression_profile is False or compute_settings.performance_log is False: perf_log_file = None elif compute_settings.performance_log is True: perf_log_file = Path(trace_label + ".log") @@ -691,22 +639,16 @@ def eval_utilities( warnings.simplefilter("always") with performance_timer.time_expression(expr): if expr.startswith("@"): - expression_value = eval( - expr[1:], globals_dict, locals_dict - ) + expression_value = eval(expr[1:], globals_dict, locals_dict) else: expression_value = fast_eval(choosers, expr) if len(w) > 0: for wrn in w: - logger.warning( - f"{trace_label} - {type(wrn).__name__} ({wrn.message}) evaluating: {str(expr)}" - ) + logger.warning(f"{trace_label} - {type(wrn).__name__} ({wrn.message}) evaluating: {str(expr)}") except Exception as err: - logger.exception( - f"{trace_label} - {type(err).__name__} ({str(err)}) evaluating: {str(expr)}" - ) + logger.exception(f"{trace_label} - {type(err).__name__} ({str(err)}) evaluating: {str(expr)}") raise err if log_alt_losers: @@ -737,9 +679,7 @@ def eval_utilities( estimator.write_expression_values(df) # - compute_utilities - utilities = np.dot( - expression_values.transpose(), spec.astype(np.float64).values - ) + utilities = np.dot(expression_values.transpose(), spec.astype(np.float64).values) timelogger.mark("simple flow", True, logger=logger, suffix=trace_label) else: @@ -791,9 +731,7 @@ def eval_utilities( if trace_column_names is not None: if isinstance(trace_column_names, str): trace_column_names = [trace_column_names] - expression_values_df.columns = pd.MultiIndex.from_frame( - choosers.loc[trace_targets, trace_column_names] - ) + expression_values_df.columns = pd.MultiIndex.from_frame(choosers.loc[trace_targets, trace_column_names]) else: expression_values_df = None @@ -836,16 +774,13 @@ def eval_utilities( ) except AssertionError as err: print(err) - misses = np.where( - ~np.isclose(sh_util, utilities.values, rtol=1e-2, atol=1e-6) - ) + misses = np.where(~np.isclose(sh_util, utilities.values, rtol=1e-2, atol=1e-6)) _sh_util_miss1 = sh_util[tuple(m[0] for m in misses)] _u_miss1 = utilities.values[tuple(m[0] for m in misses)] _sh_util_miss1 - _u_miss1 if len(misses[0]) > sh_util.size * 0.01: print( - f"big problem: {len(misses[0])} missed close values " - f"out of {sh_util.size} ({100*len(misses[0]) / sh_util.size:.2f}%)" + f"big problem: {len(misses[0])} missed close values out of {sh_util.size} ({100 * len(misses[0]) / sh_util.size:.2f}%)" ) print(f"{sh_util.shape=}") print(misses) @@ -879,16 +814,12 @@ def eval_utilities( chunk_sizer.log_df(trace_label, "utilities", None) end_time = time.time() - logger.debug( - f"simulate.eval_utils runtime: {timedelta(seconds=end_time - start_time)} {trace_label}" - ) + logger.debug(f"simulate.eval_utils runtime: {timedelta(seconds=end_time - start_time)} {trace_label}") timelogger.summary(logger, "simulate.eval_utils timing") return utilities -def eval_variables( - state: workflow.State, exprs, df, locals_d=None, trace_label: str | None = None -): +def eval_variables(state: workflow.State, exprs, df, locals_d=None, trace_label: str | None = None): """ Evaluate a set of variable expressions from a spec in the context of a given data table. @@ -971,9 +902,7 @@ def to_array(x): values[expr] = expr_values except Exception as err: - logger.exception( - f"Variable evaluation failed {type(err).__name__} ({str(err)}) evaluating: {str(expr)}" - ) + logger.exception(f"Variable evaluation failed {type(err).__name__} ({str(err)}) evaluating: {str(expr)}") raise err values = util.df_from_dict(values, index=df.index) @@ -1031,13 +960,7 @@ def set_skim_wrapper_targets(df, skims, allow_partial_success: bool = True): such failure will be raised immediately, preventing partial success. """ - skims = ( - skims - if isinstance(skims, list) - else skims.values() - if isinstance(skims, dict) - else [skims] - ) + skims = skims if isinstance(skims, list) else skims.values() if isinstance(skims, dict) else [skims] problems = [] # assume any object in skims can be treated as a skim @@ -1135,15 +1058,11 @@ def compute_nested_utilities(raw_utilities, nest_spec): for nest in logit.each_nest(nest_spec, post_order=True): name = nest.name if nest.is_leaf: - nested_utilities[name] = ( - raw_utilities[name].astype(float) / nest.product_of_coefficients - ) + nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients else: # the alternative nested_utilities will already have been computed due to post_order with np.errstate(divide="ignore"): - nested_utilities[name] = nest.coefficient * np.log( - np.exp(nested_utilities[nest.alternatives]).sum(axis=1) - ) + nested_utilities[name] = nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) return nested_utilities @@ -1177,9 +1096,7 @@ def compute_nested_exp_utilities(raw_utilities, nest_spec): if nest.is_leaf: # leaf_utility = raw_utility / nest.product_of_coefficients - nested_utilities[name] = ( - raw_utilities[name].astype(float) / nest.product_of_coefficients - ) + nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients else: # nest node @@ -1188,9 +1105,7 @@ def compute_nested_exp_utilities(raw_utilities, nest_spec): # if all nest alternative utilities are zero # but the resulting inf will become 0 when exp is applied below with np.errstate(divide="ignore"): - nested_utilities[name] = nest.coefficient * np.log( - nested_utilities[nest.alternatives].sum(axis=1) - ) + nested_utilities[name] = nest.coefficient * np.log(nested_utilities[nest.alternatives].sum(axis=1)) # exponentiate the utility nested_utilities[name] = np.exp(nested_utilities[name]) @@ -1198,9 +1113,7 @@ def compute_nested_exp_utilities(raw_utilities, nest_spec): return nested_utilities -def compute_nested_probabilities( - state: workflow.State, nested_exp_utilities, nest_spec, trace_label -): +def compute_nested_probabilities(state: workflow.State, nested_exp_utilities, nest_spec, trace_label): """ compute nested probabilities for nest leafs and nodes probability for nest alternatives is simply the alternatives's local (to nest) probability @@ -1364,26 +1277,18 @@ def eval_mnl( ) if state.settings.use_explicit_error_terms: - utilities = logit.validate_utils( - state, utilities, trace_label=trace_label, trace_choosers=choosers - ) + utilities = logit.validate_utils(state, utilities, trace_label=trace_label, trace_choosers=choosers) if custom_chooser: - choices, rands = custom_chooser( - state, utilities, choosers, spec, trace_label - ) + choices, rands = custom_chooser(state, utilities, choosers, spec, trace_label) else: - choices, rands = logit.make_choices_utility_based( - state, utilities, trace_label=trace_label - ) + choices, rands = logit.make_choices_utility_based(state, utilities, trace_label=trace_label) del utilities chunk_sizer.log_df(trace_label, "utilities", None) else: - probs = logit.utils_to_probs( - state, utilities, trace_label=trace_label, trace_choosers=choosers - ) + probs = logit.utils_to_probs(state, utilities, trace_label=trace_label, trace_choosers=choosers) chunk_sizer.log_df(trace_label, "probs", probs) del utilities @@ -1406,9 +1311,7 @@ def eval_mnl( chunk_sizer.log_df(trace_label, "probs", None) if have_trace_targets: - state.tracing.trace_df( - choices, "%s.choices" % trace_label, columns=[None, trace_choice_name] - ) + state.tracing.trace_df(choices, "%s.choices" % trace_label, columns=[None, trace_choice_name]) state.tracing.trace_df(rands, "%s.rands" % trace_label, columns=[None, "rand"]) return choices @@ -1505,26 +1408,28 @@ def eval_nl( ) if state.settings.use_explicit_error_terms: - # TODO-EET: Nested utility zero choice probability - raw_utilities = logit.validate_utils( - state, raw_utilities, allow_zero_probs=True, trace_label=trace_label - ) + raw_utilities = logit.validate_utils(state, raw_utilities, allow_zero_probs=True, trace_label=trace_label) + + # validate_utils uses allow_zero_probs=True because individual nests + # can legitimately have all alternatives unavailable. But we still need + # to catch choosers where *every* leaf alternative is unavailable. + all_unavailable = (raw_utilities == logit.UTIL_UNAVAILABLE).all(axis=1) + if all_unavailable.any(): + logit.report_bad_choices( + state, + all_unavailable, + raw_utilities, + trace_label=tracing.extend_trace_label(trace_label, "zero_prob_utils"), + trace_choosers=choosers, + msg="all alternatives have zero probability", + ) # utilities of leaves and nests nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) chunk_sizer.log_df(trace_label, "nested_utilities", nested_utilities) - # TODO-EET: use nested_utiltites directly to compute logsums? if want_logsums: - # logsum of nest root - # exponentiated utilities of leaves and nests - nested_exp_utilities = compute_nested_exp_utilities( - raw_utilities, nest_spec - ) - chunk_sizer.log_df( - trace_label, "nested_exp_utilities", nested_exp_utilities - ) - logsums = pd.Series(np.log(nested_exp_utilities.root), index=choosers.index) + logsums = pd.Series(nested_utilities.root, index=choosers.index) chunk_sizer.log_df(trace_label, "logsums", logsums) # Index of choices for nested utilities is different than unnested - this needs to be consistent for @@ -1573,9 +1478,7 @@ def eval_nl( ) # probabilities of alternatives relative to siblings sharing the same nest - nested_probabilities = compute_nested_probabilities( - state, nested_exp_utilities, nest_spec, trace_label=trace_label - ) + nested_probabilities = compute_nested_probabilities(state, nested_exp_utilities, nest_spec, trace_label=trace_label) chunk_sizer.log_df(trace_label, "nested_probabilities", nested_probabilities) if want_logsums: @@ -1594,9 +1497,7 @@ def eval_nl( ) # global (flattened) leaf probabilities based on relative nest coefficients (in spec order) - base_probabilities = compute_base_probabilities( - nested_probabilities, nest_spec, spec - ) + base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) chunk_sizer.log_df(trace_label, "base_probabilities", base_probabilities) del nested_probabilities @@ -1633,22 +1534,16 @@ def eval_nl( trace_label, ) else: - choices, rands = logit.make_choices( - state, base_probabilities, trace_label=trace_label - ) + choices, rands = logit.make_choices(state, base_probabilities, trace_label=trace_label) del base_probabilities chunk_sizer.log_df(trace_label, "base_probabilities", None) if have_trace_targets: - state.tracing.trace_df( - choices, "%s.choices" % trace_label, columns=[None, trace_choice_name] - ) + state.tracing.trace_df(choices, "%s.choices" % trace_label, columns=[None, trace_choice_name]) state.tracing.trace_df(rands, f"{trace_label}.rands", columns=[None, "rand"]) if want_logsums: - state.tracing.trace_df( - logsums, f"{trace_label}.logsums", columns=[None, "logsum"] - ) + state.tracing.trace_df(logsums, f"{trace_label}.logsums", columns=[None, "logsum"]) if want_logsums: choices = choices.to_frame("choice") @@ -1734,11 +1629,7 @@ def _simple_simulate( # if tracing is not enabled, drop unused columns # if not estimation mode, drop unused columns - if ( - (not have_trace_targets) - and (estimator is None) - and (compute_settings.drop_unused_columns) - ): + if (not have_trace_targets) and (estimator is None) and (compute_settings.drop_unused_columns): # drop unused variables in chooser table choosers = util.drop_unused_columns( choosers, @@ -1788,23 +1679,9 @@ def _simple_simulate( def tvpb_skims(skims): def list_of_skims(skims): - return ( - skims - if isinstance(skims, list) - else ( - skims.values() - if isinstance(skims, dict) - else [skims] - if skims is not None - else [] - ) - ) + return skims if isinstance(skims, list) else (skims.values() if isinstance(skims, dict) else [skims] if skims is not None else []) - return [ - skim - for skim in list_of_skims(skims) - if isinstance(skim, pathbuilder.TransitVirtualPathLogsumWrapper) - ] + return [skim for skim in list_of_skims(skims) if isinstance(skim, pathbuilder.TransitVirtualPathLogsumWrapper)] def simple_simulate( @@ -1981,9 +1858,7 @@ def eval_mnl_logsums( # trace utilities if have_trace_targets: - state.tracing.trace_df( - logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"] - ) + state.tracing.trace_df(logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"]) return logsums @@ -2010,17 +1885,13 @@ def _preprocess_tvpb_logsums_on_choosers(choosers, spec, locals_d): spec_sh = spec.copy() def _replace_in_level(multiindex, level_name, *args, **kwargs): - y = multiindex.levels[multiindex.names.index(level_name)].str.replace( - *args, **kwargs - ) + y = multiindex.levels[multiindex.names.index(level_name)].str.replace(*args, **kwargs) return multiindex.set_levels(y, level=level_name) # Preprocess TVPB logsums outside sharrow if "tvpb_logsum_odt" in locals_d: tvpb = locals_d["tvpb_logsum_odt"] - path_types = tvpb.tvpb.network_los.setting( - f"TVPB_SETTINGS.{tvpb.recipe}.path_types" - ).keys() + path_types = tvpb.tvpb.network_los.setting(f"TVPB_SETTINGS.{tvpb.recipe}.path_types").keys() assignments = {} for path_type in ["WTW", "DTW"]: if path_type not in path_types: @@ -2042,9 +1913,7 @@ def _replace_in_level(multiindex, level_name, *args, **kwargs): if "tvpb_logsum_dot" in locals_d: tvpb = locals_d["tvpb_logsum_dot"] - path_types = tvpb.tvpb.network_los.setting( - f"TVPB_SETTINGS.{tvpb.recipe}.path_types" - ).keys() + path_types = tvpb.tvpb.network_los.setting(f"TVPB_SETTINGS.{tvpb.recipe}.path_types").keys() assignments = {} for path_type in ["WTW", "WTD"]: if path_type not in path_types: @@ -2138,9 +2007,7 @@ def eval_nl_logsums( "%s.nested_exp_utilities" % trace_label, column_labels=["alternative", "utility"], ) - state.tracing.trace_df( - logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"] - ) + state.tracing.trace_df(logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"]) del nested_exp_utilities # done with nested_exp_utilities chunk_sizer.log_df(trace_label, "nested_exp_utilities", None) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 1ad8c52a90..0f2dc9abf2 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -26,9 +26,7 @@ def data_dir(): ( "fish.csv", "fish_choosers.csv", - pd.DataFrame( - [[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"] - ), + pd.DataFrame([[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"]), pd.DataFrame( [ [0.2849598, 0.2742482, 0.1605457, 0.2802463], @@ -259,14 +257,10 @@ def test_utils_to_probs_raises(): # def test_make_choices_only_one(): state = workflow.State().default_settings() - probs = pd.DataFrame( - [[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"] - ) + probs = pd.DataFrame([[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"]) choices, rands = logit.make_choices(state, probs) - pdt.assert_series_equal( - choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False - ) + pdt.assert_series_equal(choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False) def test_make_choices_real_probs(utilities): @@ -315,9 +309,7 @@ def test_different_order_make_choices(): choices_shuffled, rands_shuffled = logit.make_choices(state, probs_shuffled) # sorting both to ensure comparison is on the same index order - pdt.assert_series_equal( - choices.sort_index(), choices_shuffled.sort_index(), check_dtype=False - ) + pdt.assert_series_equal(choices.sort_index(), choices_shuffled.sort_index(), check_dtype=False) def test_make_choices_matches_random_draws(): @@ -435,9 +427,7 @@ def test_choose_from_tree_selects_leaf(): "motorized": ["car", "bus"], } - choice = logit.choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name - ) + choice = logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) assert choice == "car" @@ -452,9 +442,7 @@ def test_choose_from_tree_raises_on_missing_leaf(): } with pytest.raises(ValueError, match="no alternative found"): - logit.choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name - ) + logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) # @@ -566,9 +554,7 @@ class MCDummyState: def get_rn_generator(): return MCDummyRNG() - probs = logit.utils_to_probs( - MCDummyState(), utils, trace_label=None, overflow_protection=True - ) + probs = logit.utils_to_probs(MCDummyState(), utils, trace_label=None, overflow_protection=True) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) # Explicit-error-term (EET) path — independent RNG @@ -583,20 +569,14 @@ class EETDummyState: def get_rn_generator(): return EETDummyRNG() - choices_eet = logit.make_choices_explicit_error_term_mnl( - EETDummyState(), utils, trace_label=None - ) + choices_eet = logit.make_choices_explicit_error_term_mnl(EETDummyState(), utils, trace_label=None) mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=n_alts) / n_draws eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=n_alts) / n_draws np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol, rtol=r_tol) - np.testing.assert_allclose( - mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol - ) - np.testing.assert_allclose( - eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol - ) + np.testing.assert_allclose(mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) + np.testing.assert_allclose(eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) def test_make_choices_vs_eet_nl_same_distribution(): @@ -641,15 +621,9 @@ def default_settings(self): return self # Compute probabilities for NL using simulation logic - nested_exp_utilities = simulate.compute_nested_exp_utilities( - utils_df[["car", "bus", "walk"]], nest_spec - ) - nested_probabilities = simulate.compute_nested_probabilities( - MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None - ) - probs = simulate.compute_base_probabilities( - nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]] - ) + nested_exp_utilities = simulate.compute_nested_exp_utilities(utils_df[["car", "bus", "walk"]], nest_spec) + nested_probabilities = simulate.compute_nested_probabilities(MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None) + probs = simulate.compute_base_probabilities(nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]]) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) # 2. EET-based Nested Logit choices @@ -676,9 +650,7 @@ def tracing(self): # For EET NL, we provide the utilities for all nodes. # compute_nested_utilities handles the division by nesting coefficients for leaves # and the logsum * coefficient for internal nodes. - nested_utilities = simulate.compute_nested_utilities( - utils_df[["car", "bus", "walk"]], nest_spec - ) + nested_utilities = simulate.compute_nested_utilities(utils_df[["car", "bus", "walk"]], nest_spec) choices_eet = logit.make_choices_explicit_error_term_nl( EETDummyState(), @@ -707,9 +679,7 @@ def test_interaction_dataset_no_sample(interaction_choosers, interaction_alts): index=[1, 2, 3, 4] * 4, ) - interacted = logit.interaction_dataset( - workflow.State().default_settings(), interaction_choosers, interaction_alts - ) + interacted = logit.interaction_dataset(workflow.State().default_settings(), interaction_choosers, interaction_alts) interacted, expected = interacted.align(expected, axis=1) pdt.assert_frame_equal(interacted, expected) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 38e2be237a..03b3ce8001 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -26,9 +26,7 @@ def spec_name(data_dir): @pytest.fixture def state(data_dir) -> workflow.State: state = workflow.State() - state.initialize_filesystem( - working_dir=os.path.dirname(__file__), data_dir=(data_dir,) - ).default_settings() + state.initialize_filesystem(working_dir=os.path.dirname(__file__), data_dir=(data_dir,)).default_settings() return state @@ -67,9 +65,7 @@ def test_read_model_spec(state, spec_name): def test_eval_variables(state, spec, data): result = simulate.eval_variables(state, spec.index, data) - expected = pd.DataFrame( - [[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index - ) + expected = pd.DataFrame([[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index) expected[expected.columns[0]] = expected[expected.columns[0]].astype(np.int8) expected[expected.columns[1]] = expected[expected.columns[1]].astype(np.int8) @@ -255,13 +251,74 @@ def test_compute_nested_utilities(nest_spec): constructed_nested_utilities = pd.DataFrame(index=raw_utilities.index) constructed_nested_utilities[leaf_utilities.columns] = leaf_utilities - constructed_nested_utilities["alt0"] = alt0_nest_coefficient * np.log( - np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1) - ) + constructed_nested_utilities["alt0"] = alt0_nest_coefficient * np.log(np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1)) constructed_nested_utilities["root"] = nest_spec["coefficient"] * np.log( np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1) ) - assert np.allclose( - nested_utilities, constructed_nested_utilities[nested_utilities.columns] - ), "Mismatch in nested utilities" + assert np.allclose(nested_utilities, constructed_nested_utilities[nested_utilities.columns]), "Mismatch in nested utilities" + + +def test_eval_nl_logsums_eet_vs_non_eet(state, nest_spec): + """eval_nl with want_logsums=True must produce identical logsums under + EET and non-EET modes""" + + num_choosers = 100 + + np.random.seed(42) + data2 = pd.DataFrame( + {"chooser_attr": np.random.rand(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + + spec2 = pd.DataFrame( + {"alt1": [2.0], "alt0.0": [0.5], "alt0.1": [0.2]}, + index=pd.Index(["chooser_attr"], name="Expression"), + ) + + chunk_sizer = chunk.ChunkSizer(state, "", "", num_choosers) + + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", data2) + state.rng().begin_step("test_step_logsums") + + result_eet = simulate.eval_nl( + state=state, + choosers=data2, + spec=spec2, + nest_spec=nest_spec, + locals_d={}, + custom_chooser=None, + estimator=None, + want_logsums=True, + trace_label="test", + chunk_sizer=chunk_sizer, + ) + + state.rng().end_step("test_step_logsums") + + state.settings.use_explicit_error_terms = False + state.rng().begin_step("test_step_logsums") + + result_non_eet = simulate.eval_nl( + state=state, + choosers=data2, + spec=spec2, + nest_spec=nest_spec, + locals_d={}, + custom_chooser=None, + estimator=None, + want_logsums=True, + trace_label="test", + chunk_sizer=chunk_sizer, + ) + + state.rng().end_step("test_step_logsums") + + # Both paths should return a DataFrame with 'choice' and 'logsum' columns + assert "logsum" in result_eet.columns, "EET result missing logsum column" + assert "logsum" in result_non_eet.columns, "non-EET result missing logsum column" + + # Logsums are deterministic — they must be identical across paths + assert np.allclose(result_eet["logsum"].values, result_non_eet["logsum"].values, rtol=1e-10) From 327047443204db971018df7b7d73176477d2a1fb Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 31 Mar 2026 16:01:53 +1000 Subject: [PATCH 167/298] Shuffle check to logit.py --- activitysim/core/logit.py | 14 +++++++++++++- activitysim/core/simulate.py | 14 -------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 0354ccb2e5..dad23c1315 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -179,7 +179,19 @@ def validate_utils( arr_sum = utils_arr.sum(axis=1) - if not allow_zero_probs: + if allow_zero_probs: + # only worry if all alts for a chooser are unavailable, which would lead to all zero probabilities and thus no choice + all_unavailable = (utils_arr == UTIL_UNAVAILABLE).all(axis=1) + if all_unavailable.any(): + report_bad_choices( + state, + all_unavailable, + utils_arr, + trace_label=tracing.extend_trace_label(trace_label, "zero_prob_utils"), + msg="all alternatives have zero probability", + trace_choosers=trace_choosers, + ) + else: zero_probs = arr_sum <= utils_arr.shape[1] * UTIL_UNAVAILABLE if zero_probs.any(): report_bad_choices( diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index f388568138..c0f1711cd6 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -1410,20 +1410,6 @@ def eval_nl( if state.settings.use_explicit_error_terms: raw_utilities = logit.validate_utils(state, raw_utilities, allow_zero_probs=True, trace_label=trace_label) - # validate_utils uses allow_zero_probs=True because individual nests - # can legitimately have all alternatives unavailable. But we still need - # to catch choosers where *every* leaf alternative is unavailable. - all_unavailable = (raw_utilities == logit.UTIL_UNAVAILABLE).all(axis=1) - if all_unavailable.any(): - logit.report_bad_choices( - state, - all_unavailable, - raw_utilities, - trace_label=tracing.extend_trace_label(trace_label, "zero_prob_utils"), - trace_choosers=choosers, - msg="all alternatives have zero probability", - ) - # utilities of leaves and nests nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) chunk_sizer.log_df(trace_label, "nested_utilities", nested_utilities) From dd547115b12d6520c4785fa7b10adc02940bbc65 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 31 Mar 2026 16:06:18 +1000 Subject: [PATCH 168/298] Linting --- activitysim/core/logit.py | 81 +++++++-- activitysim/core/simulate.py | 243 +++++++++++++++++++------ activitysim/core/test/test_logit.py | 60 ++++-- activitysim/core/test/test_simulate.py | 20 +- 4 files changed, 308 insertions(+), 96 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index dad23c1315..f5dedd510c 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -270,12 +270,18 @@ def utils_to_probs( if allow_zero_probs: if overflow_protection: - warnings.warn("cannot set overflow_protection with allow_zero_probs", stacklevel=2) + warnings.warn( + "cannot set overflow_protection with allow_zero_probs", stacklevel=2 + ) overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85 if overflow_protection: - raise ValueError("cannot prevent expected overflow with allow_zero_probs") + raise ValueError( + "cannot prevent expected overflow with allow_zero_probs" + ) else: - overflow_protection = overflow_protection or (utils_arr.dtype == np.float32 and utils_arr.max() > 85) + overflow_protection = overflow_protection or ( + utils_arr.dtype == np.float32 and utils_arr.max() > 85 + ) if overflow_protection: # exponentiated utils will overflow, downshift them @@ -365,11 +371,15 @@ def add_ev1_random(state: workflow.State, df: pd.DataFrame): Utilities with EV1 errors added. """ nest_utils_for_choice = df.copy() - nest_utils_for_choice += state.get_rn_generator().gumbel_for_df(nest_utils_for_choice, n=nest_utils_for_choice.shape[1]) + nest_utils_for_choice += state.get_rn_generator().gumbel_for_df( + nest_utils_for_choice, n=nest_utils_for_choice.shape[1] + ) return nest_utils_for_choice -def choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name): +def choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name +): for level, nest_names in logit_nest_groups.items(): if level == 1: next_level_alts = nest_alternatives_by_name[nest_names[0]] @@ -411,7 +421,9 @@ def make_choices_explicit_error_term_nl( Choice indices aligned to `alt_order_array`. """ if trace_label: - state.tracing.trace_df(nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils")) + state.tracing.trace_df( + nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils") + ) nest_utils_for_choice = add_ev1_random(state, nested_utilities) all_alternatives = set(nest.name for nest in each_nest(nest_spec, type="leaf")) @@ -424,7 +436,9 @@ def make_choices_explicit_error_term_nl( # to zero. Once the tree is walked (all alternatives have been processed), take the product of the alternatives in # each leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. choices = nest_utils_for_choice.apply( - lambda x: choose_from_tree(x, all_alternatives, logit_nest_groups, nest_alternatives_by_name), + lambda x: choose_from_tree( + x, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ), axis=1, ) missing_choices = choices.isnull() # TODO: should we check for infs here too? @@ -447,7 +461,9 @@ def make_choices_explicit_error_term_nl( return choices -def make_choices_explicit_error_term_mnl(state, utilities, trace_label, trace_choosers=None, allow_bad_utils=False) -> pd.Series: +def make_choices_explicit_error_term_mnl( + state, utilities, trace_label, trace_choosers=None, allow_bad_utils=False +) -> pd.Series: """ Make EET choices for a multinomial logit model by adding EV1 errors. @@ -465,7 +481,9 @@ def make_choices_explicit_error_term_mnl(state, utilities, trace_label, trace_ch Choice indices aligned to the utilities columns order. """ if trace_label: - state.tracing.trace_df(utilities, tracing.extend_trace_label(trace_label, "utilities")) + state.tracing.trace_df( + utilities, tracing.extend_trace_label(trace_label, "utilities") + ) utilities_incl_unobs = add_ev1_random(state, utilities) if trace_label: state.tracing.trace_df( @@ -500,7 +518,9 @@ def make_choices_explicit_error_term( ) -> pd.Series: trace_label = tracing.extend_trace_label(trace_label, "make_choices_eet") if nest_spec is None: - choices = make_choices_explicit_error_term_mnl(state, utilities, trace_label, trace_choosers, allow_bad_utils) + choices = make_choices_explicit_error_term_mnl( + state, utilities, trace_label, trace_choosers, allow_bad_utils + ) else: choices = make_choices_explicit_error_term_nl( state, @@ -574,7 +594,9 @@ def make_choices( # probs should sum to 1 across each row BAD_PROB_THRESHOLD = 0.001 - bad_probs = probs.sum(axis=1).sub(np.ones(len(probs.index))).abs() > BAD_PROB_THRESHOLD * np.ones(len(probs.index)) + bad_probs = probs.sum(axis=1).sub( + np.ones(len(probs.index)) + ).abs() > BAD_PROB_THRESHOLD * np.ones(len(probs.index)) if bad_probs.any() and not allow_bad_probs: report_bad_choices( @@ -625,9 +647,13 @@ def interaction_dataset( """ if not choosers.index.is_unique: - raise TableIndexError("ERROR: choosers index is not unique, sample will not work correctly") + raise TableIndexError( + "ERROR: choosers index is not unique, sample will not work correctly" + ) if not alternatives.index.is_unique: - raise TableIndexError("ERROR: alternatives index is not unique, sample will not work correctly") + raise TableIndexError( + "ERROR: alternatives index is not unique, sample will not work correctly" + ) numchoosers = len(choosers) numalts = len(alternatives) @@ -637,7 +663,9 @@ def interaction_dataset( alts_idx = np.arange(numalts) if sample_size < numalts: - sample = state.get_rn_generator().choice_for_df(choosers, alts_idx, sample_size, replace=False) + sample = state.get_rn_generator().choice_for_df( + choosers, alts_idx, sample_size, replace=False + ) else: sample = np.tile(alts_idx, numchoosers) @@ -649,7 +677,8 @@ def interaction_dataset( alts_sample[alt_index_id] = alts_sample.index logger.debug( - "interaction_dataset pre-merge choosers %s alternatives %s alts_sample %s" % (choosers.shape, alternatives.shape, alts_sample.shape) + "interaction_dataset pre-merge choosers %s alternatives %s alts_sample %s" + % (choosers.shape, alternatives.shape, alts_sample.shape) ) # no need to do an expensive merge of alts and choosers @@ -718,14 +747,18 @@ def validate_nest_spec(nest_spec: dict | LogitNestSpec, trace_label: str): duplicates = [] for nest in each_nest(nest_spec): if nest.name in keys: - logger.error(f"validate_nest_spec:duplicate nest key '{nest.name}' in nest spec - {trace_label}") + logger.error( + f"validate_nest_spec:duplicate nest key '{nest.name}' in nest spec - {trace_label}" + ) duplicates.append(nest.name) keys.append(nest.name) # nest.print() if duplicates: - raise ModelConfigurationError(f"validate_nest_spec:duplicate nest key/s '{duplicates}' in nest spec - {trace_label}") + raise ModelConfigurationError( + f"validate_nest_spec:duplicate nest key/s '{duplicates}' in nest spec - {trace_label}" + ) def _each_nest(spec: LogitNestSpec, parent_nest, post_order): @@ -758,7 +791,9 @@ def _each_nest(spec: LogitNestSpec, parent_nest, post_order): if isinstance(spec, LogitNestSpec): name = spec.name coefficient = spec.coefficient - assert isinstance(coefficient, int | float), f"Coefficient '{name}' ({coefficient}) not a number" # forgot to eval coefficient? + assert isinstance( + coefficient, int | float + ), f"Coefficient '{name}' ({coefficient}) not a number" # forgot to eval coefficient? alternatives = [] for a in spec.alternatives: if isinstance(a, dict): @@ -821,7 +856,9 @@ def each_nest(nest_spec: dict | LogitNestSpec, type=None, post_order=False): Nest object with info about the current node (nest or leaf) """ if type is not None and type not in Nest.nest_types(): - raise ModelConfigurationError("Unknown nest type '%s' in call to each_nest" % type) + raise ModelConfigurationError( + "Unknown nest type '%s' in call to each_nest" % type + ) if isinstance(nest_spec, dict): nest_spec = LogitNestSpec.model_validate(nest_spec) @@ -838,7 +875,11 @@ def count_nests(nest_spec): def count_each_nest(spec, count): if isinstance(spec, dict): - return count + 1 + sum([count_each_nest(alt, count) for alt in spec["alternatives"]]) + return ( + count + + 1 + + sum([count_each_nest(alt, count) for alt in spec["alternatives"]]) + ) else: assert isinstance(spec, str) return 1 diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index c0f1711cd6..1079d9b6f2 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -88,7 +88,9 @@ def read_model_alts(state: workflow.State, file_name, set_index=None): DeprecationWarning, ) # warning above does not actually output to logger, so also log it - logger.warning("Support for 'Alt' column name in alternatives files will be removed. Use 'alt' (lowercase) instead.") + logger.warning( + "Support for 'Alt' column name in alternatives files will be removed. Use 'alt' (lowercase) instead." + ) df.rename(columns={"Alt": "alt"}, inplace=True) df.set_index(set_index, inplace=True) @@ -176,11 +178,14 @@ def read_model_coefficients( else: assert file_name is None if isinstance(model_settings, BaseLogitComponentSettings) or ( - isinstance(model_settings, PydanticBase) and hasattr(model_settings, "COEFFICIENTS") + isinstance(model_settings, PydanticBase) + and hasattr(model_settings, "COEFFICIENTS") ): file_name = model_settings.COEFFICIENTS else: - assert "COEFFICIENTS" in model_settings, "'COEFFICIENTS' tag not in model_settings in %s" % model_settings.get( + assert ( + "COEFFICIENTS" in model_settings + ), "'COEFFICIENTS' tag not in model_settings in %s" % model_settings.get( "source_file_paths" ) file_name = model_settings["COEFFICIENTS"] @@ -194,11 +199,15 @@ def read_model_coefficients( raise if coefficients.index.duplicated().any(): - logger.warning(f"duplicate coefficients in {file_path}\n{coefficients[coefficients.index.duplicated(keep=False)]}") + logger.warning( + f"duplicate coefficients in {file_path}\n{coefficients[coefficients.index.duplicated(keep=False)]}" + ) raise ModelConfigurationError(f"duplicate coefficients in {file_path}") if coefficients.value.isnull().any(): - logger.warning(f"null coefficients in {file_path}\n{coefficients[coefficients.value.isnull()]}") + logger.warning( + f"null coefficients in {file_path}\n{coefficients[coefficients.value.isnull()]}" + ) raise ModelConfigurationError(f"null coefficients in {file_path}") return coefficients @@ -242,11 +251,17 @@ def spec_for_segment( # doesn't really matter what it is called, but this may catch errors assert spec.columns[0] in ["coefficient", segment_name] - if coefficients_file_name is None and isinstance(model_settings, dict) and "COEFFICIENTS" in model_settings: + if ( + coefficients_file_name is None + and isinstance(model_settings, dict) + and "COEFFICIENTS" in model_settings + ): coefficients_file_name = model_settings["COEFFICIENTS"] if coefficients_file_name is None: - logger.warning(f"no coefficient file specified in model_settings for {spec_file_name}") + logger.warning( + f"no coefficient file specified in model_settings for {spec_file_name}" + ) try: assert (spec.astype(float) == spec).all(axis=None) except (ValueError, AssertionError): @@ -256,7 +271,9 @@ def spec_for_segment( return spec - coefficients = read_model_coefficients(state.filesystem, file_name=coefficients_file_name) + coefficients = read_model_coefficients( + state.filesystem, file_name=coefficients_file_name + ) spec = eval_coefficients(state, spec, coefficients, estimator) @@ -272,7 +289,9 @@ def read_model_coefficient_template( """ if isinstance(model_settings, dict): - assert "COEFFICIENT_TEMPLATE" in model_settings, "'COEFFICIENT_TEMPLATE' not in model_settings in %s" % model_settings.get( + assert ( + "COEFFICIENT_TEMPLATE" in model_settings + ), "'COEFFICIENT_TEMPLATE' not in model_settings in %s" % model_settings.get( "source_file_paths" ) coefficients_file_name = model_settings["COEFFICIENT_TEMPLATE"] @@ -298,7 +317,9 @@ def read_model_coefficient_template( if template.index.duplicated().any(): dupes = template[template.index.duplicated(keep=False)].sort_index() - logger.warning(f"duplicate coefficient names in {coefficients_file_name}:\n{dupes}") + logger.warning( + f"duplicate coefficient names in {coefficients_file_name}:\n{dupes}" + ) assert not template.index.duplicated().any() return template @@ -371,7 +392,9 @@ def get_segment_coefficients( and model_settings["COEFFICIENT_TEMPLATE"] is not None ): legacy = False - elif "COEFFICIENTS" in model_settings and model_settings["COEFFICIENTS"] is not None: + elif ( + "COEFFICIENTS" in model_settings and model_settings["COEFFICIENTS"] is not None + ): legacy = "COEFFICIENTS" warnings.warn( "Support for COEFFICIENTS without COEFFICIENT_TEMPLATE in model settings file will be removed." @@ -390,8 +413,12 @@ def get_segment_coefficients( if legacy: constants = config.get_model_constants(model_settings) - legacy_coeffs_file_path = filesystem.get_config_file_path(model_settings[legacy]) - omnibus_coefficients = pd.read_csv(legacy_coeffs_file_path, comment="#", index_col="coefficient_name") + legacy_coeffs_file_path = filesystem.get_config_file_path( + model_settings[legacy] + ) + omnibus_coefficients = pd.read_csv( + legacy_coeffs_file_path, comment="#", index_col="coefficient_name" + ) try: omnibus_coefficients_segment_name = omnibus_coefficients[segment_name] except KeyError: @@ -399,12 +426,16 @@ def get_segment_coefficients( possible_keys = "\n- ".join(omnibus_coefficients.keys()) logger.error(f"possible keys include: \n- {possible_keys}") raise - coefficients_dict = assign.evaluate_constants(omnibus_coefficients_segment_name, constants=constants) + coefficients_dict = assign.evaluate_constants( + omnibus_coefficients_segment_name, constants=constants + ) else: coefficients_df = filesystem.read_model_coefficients(model_settings) template_df = read_model_coefficient_template(filesystem, model_settings) - coefficients_col = template_df[segment_name].map(coefficients_df.value).astype(float) + coefficients_col = ( + template_df[segment_name].map(coefficients_df.value).astype(float) + ) if coefficients_col.isnull().any(): # show them the offending lines from interaction_coefficients_file @@ -418,13 +449,17 @@ def get_segment_coefficients( return coefficients_dict -def eval_nest_coefficients(nest_spec: LogitNestSpec | dict, coefficients: dict, trace_label: str) -> LogitNestSpec: +def eval_nest_coefficients( + nest_spec: LogitNestSpec | dict, coefficients: dict, trace_label: str +) -> LogitNestSpec: def replace_coefficients(nest: LogitNestSpec): if isinstance(nest, dict): assert "coefficient" in nest coefficient_name = nest["coefficient"] if isinstance(coefficient_name, str): - assert coefficient_name in coefficients, f"{coefficient_name} not in nest coefficients" + assert ( + coefficient_name in coefficients + ), f"{coefficient_name} not in nest coefficients" nest["coefficient"] = coefficients[coefficient_name] assert "alternatives" in nest @@ -433,7 +468,9 @@ def replace_coefficients(nest: LogitNestSpec): replace_coefficients(alternative) elif isinstance(nest, LogitNestSpec): if isinstance(nest.coefficient, str): - assert nest.coefficient in coefficients, f"{nest.coefficient} not in nest coefficients" + assert ( + nest.coefficient in coefficients + ), f"{nest.coefficient} not in nest coefficients" nest.coefficient = coefficients[nest.coefficient] for alternative in nest.alternatives: @@ -466,12 +503,16 @@ def eval_coefficients( assert "value" in coefficients.columns coefficients = coefficients["value"].to_dict() - assert isinstance(coefficients, dict), "eval_coefficients doesn't grok type of coefficients: %s" % (type(coefficients)) + assert isinstance( + coefficients, dict + ), "eval_coefficients doesn't grok type of coefficients: %s" % (type(coefficients)) for c in spec.columns: if c == SPEC_LABEL_NAME: continue - spec[c] = spec[c].apply(lambda x: eval(str(x), {}, coefficients)).astype(np.float32) + spec[c] = ( + spec[c].apply(lambda x: eval(str(x), {}, coefficients)).astype(np.float32) + ) sharrow_enabled = state.settings.sharrow if sharrow_enabled: @@ -601,9 +642,15 @@ def eval_utilities( if utilities is None or estimator or sharrow_enabled == "test": trace_label = tracing.extend_trace_label(trace_label, "eval_utils") - if state.settings.expression_profile and compute_settings.performance_log is None: + if ( + state.settings.expression_profile + and compute_settings.performance_log is None + ): perf_log_file = Path(trace_label + ".log") - elif state.settings.expression_profile is False or compute_settings.performance_log is False: + elif ( + state.settings.expression_profile is False + or compute_settings.performance_log is False + ): perf_log_file = None elif compute_settings.performance_log is True: perf_log_file = Path(trace_label + ".log") @@ -639,16 +686,22 @@ def eval_utilities( warnings.simplefilter("always") with performance_timer.time_expression(expr): if expr.startswith("@"): - expression_value = eval(expr[1:], globals_dict, locals_dict) + expression_value = eval( + expr[1:], globals_dict, locals_dict + ) else: expression_value = fast_eval(choosers, expr) if len(w) > 0: for wrn in w: - logger.warning(f"{trace_label} - {type(wrn).__name__} ({wrn.message}) evaluating: {str(expr)}") + logger.warning( + f"{trace_label} - {type(wrn).__name__} ({wrn.message}) evaluating: {str(expr)}" + ) except Exception as err: - logger.exception(f"{trace_label} - {type(err).__name__} ({str(err)}) evaluating: {str(expr)}") + logger.exception( + f"{trace_label} - {type(err).__name__} ({str(err)}) evaluating: {str(expr)}" + ) raise err if log_alt_losers: @@ -679,7 +732,9 @@ def eval_utilities( estimator.write_expression_values(df) # - compute_utilities - utilities = np.dot(expression_values.transpose(), spec.astype(np.float64).values) + utilities = np.dot( + expression_values.transpose(), spec.astype(np.float64).values + ) timelogger.mark("simple flow", True, logger=logger, suffix=trace_label) else: @@ -731,7 +786,9 @@ def eval_utilities( if trace_column_names is not None: if isinstance(trace_column_names, str): trace_column_names = [trace_column_names] - expression_values_df.columns = pd.MultiIndex.from_frame(choosers.loc[trace_targets, trace_column_names]) + expression_values_df.columns = pd.MultiIndex.from_frame( + choosers.loc[trace_targets, trace_column_names] + ) else: expression_values_df = None @@ -774,7 +831,9 @@ def eval_utilities( ) except AssertionError as err: print(err) - misses = np.where(~np.isclose(sh_util, utilities.values, rtol=1e-2, atol=1e-6)) + misses = np.where( + ~np.isclose(sh_util, utilities.values, rtol=1e-2, atol=1e-6) + ) _sh_util_miss1 = sh_util[tuple(m[0] for m in misses)] _u_miss1 = utilities.values[tuple(m[0] for m in misses)] _sh_util_miss1 - _u_miss1 @@ -814,12 +873,16 @@ def eval_utilities( chunk_sizer.log_df(trace_label, "utilities", None) end_time = time.time() - logger.debug(f"simulate.eval_utils runtime: {timedelta(seconds=end_time - start_time)} {trace_label}") + logger.debug( + f"simulate.eval_utils runtime: {timedelta(seconds=end_time - start_time)} {trace_label}" + ) timelogger.summary(logger, "simulate.eval_utils timing") return utilities -def eval_variables(state: workflow.State, exprs, df, locals_d=None, trace_label: str | None = None): +def eval_variables( + state: workflow.State, exprs, df, locals_d=None, trace_label: str | None = None +): """ Evaluate a set of variable expressions from a spec in the context of a given data table. @@ -902,7 +965,9 @@ def to_array(x): values[expr] = expr_values except Exception as err: - logger.exception(f"Variable evaluation failed {type(err).__name__} ({str(err)}) evaluating: {str(expr)}") + logger.exception( + f"Variable evaluation failed {type(err).__name__} ({str(err)}) evaluating: {str(expr)}" + ) raise err values = util.df_from_dict(values, index=df.index) @@ -960,7 +1025,13 @@ def set_skim_wrapper_targets(df, skims, allow_partial_success: bool = True): such failure will be raised immediately, preventing partial success. """ - skims = skims if isinstance(skims, list) else skims.values() if isinstance(skims, dict) else [skims] + skims = ( + skims + if isinstance(skims, list) + else skims.values() + if isinstance(skims, dict) + else [skims] + ) problems = [] # assume any object in skims can be treated as a skim @@ -1058,11 +1129,15 @@ def compute_nested_utilities(raw_utilities, nest_spec): for nest in logit.each_nest(nest_spec, post_order=True): name = nest.name if nest.is_leaf: - nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients + nested_utilities[name] = ( + raw_utilities[name].astype(float) / nest.product_of_coefficients + ) else: # the alternative nested_utilities will already have been computed due to post_order with np.errstate(divide="ignore"): - nested_utilities[name] = nest.coefficient * np.log(np.exp(nested_utilities[nest.alternatives]).sum(axis=1)) + nested_utilities[name] = nest.coefficient * np.log( + np.exp(nested_utilities[nest.alternatives]).sum(axis=1) + ) return nested_utilities @@ -1096,7 +1171,9 @@ def compute_nested_exp_utilities(raw_utilities, nest_spec): if nest.is_leaf: # leaf_utility = raw_utility / nest.product_of_coefficients - nested_utilities[name] = raw_utilities[name].astype(float) / nest.product_of_coefficients + nested_utilities[name] = ( + raw_utilities[name].astype(float) / nest.product_of_coefficients + ) else: # nest node @@ -1105,7 +1182,9 @@ def compute_nested_exp_utilities(raw_utilities, nest_spec): # if all nest alternative utilities are zero # but the resulting inf will become 0 when exp is applied below with np.errstate(divide="ignore"): - nested_utilities[name] = nest.coefficient * np.log(nested_utilities[nest.alternatives].sum(axis=1)) + nested_utilities[name] = nest.coefficient * np.log( + nested_utilities[nest.alternatives].sum(axis=1) + ) # exponentiate the utility nested_utilities[name] = np.exp(nested_utilities[name]) @@ -1113,7 +1192,9 @@ def compute_nested_exp_utilities(raw_utilities, nest_spec): return nested_utilities -def compute_nested_probabilities(state: workflow.State, nested_exp_utilities, nest_spec, trace_label): +def compute_nested_probabilities( + state: workflow.State, nested_exp_utilities, nest_spec, trace_label +): """ compute nested probabilities for nest leafs and nodes probability for nest alternatives is simply the alternatives's local (to nest) probability @@ -1277,18 +1358,26 @@ def eval_mnl( ) if state.settings.use_explicit_error_terms: - utilities = logit.validate_utils(state, utilities, trace_label=trace_label, trace_choosers=choosers) + utilities = logit.validate_utils( + state, utilities, trace_label=trace_label, trace_choosers=choosers + ) if custom_chooser: - choices, rands = custom_chooser(state, utilities, choosers, spec, trace_label) + choices, rands = custom_chooser( + state, utilities, choosers, spec, trace_label + ) else: - choices, rands = logit.make_choices_utility_based(state, utilities, trace_label=trace_label) + choices, rands = logit.make_choices_utility_based( + state, utilities, trace_label=trace_label + ) del utilities chunk_sizer.log_df(trace_label, "utilities", None) else: - probs = logit.utils_to_probs(state, utilities, trace_label=trace_label, trace_choosers=choosers) + probs = logit.utils_to_probs( + state, utilities, trace_label=trace_label, trace_choosers=choosers + ) chunk_sizer.log_df(trace_label, "probs", probs) del utilities @@ -1311,7 +1400,9 @@ def eval_mnl( chunk_sizer.log_df(trace_label, "probs", None) if have_trace_targets: - state.tracing.trace_df(choices, "%s.choices" % trace_label, columns=[None, trace_choice_name]) + state.tracing.trace_df( + choices, "%s.choices" % trace_label, columns=[None, trace_choice_name] + ) state.tracing.trace_df(rands, "%s.rands" % trace_label, columns=[None, "rand"]) return choices @@ -1408,7 +1499,9 @@ def eval_nl( ) if state.settings.use_explicit_error_terms: - raw_utilities = logit.validate_utils(state, raw_utilities, allow_zero_probs=True, trace_label=trace_label) + raw_utilities = logit.validate_utils( + state, raw_utilities, allow_zero_probs=True, trace_label=trace_label + ) # utilities of leaves and nests nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) @@ -1464,7 +1557,9 @@ def eval_nl( ) # probabilities of alternatives relative to siblings sharing the same nest - nested_probabilities = compute_nested_probabilities(state, nested_exp_utilities, nest_spec, trace_label=trace_label) + nested_probabilities = compute_nested_probabilities( + state, nested_exp_utilities, nest_spec, trace_label=trace_label + ) chunk_sizer.log_df(trace_label, "nested_probabilities", nested_probabilities) if want_logsums: @@ -1483,7 +1578,9 @@ def eval_nl( ) # global (flattened) leaf probabilities based on relative nest coefficients (in spec order) - base_probabilities = compute_base_probabilities(nested_probabilities, nest_spec, spec) + base_probabilities = compute_base_probabilities( + nested_probabilities, nest_spec, spec + ) chunk_sizer.log_df(trace_label, "base_probabilities", base_probabilities) del nested_probabilities @@ -1520,16 +1617,22 @@ def eval_nl( trace_label, ) else: - choices, rands = logit.make_choices(state, base_probabilities, trace_label=trace_label) + choices, rands = logit.make_choices( + state, base_probabilities, trace_label=trace_label + ) del base_probabilities chunk_sizer.log_df(trace_label, "base_probabilities", None) if have_trace_targets: - state.tracing.trace_df(choices, "%s.choices" % trace_label, columns=[None, trace_choice_name]) + state.tracing.trace_df( + choices, "%s.choices" % trace_label, columns=[None, trace_choice_name] + ) state.tracing.trace_df(rands, f"{trace_label}.rands", columns=[None, "rand"]) if want_logsums: - state.tracing.trace_df(logsums, f"{trace_label}.logsums", columns=[None, "logsum"]) + state.tracing.trace_df( + logsums, f"{trace_label}.logsums", columns=[None, "logsum"] + ) if want_logsums: choices = choices.to_frame("choice") @@ -1615,7 +1718,11 @@ def _simple_simulate( # if tracing is not enabled, drop unused columns # if not estimation mode, drop unused columns - if (not have_trace_targets) and (estimator is None) and (compute_settings.drop_unused_columns): + if ( + (not have_trace_targets) + and (estimator is None) + and (compute_settings.drop_unused_columns) + ): # drop unused variables in chooser table choosers = util.drop_unused_columns( choosers, @@ -1665,9 +1772,23 @@ def _simple_simulate( def tvpb_skims(skims): def list_of_skims(skims): - return skims if isinstance(skims, list) else (skims.values() if isinstance(skims, dict) else [skims] if skims is not None else []) + return ( + skims + if isinstance(skims, list) + else ( + skims.values() + if isinstance(skims, dict) + else [skims] + if skims is not None + else [] + ) + ) - return [skim for skim in list_of_skims(skims) if isinstance(skim, pathbuilder.TransitVirtualPathLogsumWrapper)] + return [ + skim + for skim in list_of_skims(skims) + if isinstance(skim, pathbuilder.TransitVirtualPathLogsumWrapper) + ] def simple_simulate( @@ -1844,7 +1965,9 @@ def eval_mnl_logsums( # trace utilities if have_trace_targets: - state.tracing.trace_df(logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"]) + state.tracing.trace_df( + logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"] + ) return logsums @@ -1871,13 +1994,17 @@ def _preprocess_tvpb_logsums_on_choosers(choosers, spec, locals_d): spec_sh = spec.copy() def _replace_in_level(multiindex, level_name, *args, **kwargs): - y = multiindex.levels[multiindex.names.index(level_name)].str.replace(*args, **kwargs) + y = multiindex.levels[multiindex.names.index(level_name)].str.replace( + *args, **kwargs + ) return multiindex.set_levels(y, level=level_name) # Preprocess TVPB logsums outside sharrow if "tvpb_logsum_odt" in locals_d: tvpb = locals_d["tvpb_logsum_odt"] - path_types = tvpb.tvpb.network_los.setting(f"TVPB_SETTINGS.{tvpb.recipe}.path_types").keys() + path_types = tvpb.tvpb.network_los.setting( + f"TVPB_SETTINGS.{tvpb.recipe}.path_types" + ).keys() assignments = {} for path_type in ["WTW", "DTW"]: if path_type not in path_types: @@ -1899,7 +2026,9 @@ def _replace_in_level(multiindex, level_name, *args, **kwargs): if "tvpb_logsum_dot" in locals_d: tvpb = locals_d["tvpb_logsum_dot"] - path_types = tvpb.tvpb.network_los.setting(f"TVPB_SETTINGS.{tvpb.recipe}.path_types").keys() + path_types = tvpb.tvpb.network_los.setting( + f"TVPB_SETTINGS.{tvpb.recipe}.path_types" + ).keys() assignments = {} for path_type in ["WTW", "WTD"]: if path_type not in path_types: @@ -1993,7 +2122,9 @@ def eval_nl_logsums( "%s.nested_exp_utilities" % trace_label, column_labels=["alternative", "utility"], ) - state.tracing.trace_df(logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"]) + state.tracing.trace_df( + logsums, "%s.logsums" % trace_label, column_labels=["alternative", "logsum"] + ) del nested_exp_utilities # done with nested_exp_utilities chunk_sizer.log_df(trace_label, "nested_exp_utilities", None) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 0f2dc9abf2..1ad8c52a90 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -26,7 +26,9 @@ def data_dir(): ( "fish.csv", "fish_choosers.csv", - pd.DataFrame([[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"]), + pd.DataFrame( + [[-0.02047652], [0.95309824]], index=["price", "catch"], columns=["Alt"] + ), pd.DataFrame( [ [0.2849598, 0.2742482, 0.1605457, 0.2802463], @@ -257,10 +259,14 @@ def test_utils_to_probs_raises(): # def test_make_choices_only_one(): state = workflow.State().default_settings() - probs = pd.DataFrame([[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"]) + probs = pd.DataFrame( + [[1, 0, 0], [0, 1, 0]], columns=["a", "b", "c"], index=["x", "y"] + ) choices, rands = logit.make_choices(state, probs) - pdt.assert_series_equal(choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False) + pdt.assert_series_equal( + choices, pd.Series([0, 1], index=["x", "y"]), check_dtype=False + ) def test_make_choices_real_probs(utilities): @@ -309,7 +315,9 @@ def test_different_order_make_choices(): choices_shuffled, rands_shuffled = logit.make_choices(state, probs_shuffled) # sorting both to ensure comparison is on the same index order - pdt.assert_series_equal(choices.sort_index(), choices_shuffled.sort_index(), check_dtype=False) + pdt.assert_series_equal( + choices.sort_index(), choices_shuffled.sort_index(), check_dtype=False + ) def test_make_choices_matches_random_draws(): @@ -427,7 +435,9 @@ def test_choose_from_tree_selects_leaf(): "motorized": ["car", "bus"], } - choice = logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) + choice = logit.choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ) assert choice == "car" @@ -442,7 +452,9 @@ def test_choose_from_tree_raises_on_missing_leaf(): } with pytest.raises(ValueError, match="no alternative found"): - logit.choose_from_tree(nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name) + logit.choose_from_tree( + nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + ) # @@ -554,7 +566,9 @@ class MCDummyState: def get_rn_generator(): return MCDummyRNG() - probs = logit.utils_to_probs(MCDummyState(), utils, trace_label=None, overflow_protection=True) + probs = logit.utils_to_probs( + MCDummyState(), utils, trace_label=None, overflow_protection=True + ) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) # Explicit-error-term (EET) path — independent RNG @@ -569,14 +583,20 @@ class EETDummyState: def get_rn_generator(): return EETDummyRNG() - choices_eet = logit.make_choices_explicit_error_term_mnl(EETDummyState(), utils, trace_label=None) + choices_eet = logit.make_choices_explicit_error_term_mnl( + EETDummyState(), utils, trace_label=None + ) mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=n_alts) / n_draws eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=n_alts) / n_draws np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol, rtol=r_tol) - np.testing.assert_allclose(mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) - np.testing.assert_allclose(eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol) + np.testing.assert_allclose( + mc_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol + ) + np.testing.assert_allclose( + eet_fracs, probs.iloc[0].to_numpy(), atol=a_tol, rtol=r_tol + ) def test_make_choices_vs_eet_nl_same_distribution(): @@ -621,9 +641,15 @@ def default_settings(self): return self # Compute probabilities for NL using simulation logic - nested_exp_utilities = simulate.compute_nested_exp_utilities(utils_df[["car", "bus", "walk"]], nest_spec) - nested_probabilities = simulate.compute_nested_probabilities(MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None) - probs = simulate.compute_base_probabilities(nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]]) + nested_exp_utilities = simulate.compute_nested_exp_utilities( + utils_df[["car", "bus", "walk"]], nest_spec + ) + nested_probabilities = simulate.compute_nested_probabilities( + MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None + ) + probs = simulate.compute_base_probabilities( + nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]] + ) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) # 2. EET-based Nested Logit choices @@ -650,7 +676,9 @@ def tracing(self): # For EET NL, we provide the utilities for all nodes. # compute_nested_utilities handles the division by nesting coefficients for leaves # and the logsum * coefficient for internal nodes. - nested_utilities = simulate.compute_nested_utilities(utils_df[["car", "bus", "walk"]], nest_spec) + nested_utilities = simulate.compute_nested_utilities( + utils_df[["car", "bus", "walk"]], nest_spec + ) choices_eet = logit.make_choices_explicit_error_term_nl( EETDummyState(), @@ -679,7 +707,9 @@ def test_interaction_dataset_no_sample(interaction_choosers, interaction_alts): index=[1, 2, 3, 4] * 4, ) - interacted = logit.interaction_dataset(workflow.State().default_settings(), interaction_choosers, interaction_alts) + interacted = logit.interaction_dataset( + workflow.State().default_settings(), interaction_choosers, interaction_alts + ) interacted, expected = interacted.align(expected, axis=1) pdt.assert_frame_equal(interacted, expected) diff --git a/activitysim/core/test/test_simulate.py b/activitysim/core/test/test_simulate.py index 03b3ce8001..21e0f90e73 100644 --- a/activitysim/core/test/test_simulate.py +++ b/activitysim/core/test/test_simulate.py @@ -26,7 +26,9 @@ def spec_name(data_dir): @pytest.fixture def state(data_dir) -> workflow.State: state = workflow.State() - state.initialize_filesystem(working_dir=os.path.dirname(__file__), data_dir=(data_dir,)).default_settings() + state.initialize_filesystem( + working_dir=os.path.dirname(__file__), data_dir=(data_dir,) + ).default_settings() return state @@ -65,7 +67,9 @@ def test_read_model_spec(state, spec_name): def test_eval_variables(state, spec, data): result = simulate.eval_variables(state, spec.index, data) - expected = pd.DataFrame([[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index) + expected = pd.DataFrame( + [[1, 0, 4, 1], [0, 1, 4, 1], [0, 1, 5, 1]], index=data.index, columns=spec.index + ) expected[expected.columns[0]] = expected[expected.columns[0]].astype(np.int8) expected[expected.columns[1]] = expected[expected.columns[1]].astype(np.int8) @@ -251,12 +255,16 @@ def test_compute_nested_utilities(nest_spec): constructed_nested_utilities = pd.DataFrame(index=raw_utilities.index) constructed_nested_utilities[leaf_utilities.columns] = leaf_utilities - constructed_nested_utilities["alt0"] = alt0_nest_coefficient * np.log(np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1)) + constructed_nested_utilities["alt0"] = alt0_nest_coefficient * np.log( + np.exp(leaf_utilities[["alt0.0", "alt0.1"]]).sum(axis=1) + ) constructed_nested_utilities["root"] = nest_spec["coefficient"] * np.log( np.exp(constructed_nested_utilities[["alt1", "alt0"]]).sum(axis=1) ) - assert np.allclose(nested_utilities, constructed_nested_utilities[nested_utilities.columns]), "Mismatch in nested utilities" + assert np.allclose( + nested_utilities, constructed_nested_utilities[nested_utilities.columns] + ), "Mismatch in nested utilities" def test_eval_nl_logsums_eet_vs_non_eet(state, nest_spec): @@ -321,4 +329,6 @@ def test_eval_nl_logsums_eet_vs_non_eet(state, nest_spec): assert "logsum" in result_non_eet.columns, "non-EET result missing logsum column" # Logsums are deterministic — they must be identical across paths - assert np.allclose(result_eet["logsum"].values, result_non_eet["logsum"].values, rtol=1e-10) + assert np.allclose( + result_eet["logsum"].values, result_non_eet["logsum"].values, rtol=1e-10 + ) From a73f6ea11f94085d7ef275d21f6a775bec7d526f Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 31 Mar 2026 16:28:31 +1000 Subject: [PATCH 169/298] Fix test --- activitysim/core/test/test_logit.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 1ad8c52a90..598129e587 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -108,11 +108,12 @@ def test_validate_utils_raises_when_all_unavailable(): def test_validate_utils_allows_zero_probs(): state = workflow.State().default_settings() - utils = pd.DataFrame([[logit.UTIL_MIN - 1.0, logit.UTIL_MIN - 2.0]]) + utils = pd.DataFrame([[0.5, logit.UTIL_MIN - 1.0]]) validated = logit.validate_utils(state, utils, allow_zero_probs=True) - assert (validated.iloc[0] == logit.UTIL_UNAVAILABLE).all() + assert validated.iloc[0, 0] == 0.5 + assert validated.iloc[0, 1] == logit.UTIL_UNAVAILABLE def test_validate_utils_does_not_mutate_input(): From b455e2afe44c6607a078b937fd65c4771d24e973 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Tue, 31 Mar 2026 17:02:02 +1000 Subject: [PATCH 170/298] Roll back validate_utils --- activitysim/core/logit.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index f5dedd510c..18168591c2 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -179,19 +179,7 @@ def validate_utils( arr_sum = utils_arr.sum(axis=1) - if allow_zero_probs: - # only worry if all alts for a chooser are unavailable, which would lead to all zero probabilities and thus no choice - all_unavailable = (utils_arr == UTIL_UNAVAILABLE).all(axis=1) - if all_unavailable.any(): - report_bad_choices( - state, - all_unavailable, - utils_arr, - trace_label=tracing.extend_trace_label(trace_label, "zero_prob_utils"), - msg="all alternatives have zero probability", - trace_choosers=trace_choosers, - ) - else: + if not allow_zero_probs: zero_probs = arr_sum <= utils_arr.shape[1] * UTIL_UNAVAILABLE if zero_probs.any(): report_bad_choices( From a490999591cc2ba94e5c251592745b62d39624c8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 31 Mar 2026 20:34:51 +1000 Subject: [PATCH 171/298] eet doco start --- docs/users-guide/ways_to_run.rst | 48 ++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 3e5c159b12..ff153b63ab 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -289,12 +289,42 @@ Refer to :ref:`trace` for more details on configuring tracing and the various ou Explicit Error Terms ____________________ -By default, ActivitySim makes choices using analytical probabilities derived from systematic utilities. -Alternatively, users can enable Explicit Error Terms (EET) by setting ``use_explicit_error_terms: True`` -in the global or model-specific settings. - -In EET mode, unobserved utility components are drawn directly from the Gumbel distribution (EV1) and added -to the systematic utilities. The alternative with the highest total utility is then selected. This approach -is particularly useful for reducing simulation noise and improving consistency when comparing scenarios -where only a subset of alternatives has changed, as it maintains the same unobserved error draws for -alternatives that remain constant. +ActivitySim makes heavy use of micro-simulation. Most model components are discrete choice models with an inherent +random component, and each for each choice situation a single outcome is generated. +With the default Monte Carlo draw method, ActivitySim first calculates analytical probabilities from the +systematic utilities of a multinomial or nested logit model and then makes one draw from the +cumulative distribution for each chooser. Explicit Error Terms (EET) replaces that final draw with a direct +random-utility simulation by drawing an independent EV1 (Gumbel) error term for each available +chooser-alternative pair, adding it to the systematic utility, and selecting the alternative with the highest +total utility. Both methods are valid ways to simulate from a discrete choice model, but EET is more +consistent with the underlying random utility model and is less affected by Monte Carlo noise when comparing +scenarios. + +To enable EET for a model run, set the global switch in ``settings.yaml``: + +.. code-block:: yaml + + use_explicit_error_terms: True + +When comparing runs, enable or disable this setting consistently across the runs you want to compare. + +Using EET changes the simulation method, not the underlying utility expressions or availability rules. +Aggregate behavior should remain comparable to the default method, but individual simulated choices will +not usually match record-by-record. EET is also slower than the default probability-based draw because it +requires additional random draws for each chooser and alternative and the core simulation algorithms have not +yet been optimized for EET performance. Most of the slowdown is due to location choice models, where the number +of alternatives is large and the current importance-sampling method requires many repeated choices for all +alternatives. There are several ways to reduce the additional runtime, several of which are currently being +investigated. It is also possible to turn off EET for the sampling part of location choice models by adding the +following line to the location choice model settings: + +.. code-block:: yaml + + compute_settings: + use_explicit_error_terms: + sample: false + +This applies to all models where location choice sampling is applied, e.g., school and workplace location choice and +disaggregate accessibilities. + +For more details see :doc:`/dev-guide/explicit-error-terms`. From 661c6b386699ebf0aa619346b844da1c15662b06 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 31 Mar 2026 21:53:39 +1000 Subject: [PATCH 172/298] doco part two --- docs/core.rst | 16 ++-- docs/dev-guide/explicit-error-terms.md | 126 +++++++++++++++++++++++++ docs/dev-guide/index.rst | 1 + 3 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 docs/dev-guide/explicit-error-terms.md diff --git a/docs/core.rst b/docs/core.rst index e5a2c2c293..6ac122b1a6 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -326,12 +326,16 @@ To specify and solve an NL model: Explicit Error Terms ^^^^^^^^^^^^^^^^^^^^ -By default, ActivitySim uses analytical probabilities to make choices. Alternatively, users can enable -``use_explicit_error_terms: True`` in the model settings. In this mode, unobserved utility components -are drawn directly from the Gumbel distribution (EV1) and added to the systematic utilities. The -alternative with the highest total utility is then selected. This approach can be useful for -reducing simulation noise and improving consistency, particularly when comparing scenarios where -only a subset of alternatives has changed. +By default, ActivitySim makes choices by calculating analytical probabilities and then drawing once from +the cumulative distribution. With Explicit Error Terms (EET), enabled by setting +``use_explicit_error_terms: True`` in ``settings.yaml``, ActivitySim instead draws an EV1 (Gumbel) error +term for each available alternative, adds this to the observed utility, and chooses the maximum total utility. + +EET changes the final simulation step, not the utility expressions, availability logic, or nesting +structure. In practice, it can reduce Monte Carlo noise in scenario comparisons. + +For configuration guidance see :ref:`explicit_error_terms_ways_to_run`. For detailed implementation notes +see :doc:`/dev-guide/explicit-error-terms`. API ^^^ diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md new file mode 100644 index 0000000000..0fe813d2ef --- /dev/null +++ b/docs/dev-guide/explicit-error-terms.md @@ -0,0 +1,126 @@ +(explicit-error-terms-dev)= +# Explicit Error Terms for Developers + +Explicit Error Terms (EET) is an alternative way to simulate choices from ActivitySim's +logit models. It keeps the same systematic utilities and the same random-utility +interpretation as the standard method, but changes how the final simulated choice is +drawn. + +For user-facing guidance on when to use EET, see {ref}`explicit_error_terms_ways_to_run`. + +## Enabling EET + +Enable EET globally in `settings.yaml`: + +```yaml +use_explicit_error_terms: True +``` + +The top-level switch is defined in +`activitysim.core.configuration.top.SimulationSettings.use_explicit_error_terms`. +Choice simulation code reads that setting through the model compute settings and routes +supported logit simulations through the EET path. + +## Default Draw Versus EET + +Under the default ActivitySim simulation path, choice drawing works like this: + +1. Compute systematic utilities. +2. Convert those utilities into analytical probabilities. +3. Draw one uniform random number per chooser. +4. Select the alternative whose cumulative probability interval contains that draw. + +With EET enabled, the final draw step changes: + +1. Compute systematic utilities. +2. Draw one iid EV1 error term for each chooser-alternative pair. +3. Add that error term to the systematic utility. +4. Choose the alternative with the highest total utility. + +For multinomial logit, ActivitySim adds Gumbel draws to the utility table and takes the +row-wise maximum. For nested logit, ActivitySim applies the same idea while walking the +nest tree, preserving the configured nesting structure. For details, see +[this ATRF paper](https://australasiantransportresearchforum.org.au/frozen-randomness-at-the-individual-utility-level/). + +The model being simulated does not change. EET changes how the random utility model is +sampled, not the underlying utility specification. + +## Practical Effects + +### Comparisons and Simulation Noise + +For EET to reduce simulation noise, it is important that alternatives of a choice situation +keep the same unobserved error term in different scenario runs. This is intimately tied +to how random numbers are generated; see {ref}`random_in_detail` for the underlying +random-number stream design and the `activitysim.core.random` API. +Because unchanged alternatives can keep the same unobserved draws, changes to choices in +can only happen when the observed utility of an alternative increases. This is not the case +for the Monte Carlo simulation method, where the draws are based on probabilities, which +necessarily change for all alternatives if any observed utility changes. + +This also means that one should use the same setting in all runs. Comparing a baseline run +with EET to a scenario run without EET mixes two simulation methods and makes differences +harder to interpret. + +Aggregate choice patterns should remain statistically the same as for the default +probability-based method. The project test suite includes parity tests for MNL, NL, +and interaction-based simulations. + +### Numerical and Debugging Behavior + +EET changes the final simulation step, not the utility calculation itself. Utility +expressions, availability logic, nesting structure, and utility validation still matter in +the same way as in the default method. + +In practice, EET can make some comparisons easier to interpret because the selected +alternative is the one with the highest total utility after adding the explicit error term, +rather than the one reached by a cumulative-probability threshold. That can reduce +sensitivity to small differences in the final CDF draw when comparing nearby scenarios. +It does not eliminate the need to inspect invalid or unavailable alternatives, and it does +not guarantee identical results across different RNG seeds or different model +configurations. + +For shadow-priced location choice, ActivitySim resets RNG offsets between iterations when +EET is enabled so each shadow-pricing iteration uses the same sequence of random numbers. +That keeps the comparison across iterations focused on the shadow price updates instead of +changing random draws between iterations. + +### Runtime + +EET is slower than the default probability-based draw because it generates and processes +one random error term per chooser-alternative pair, rather than one uniform draw per +chooser after probabilities are computed. The exact runtime impact depends on the number +of alternatives, nesting structure, and interaction size. Current runtime increases are on the +order of 100% per demand model run, which is due to the non-optimized way in which location +choice is currently handled. Runtime improvement work is under way, but large improvements can +also be obtained by using Monte Carlo simulation for the sampling part of location choice, see +{ref}`explicit_error_terms_ways_to_run`. + +## Implementation Details and Adding New Models + +The core simulation is implemented in `activitysim.core.logit.make_choices_utility_based`. Most +calls to this function are wrapped in one of the following methods: + +- `activitysim.core.simulate` +- `activitysim.core.interaction_simulate` +- `activitysim.core.interaction_sample` +- `activitysim.core.interaction_sample_simulate` + +These methods have consistent implementations of EET and therefore any model using these will +automatically have EET implemented. Some models call the underlying choice simulation method +`activitysim.core.logit.make_choices` directly. For EET to work in that case, the developer has +to add a corresponding call to `logit.make_choices_utility_based`, see, e.g., +`activitysim.abm.models.utils.cdap.household_activity_choices`. Note models that draw directly +from probability distributions, like `activitysim.abm.models.utils.cdap.extra_hh_member_choices` +do not have a corresponding EET implementation because there are no utilities to work with. + + +### Unavailable choices utility convention + +For EET, only utility differences matter and therefore the choice between two utilities that are +very small, say -10000 and -10001, are identical to a choice between 0 and 1. For MC, utilities +have to be exponentiated and therefore floating point precision dictates the smallest and largest +utility that can be used in practice. ActivitySim historically uses a utility of -999 to make +alternatives practically unavailable. To keep consistent with this behaviour, EET also treats +alternatives with utilities smaller or equal to -999 as unavailable, see +`activitysim.core.logit.validate_utils`. diff --git a/docs/dev-guide/index.rst b/docs/dev-guide/index.rst index da6c649733..82051ff08e 100644 --- a/docs/dev-guide/index.rst +++ b/docs/dev-guide/index.rst @@ -33,6 +33,7 @@ Contents component-configs components/index ../core + explicit-error-terms ../benchmarking build-docs changes From 40b0b0611019ce63dc13551955092e56593d229d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 31 Mar 2026 21:58:15 +1000 Subject: [PATCH 173/298] add scale comment --- docs/dev-guide/explicit-error-terms.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 0fe813d2ef..02bd03d5bf 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -124,3 +124,7 @@ utility that can be used in practice. ActivitySim historically uses a utility of alternatives practically unavailable. To keep consistent with this behaviour, EET also treats alternatives with utilities smaller or equal to -999 as unavailable, see `activitysim.core.logit.validate_utils`. + +### Scale of the distribution +Error terms are drawn from standard Gumbel distributions, i.e., the scale of the error term is +fixed to one. From 21daa5c0b50c2aefb44de2957ad89cfa30704e8c Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Thu, 2 Apr 2026 09:44:16 +1000 Subject: [PATCH 174/298] Revert linting changes --- .../abm/models/joint_tour_participation.py | 2 +- activitysim/abm/models/location_choice.py | 2 +- .../test_joint_tour_participation.py | 2 -- .../test_misc/test_trip_departure_choice.py | 5 +--- activitysim/core/logit.py | 11 ++++---- activitysim/core/random.py | 2 +- activitysim/core/simulate.py | 25 ++++++++++++------- .../core/test/test_interaction_sample.py | 1 - .../test/test_interaction_sample_simulate.py | 1 - .../core/test/test_interaction_simulate.py | 1 - 10 files changed, 25 insertions(+), 27 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 0fb562c189..e6dbee8b64 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -20,8 +20,8 @@ ) from activitysim.core.configuration.base import ComputeSettings, PreprocessorSettings from activitysim.core.configuration.logit import LogitComponentSettings -from activitysim.core.exceptions import InvalidTravelError from activitysim.core.util import assign_in_place, reindex +from activitysim.core.exceptions import InvalidTravelError logger = logging.getLogger(__name__) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 5ea669aad0..eccc51fb79 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -15,10 +15,10 @@ TourLocationComponentSettings, TourModeComponentSettings, ) -from activitysim.core.exceptions import DuplicateWorkflowTableError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.util import reindex +from activitysim.core.exceptions import DuplicateWorkflowTableError """ The school/workplace location model predicts the zones in which various people will diff --git a/activitysim/abm/test/test_misc/test_joint_tour_participation.py b/activitysim/abm/test/test_misc/test_joint_tour_participation.py index 18905ef107..5aa15c6e8e 100644 --- a/activitysim/abm/test/test_misc/test_joint_tour_participation.py +++ b/activitysim/abm/test/test_misc/test_joint_tour_participation.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import numpy as np import pandas as pd import pandas.testing as pdt diff --git a/activitysim/abm/test/test_misc/test_trip_departure_choice.py b/activitysim/abm/test/test_misc/test_trip_departure_choice.py index 85e0732f94..d6645ce94f 100644 --- a/activitysim/abm/test/test_misc/test_trip_departure_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_departure_choice.py @@ -1,10 +1,7 @@ -from __future__ import annotations - -import os - import numpy as np import pandas as pd import pytest +import os import activitysim.abm.models.trip_departure_choice as tdc from activitysim.abm.models.util.trip import get_time_windows diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 18168591c2..5cb7774f47 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -116,7 +116,7 @@ def utils_to_logsums(utils, exponentiated=False, allow_zero_probs=False): # fixme - conversion to float not needed in either case? # utils_arr = utils.values.astype('float') - utils_arr = utils.to_numpy(copy=True) + utils_arr = utils.values if not exponentiated: utils_arr = np.exp(utils_arr) @@ -173,7 +173,7 @@ def validate_utils( """ trace_label = tracing.extend_trace_label(trace_label, "validate_utils") - utils_arr = utils.to_numpy(copy=True) + utils_arr = utils.values np.putmask(utils_arr, utils_arr <= UTIL_MIN, UTIL_UNAVAILABLE) @@ -254,7 +254,7 @@ def utils_to_probs( # fixme - conversion to float not needed in either case? # utils_arr = utils.values.astype('float') - utils_arr = utils.to_numpy(copy=True) + utils_arr = utils.values if allow_zero_probs: if overflow_protection: @@ -491,7 +491,6 @@ def make_choices_explicit_error_term_mnl( trace_choosers=trace_choosers, ) choices = pd.Series(choices, index=utilities_incl_unobs.index) - return choices @@ -636,11 +635,11 @@ def interaction_dataset( """ if not choosers.index.is_unique: raise TableIndexError( - "ERROR: choosers index is not unique, sample will not work correctly" + "ERROR: choosers index is not unique, " "sample will not work correctly" ) if not alternatives.index.is_unique: raise TableIndexError( - "ERROR: alternatives index is not unique, sample will not work correctly" + "ERROR: alternatives index is not unique, " "sample will not work correctly" ) numchoosers = len(choosers) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index b47b2d22df..5b17097c4f 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -9,8 +9,8 @@ import numpy as np import pandas as pd -from activitysim.core.exceptions import DuplicateLoadableObjectError, TableIndexError from activitysim.core.util import reindex +from activitysim.core.exceptions import DuplicateLoadableObjectError, TableIndexError from .tracing import print_elapsed_time diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 1079d9b6f2..31c6eeec3f 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -32,7 +32,8 @@ LogitNestSpec, TemplatedLogitComponentSettings, ) -from activitysim.core.exceptions import ModelConfigurationError +if TYPE_CHECKING: + from activitysim.core.estimation import Estimator from activitysim.core.fast_eval import fast_eval from activitysim.core.simulate_consts import ( ALT_LOSER_UTIL, @@ -40,9 +41,9 @@ SPEC_EXPRESSION_NAME, SPEC_LABEL_NAME, ) +from activitysim.core.exceptions import ModelConfigurationError + -if TYPE_CHECKING: - from activitysim.core.estimation import Estimator logger = logging.getLogger(__name__) @@ -84,12 +85,14 @@ def read_model_alts(state: workflow.State, file_name, set_index=None): if "Alt" in df.columns: # Handle deprecated ALTS index warnings.warn( - "Support for 'Alt' column name in alternatives files will be removed. Use 'alt' (lowercase) instead.", + "Support for 'Alt' column name in alternatives files will be removed." + " Use 'alt' (lowercase) instead.", DeprecationWarning, ) # warning above does not actually output to logger, so also log it logger.warning( - "Support for 'Alt' column name in alternatives files will be removed. Use 'alt' (lowercase) instead." + "Support for 'Alt' column name in alternatives files will be removed." + " Use 'alt' (lowercase) instead." ) df.rename(columns={"Alt": "alt"}, inplace=True) @@ -200,7 +203,8 @@ def read_model_coefficients( if coefficients.index.duplicated().any(): logger.warning( - f"duplicate coefficients in {file_path}\n{coefficients[coefficients.index.duplicated(keep=False)]}" + f"duplicate coefficients in {file_path}\n" + f"{coefficients[coefficients.index.duplicated(keep=False)]}" ) raise ModelConfigurationError(f"duplicate coefficients in {file_path}") @@ -266,7 +270,8 @@ def spec_for_segment( assert (spec.astype(float) == spec).all(axis=None) except (ValueError, AssertionError): raise ModelConfigurationError( - f"No coefficient file specified for {spec_file_name} but not all spec column values are numeric" + f"No coefficient file specified for {spec_file_name} " + f"but not all spec column values are numeric" ) from None return spec @@ -440,7 +445,8 @@ def get_segment_coefficients( if coefficients_col.isnull().any(): # show them the offending lines from interaction_coefficients_file logger.warning( - f"bad coefficients in COEFFICIENTS {model_settings['COEFFICIENTS']}\n{coefficients_col[coefficients_col.isnull()]}" + f"bad coefficients in COEFFICIENTS {model_settings['COEFFICIENTS']}\n" + f"{coefficients_col[coefficients_col.isnull()]}" ) assert not coefficients_col.isnull().any() @@ -839,7 +845,8 @@ def eval_utilities( _sh_util_miss1 - _u_miss1 if len(misses[0]) > sh_util.size * 0.01: print( - f"big problem: {len(misses[0])} missed close values out of {sh_util.size} ({100 * len(misses[0]) / sh_util.size:.2f}%)" + f"big problem: {len(misses[0])} missed close values " + f"out of {sh_util.size} ({100*len(misses[0]) / sh_util.size:.2f}%)" ) print(f"{sh_util.shape=}") print(misses) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index b4bc3c77f2..623b1622fb 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -1,6 +1,5 @@ # ActivitySim # See full license in LICENSE.txt. -from __future__ import annotations import numpy as np import pandas as pd diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 202ca95e3e..1be7954172 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -1,6 +1,5 @@ # ActivitySim # See full license in LICENSE.txt. -from __future__ import annotations import numpy as np import pandas as pd diff --git a/activitysim/core/test/test_interaction_simulate.py b/activitysim/core/test/test_interaction_simulate.py index db91e5d6a8..af9442e228 100644 --- a/activitysim/core/test/test_interaction_simulate.py +++ b/activitysim/core/test/test_interaction_simulate.py @@ -1,6 +1,5 @@ # ActivitySim # See full license in LICENSE.txt. -from __future__ import annotations import numpy as np import pandas as pd From 0da80bf860294c81c80a32e24abd132c031b853f Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Thu, 2 Apr 2026 09:47:52 +1000 Subject: [PATCH 175/298] . --- activitysim/core/simulate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 31c6eeec3f..6268c5174c 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -32,6 +32,7 @@ LogitNestSpec, TemplatedLogitComponentSettings, ) + if TYPE_CHECKING: from activitysim.core.estimation import Estimator from activitysim.core.fast_eval import fast_eval @@ -43,8 +44,6 @@ ) from activitysim.core.exceptions import ModelConfigurationError - - logger = logging.getLogger(__name__) CustomChooser_T = Callable[ From 1d139e08cfbd759a3d6bc2face83051e01cf1093 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Thu, 2 Apr 2026 10:10:30 +1000 Subject: [PATCH 176/298] Fix failing tests --- activitysim/core/test/test_logit.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 598129e587..cfb298081c 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -116,16 +116,6 @@ def test_validate_utils_allows_zero_probs(): assert validated.iloc[0, 1] == logit.UTIL_UNAVAILABLE -def test_validate_utils_does_not_mutate_input(): - state = workflow.State().default_settings() - utils = pd.DataFrame([[0.0, logit.UTIL_MIN - 1.0], [1.0, 2.0]]) - original = utils.copy() - - _ = logit.validate_utils(state, utils, allow_zero_probs=False) - - pdt.assert_frame_equal(utils, original) - - # # `utils_to_probs` Tests # @@ -199,16 +189,6 @@ def test_utils_to_probs_raises_on_float32_zero_probs_overflow(): ) -def test_utils_to_probs_does_not_mutate_input(): - state = workflow.State().default_settings() - utils = pd.DataFrame([[1.0, 2.0], [3.0, 4.0]], columns=["a", "b"]) - original = utils.copy() - - _ = logit.utils_to_probs(state, utils, trace_label=None) - - pdt.assert_frame_equal(utils, original) - - def test_utils_to_probs(utilities, test_data): state = workflow.State().default_settings() probs = logit.utils_to_probs(state, utilities, trace_label=None) From 605366fc80931885a051e27bc9c6481f99b415f1 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 16:38:41 +1000 Subject: [PATCH 177/298] doc fix --- docs/users-guide/ways_to_run.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index ff153b63ab..67daf4a92e 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -290,7 +290,7 @@ Explicit Error Terms ____________________ ActivitySim makes heavy use of micro-simulation. Most model components are discrete choice models with an inherent -random component, and each for each choice situation a single outcome is generated. +random component, and for each choice situation a single outcome is generated. With the default Monte Carlo draw method, ActivitySim first calculates analytical probabilities from the systematic utilities of a multinomial or nested logit model and then makes one draw from the cumulative distribution for each chooser. Explicit Error Terms (EET) replaces that final draw with a direct @@ -324,7 +324,7 @@ following line to the location choice model settings: use_explicit_error_terms: sample: false -This applies to all models where location choice sampling is applied, e.g., school and workplace location choice and -disaggregate accessibilities. +This can be applied to all models where location choice sampling is used, which currently includesall location +and destination choice models as well as disaggregate accessibilities). For more details see :doc:`/dev-guide/explicit-error-terms`. From 8c74d2a5be97017a1f29f5595bf7fb4383ca3db1 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 16:55:08 +1000 Subject: [PATCH 178/298] docs --- docs/core.rst | 6 ++--- docs/dev-guide/explicit-error-terms.md | 31 +++++++++++++------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/core.rst b/docs/core.rst index 6ac122b1a6..a7a9ba59d6 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -327,9 +327,9 @@ Explicit Error Terms ^^^^^^^^^^^^^^^^^^^^ By default, ActivitySim makes choices by calculating analytical probabilities and then drawing once from -the cumulative distribution. With Explicit Error Terms (EET), enabled by setting -``use_explicit_error_terms: True`` in ``settings.yaml``, ActivitySim instead draws an EV1 (Gumbel) error -term for each available alternative, adds this to the observed utility, and chooses the maximum total utility. +the cumulative distribution for each chooser. With Explicit Error Terms (EET), enabled by setting +``use_explicit_error_terms: True`` in ``settings.yaml``, ActivitySim instead draws a standard EV1 (Gumbel) error +term for each chooser-alternative pair, adds it to the observed utility, and chooses the maximum total utility. EET changes the final simulation step, not the utility expressions, availability logic, or nesting structure. In practice, it can reduce Monte Carlo noise in scenario comparisons. diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 02bd03d5bf..253693308f 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -1,5 +1,5 @@ (explicit-error-terms-dev)= -# Explicit Error Terms for Developers +# Explicit Error Terms Explicit Error Terms (EET) is an alternative way to simulate choices from ActivitySim's logit models. It keeps the same systematic utilities and the same random-utility @@ -53,18 +53,16 @@ For EET to reduce simulation noise, it is important that alternatives of a choic keep the same unobserved error term in different scenario runs. This is intimately tied to how random numbers are generated; see {ref}`random_in_detail` for the underlying random-number stream design and the `activitysim.core.random` API. -Because unchanged alternatives can keep the same unobserved draws, changes to choices in -can only happen when the observed utility of an alternative increases. This is not the case -for the Monte Carlo simulation method, where the draws are based on probabilities, which -necessarily change for all alternatives if any observed utility changes. +Because unchanged alternatives can keep the same unobserved draws, changes to choices between +scenarios can only happen when the observed utility of an alternative increases. This is not +the case for the Monte Carlo simulation method, where the draws are based on probabilities, +which necessarily change for all alternatives if any observed utility changes. -This also means that one should use the same setting in all runs. Comparing a baseline run -with EET to a scenario run without EET mixes two simulation methods and makes differences -harder to interpret. - -Aggregate choice patterns should remain statistically the same as for the default -probability-based method. The project test suite includes parity tests for MNL, NL, -and interaction-based simulations. +This also means that it is advisable to use the same setting in all runs. Comparing a baseline +run with EET to a scenario run without EET mixes two simulation methods and can make differences +harder to interpret. Aggregate choice patterns should remain statistically the same +as for the default probability-based method. The project test suite includes parity tests for +MNL, NL, and interaction-based simulations. ### Numerical and Debugging Behavior @@ -120,10 +118,11 @@ do not have a corresponding EET implementation because there are no utilities to For EET, only utility differences matter and therefore the choice between two utilities that are very small, say -10000 and -10001, are identical to a choice between 0 and 1. For MC, utilities have to be exponentiated and therefore floating point precision dictates the smallest and largest -utility that can be used in practice. ActivitySim historically uses a utility of -999 to make -alternatives practically unavailable. To keep consistent with this behaviour, EET also treats -alternatives with utilities smaller or equal to -999 as unavailable, see -`activitysim.core.logit.validate_utils`. +utility that can be used in practice. ActivitySim models historically often use a utility of +-999 to make alternatives practically unavailable. That value is below the utility threshold +used in the probability-based path, which is about -691 because ActivitySim clips +exponentiated utilities at 1e-300. To keep behavior consistent, EET treats alternatives with +utilities at or below that threshold as unavailable; see `activitysim.core.logit.validate_utils`. ### Scale of the distribution Error terms are drawn from standard Gumbel distributions, i.e., the scale of the error term is From f542e58f206dbb62ec10c116c8779ec711868dca Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 20:53:58 +1000 Subject: [PATCH 179/298] doco clean up --- docs/users-guide/ways_to_run.rst | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 67daf4a92e..4823bb4c11 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -294,11 +294,10 @@ random component, and for each choice situation a single outcome is generated. With the default Monte Carlo draw method, ActivitySim first calculates analytical probabilities from the systematic utilities of a multinomial or nested logit model and then makes one draw from the cumulative distribution for each chooser. Explicit Error Terms (EET) replaces that final draw with a direct -random-utility simulation by drawing an independent EV1 (Gumbel) error term for each available +random-utility simulation by drawing an independent standard EV1 (Gumbel) error term for each chooser-alternative pair, adding it to the systematic utility, and selecting the alternative with the highest -total utility. Both methods are valid ways to simulate from a discrete choice model, but EET is more -consistent with the underlying random utility model and is less affected by Monte Carlo noise when comparing -scenarios. +total utility. Both methods simulate the same underlying model, but EET can be less affected by Monte Carlo +noise when comparing scenarios. For more details see :doc:`/dev-guide/explicit-error-terms`. To enable EET for a model run, set the global switch in ``settings.yaml``: @@ -308,15 +307,15 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: When comparing runs, enable or disable this setting consistently across the runs you want to compare. -Using EET changes the simulation method, not the underlying utility expressions or availability rules. -Aggregate behavior should remain comparable to the default method, but individual simulated choices will -not usually match record-by-record. EET is also slower than the default probability-based draw because it -requires additional random draws for each chooser and alternative and the core simulation algorithms have not -yet been optimized for EET performance. Most of the slowdown is due to location choice models, where the number -of alternatives is large and the current importance-sampling method requires many repeated choices for all -alternatives. There are several ways to reduce the additional runtime, several of which are currently being -investigated. It is also possible to turn off EET for the sampling part of location choice models by adding the -following line to the location choice model settings: +Using EET changes the simulation method, not the underlying model. Aggregate behavior should remain statistically +comparable to the default method, but individual simulated choices will not usually match record-by-record. +EET is also slower than the default probability-based draw because it generates and processes one random error +term per chooser-alternative pair, rather than one uniform draw per chooser after probabilities are computed. +Most of the current slowdown comes from location choice models, where the number of alternatives is large and +the current importance-sampling workflow still requires many repeated simulations. Work to reduce that overhead is +ongoing. Until then, it is also possible to turn off EET for the sampling part of these models by adding the following +lines to the settings of all models where location choice sampling is used (currently all location and destination +choice models as well as disaggregate accessibilities): .. code-block:: yaml @@ -324,7 +323,6 @@ following line to the location choice model settings: use_explicit_error_terms: sample: false -This can be applied to all models where location choice sampling is used, which currently includesall location -and destination choice models as well as disaggregate accessibilities). - -For more details see :doc:`/dev-guide/explicit-error-terms`. +If the user decides against this option, then another point to consider is memory usage during location sampling. +We recommend using explicit chunking with fractional numbers in this case; see :ref:`explicit_error_terms_memory` +for additional information. From 2d1dbcb89536eb07595abc5d0166b050d83015a2 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 20:59:29 +1000 Subject: [PATCH 180/298] doco re memory --- docs/dev-guide/explicit-error-terms.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 253693308f..5071245f3c 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -94,6 +94,18 @@ choice is currently handled. Runtime improvement work is under way, but large im also be obtained by using Monte Carlo simulation for the sampling part of location choice, see {ref}`explicit_error_terms_ways_to_run`. +(explicit_error_terms_memory)= +### Memory usage +Another point to consider is memory usage during location sampling. For example, the MTC extended +example model samples half of all zones for disaggregate accessibility settings which amounts to +727 samples per chooser across 1454 alternatives. Due to the large memory footprint of all error +terms for all choosers, for machines with limited memory it is likely that chunking will be needed. +We recommend to use explicit chunking if possible, because the chunk size is set at the model +level, but location sampling, location logsums, and location choice from the sampled choice set +all have very different chooser characteristics and using absolute values for the explicit chunk +size would lead to a large number of chunks for the logsum calculations, which is relatively slow. + + ## Implementation Details and Adding New Models The core simulation is implemented in `activitysim.core.logit.make_choices_utility_based`. Most From 837ae417fb65a16c2da37fea4ea5d9f4242130bb Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 21:03:06 +1000 Subject: [PATCH 181/298] doco clean up --- docs/users-guide/ways_to_run.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 4823bb4c11..385367999e 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -323,6 +323,6 @@ choice models as well as disaggregate accessibilities): use_explicit_error_terms: sample: false -If the user decides against this option, then another point to consider is memory usage during location sampling. -We recommend using explicit chunking with fractional numbers in this case; see :ref:`explicit_error_terms_memory` -for additional information. +If you keep EET enabled for the sampling step, also consider memory usage during location sampling. +In that case, explicit chunking with a fractional ``explicit_chunk`` setting is often the most +practical approach; see :ref:`explicit_error_terms_memory` for details. From d674268da43b93148e4d93664e2c7b6c8fae5483 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 21:14:39 +1000 Subject: [PATCH 182/298] memory usage doco --- docs/dev-guide/explicit-error-terms.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 5071245f3c..f603efbebc 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -96,14 +96,18 @@ also be obtained by using Monte Carlo simulation for the sampling part of locati (explicit_error_terms_memory)= ### Memory usage -Another point to consider is memory usage during location sampling. For example, the MTC extended -example model samples half of all zones for disaggregate accessibility settings which amounts to -727 samples per chooser across 1454 alternatives. Due to the large memory footprint of all error -terms for all choosers, for machines with limited memory it is likely that chunking will be needed. -We recommend to use explicit chunking if possible, because the chunk size is set at the model -level, but location sampling, location logsums, and location choice from the sampled choice set -all have very different chooser characteristics and using absolute values for the explicit chunk -size would lead to a large number of chunks for the logsum calculations, which is relatively slow. + +EET in its current implementation also increase memory pressure during location sampling. +During the sampling step, an array of size (number of choosers, number of alternatives, +number of samples) is allocated for all random error terms. This can quickly become unwieldy +for machines with limited memory and it is likely that chunking will be needed. + +When chunking is needed and explicit chunking is used, using fractional values for the chunk +size rather than absolute numbers of choosers is often a better fit. This is because the individual +steps of location choice models (location sampling, location logsums, and location choice from the +sampled choice set) all have very different chooser characteristics, but the chunk size currently +can only be set at the model level. Using absolute values for the explicit chunk size would lead to +a large number of chunks for the logsum calculations, which is relatively slow. ## Implementation Details and Adding New Models From 7959c9411c7fbaab6c969fe3b42b78b1208ff959 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 21:38:03 +1000 Subject: [PATCH 183/298] clean up doco --- docs/dev-guide/explicit-error-terms.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index f603efbebc..da80fe4502 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -97,17 +97,19 @@ also be obtained by using Monte Carlo simulation for the sampling part of locati (explicit_error_terms_memory)= ### Memory usage -EET in its current implementation also increase memory pressure during location sampling. +EET in its current implementation also increases memory pressure during location sampling. During the sampling step, an array of size (number of choosers, number of alternatives, number of samples) is allocated for all random error terms. This can quickly become unwieldy -for machines with limited memory and it is likely that chunking will be needed. - -When chunking is needed and explicit chunking is used, using fractional values for the chunk -size rather than absolute numbers of choosers is often a better fit. This is because the individual -steps of location choice models (location sampling, location logsums, and location choice from the -sampled choice set) all have very different chooser characteristics, but the chunk size currently -can only be set at the model level. Using absolute values for the explicit chunk size would lead to -a large number of chunks for the logsum calculations, which is relatively slow. +for machines with limited memory, and [chunking](../users-guide/performance/chunking.md) will +likely be needed. + +When chunking is needed and [explicit chunking](../users-guide/performance/chunking.md#explicit-chunking) +is used, using fractional values for the chunk size rather than absolute numbers of choosers is +often a better fit. This is because the individual steps of location choice models +(location sampling, location logsums, and location choice from the sampled choice set) all have +very different chooser characteristics, but the chunk size currently can only be set at the model +level. Using absolute values for the explicit chunk size would lead to a large number of chunks +for the logsum calculations, which is relatively slow. ## Implementation Details and Adding New Models From a7e8529d6be103ced9719e1cd0e0ac5ea0597592 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 2 Apr 2026 21:52:41 +1000 Subject: [PATCH 184/298] undo changes in unrelated code to reduce noise --- activitysim/core/test/test_logit.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index cfb298081c..e381cd85ae 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -697,15 +697,6 @@ def test_interaction_dataset_no_sample(interaction_choosers, interaction_alts): def test_interaction_dataset_sampled(interaction_choosers, interaction_alts): - class DummyRNG: - def choice_for_df(self, df, a, size, replace=False): - return np.array([2, 3, 0, 2, 3, 0, 1, 0]) - - class DummyState: - @staticmethod - def get_rn_generator(): - return DummyRNG() - expected = pd.DataFrame( { "attr": ["a"] * 2 + ["b"] * 2 + ["c"] * 2 + ["b"] * 2, @@ -715,7 +706,7 @@ def get_rn_generator(): ) interacted = logit.interaction_dataset( - DummyState(), + workflow.State().default_settings(), interaction_choosers, interaction_alts, sample_size=2, From be3a270ce819f5de98d861aee3ef457a7174cd4d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 3 Apr 2026 08:35:58 +1000 Subject: [PATCH 185/298] testing for semcog model, update to shadow pricing reset --- activitysim/abm/models/location_choice.py | 21 +- .../test/configs_eet/settings.yaml | 3 + .../test/regress/final_eet_trips.csv | 205 ++++++++++++++++++ .../production_semcog/test/test_semcog.py | 26 ++- 4 files changed, 242 insertions(+), 13 deletions(-) create mode 100644 activitysim/examples/production_semcog/test/configs_eet/settings.yaml create mode 100644 activitysim/examples/production_semcog/test/regress/final_eet_trips.csv diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index eccc51fb79..645912e2af 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -1019,15 +1019,6 @@ def iterate_location_choice( ) = None # initialize to None, will be populated in first iteration for iteration in range(1, max_iterations + 1): - # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is - # used on each iteration. Only applying when using EET for now because this will need changes to integration - # tests, but we will probably want this for MC simulation as well. - if state.settings.use_explicit_error_terms and iteration > 1: - logger.debug( - f"{trace_label} resetting random number generator offsets for iteration {iteration}" - ) - state.get_rn_generator().reset_offsets_for_step(state.current_model_name) - persons_merged_df_ = persons_merged_df.copy() if spc.use_shadow_pricing and iteration > 1: @@ -1040,6 +1031,18 @@ def iterate_location_choice( ] persons_merged_df_ = persons_merged_df_.sort_index() + # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is + # used on each iteration. Note this has to happen AFTER updating shadow prices because the simulation method + # draws random numbers. + # Only applying when using EET for now because this will need changes to integration + # tests, but it's probably a good idea for MC simulation as well. + if state.settings.use_explicit_error_terms and iteration > 1: + logger.debug( + f"{trace_label} resetting random number generator offsets for iteration {iteration}" + ) + state.get_rn_generator().reset_offsets_for_step(state.current_model_name) + + choices_df_, save_sample_df = run_location_choice( state, persons_merged_df_, diff --git a/activitysim/examples/production_semcog/test/configs_eet/settings.yaml b/activitysim/examples/production_semcog/test/configs_eet/settings.yaml new file mode 100644 index 0000000000..08c06d702e --- /dev/null +++ b/activitysim/examples/production_semcog/test/configs_eet/settings.yaml @@ -0,0 +1,3 @@ +inherit_settings: True + +use_explicit_error_terms: True diff --git a/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv new file mode 100644 index 0000000000..5c0b578235 --- /dev/null +++ b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv @@ -0,0 +1,205 @@ +"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","original_school_zone_id","parked_at_university","depart","tour_includes_parking","trip_id_pre_parking","trip_mode","mode_choice_logsum","trip_id" +2632656,1066353,"othdiscr",1,true,1,22812,22688,107938911,"othdiscr",,,false,29,1,863511289,"SHARED2",-0.8488473021059283,1727022577 +2632656,1066353,"othdiscr",1,false,4,22795,22812,107938911,"parking",,,false,32,1,863511293,"SHARED2",-0.4222882135487892,1727022585 +2632656,1066353,"othdiscr",2,false,4,22767,22795,107938911,"shopping",8.2354996454247,,true,32,1,863511293,"WALK",2.1502572806761995,1727022586 +2632656,1066353,"othdiscr",3,false,4,22795,22767,107938911,"parking",,,true,33,1,863511294,"WALK",4.3546491508875285,1727022587 +2632656,1066353,"othdiscr",4,false,4,22688,22795,107938911,"home",,,false,33,1,863511294,"SHARED2",-0.7598314660410206,1727022588 +2632656,1066353,"work",1,true,1,22754,22688,107938935,"work",,,false,10,0,863511481,"DRIVEALONE",-0.49288893792864136,1727022961 +2632656,1066353,"work",1,false,1,22688,22754,107938935,"home",,,false,28,0,863511485,"DRIVEALONE",-0.524270408328608,1727022969 +2632657,1066353,"othdiscr",1,true,1,22688,22688,107938962,"othdiscr",,,false,28,0,863511697,"WALK",0.3324082937283966,1727023393 +2632657,1066353,"othdiscr",1,false,1,22688,22688,107938962,"home",,,false,38,0,863511701,"WALK",0.3324082937283966,1727023401 +2632657,1066353,"othdiscr",1,true,1,22688,22688,107938963,"othdiscr",,,false,10,0,863511705,"WALK",0.3324082937283966,1727023409 +2632657,1066353,"othdiscr",1,false,1,22688,22688,107938963,"home",,,false,10,0,863511709,"WALK",0.3324082937283966,1727023417 +2632657,1066353,"school",1,true,1,22694,22688,107938968,"school",,,false,10,0,863511745,"SCHOOLBUS",-1.3476633093405816,1727023489 +2632657,1066353,"school",1,false,2,22676,22694,107938968,"eatout",-23.434159325514656,,false,27,0,863511749,"WALK",-9.200009261635815,1727023497 +2632657,1066353,"school",2,false,2,22688,22676,107938968,"home",,,false,27,0,863511750,"WALK",-9.038579100395838,1727023498 +2632659,1066353,"othdiscr",1,true,2,22804,22688,107939044,"parking",,,false,17,1,863512353,"DRIVEALONE",-0.8467894486224761,1727024705 +2632659,1066353,"othdiscr",2,true,2,22802,22804,107939044,"othdiscr",,,true,17,1,863512353,"WALK",8.06152871202933,1727024706 +2632659,1066353,"othdiscr",1,false,2,22804,22802,107939044,"parking",,,true,19,1,863512357,"WALK",8.06152871202933,1727024713 +2632659,1066353,"othdiscr",2,false,2,22688,22804,107939044,"home",,,false,19,1,863512357,"DRIVEALONE",-0.8411471912616634,1727024714 +2632659,1066353,"shopping",1,true,2,22688,22688,107939052,"othmaint",11.802188841934507,,false,12,0,863512417,"SHARED2",0.45240909180099215,1727024833 +2632659,1066353,"shopping",2,true,2,22683,22688,107939052,"shopping",,,false,13,0,863512418,"SHARED2",0.2405447885277471,1727024834 +2632659,1066353,"shopping",1,false,1,22688,22683,107939052,"home",,,false,13,0,863512421,"SHARED2",0.24080195215313727,1727024841 +2632738,1066386,"eatout",1,true,1,22767,22688,107942264,"eatout",,,false,27,0,863538113,"BIKE",-0.6093298604450466,1727076225 +2632738,1066386,"eatout",1,false,1,22688,22767,107942264,"home",,,false,32,0,863538117,"BIKE",-0.6080172231452846,1727076233 +2632738,1066386,"school",1,true,1,22716,22688,107942289,"school",,,false,10,0,863538313,"SCHOOLBUS",-1.3229057306562648,1727076625 +2632738,1066386,"school",1,false,3,22716,22716,107942289,"escort",-22.29714629889315,,false,25,0,863538317,"WALK",-8.708489622905788,1727076633 +2632738,1066386,"school",2,false,3,22682,22716,107942289,"othmaint",-22.914110705787074,,false,25,0,863538318,"SHARED3",-9.231759948709062,1727076634 +2632738,1066386,"school",3,false,3,22688,22682,107942289,"home",,,false,25,0,863538319,"SHARED3",-9.193961566181608,1727076635 +2632739,1066386,"othmaint",1,true,1,22678,22688,107942327,"othmaint",,,false,12,0,863538617,"DRIVEALONE",0.05480173136111777,1727077233 +2632739,1066386,"othmaint",1,false,1,22688,22678,107942327,"home",,,false,13,0,863538621,"DRIVEALONE",0.05478595698539101,1727077241 +2632739,1066386,"shopping",1,true,1,22689,22688,107942332,"shopping",,,false,33,0,863538657,"DRIVEALONE",0.4655442011339064,1727077313 +2632739,1066386,"shopping",1,false,1,22688,22689,107942332,"home",,,false,35,0,863538661,"SHARED3",0.4655442011339064,1727077321 +2640879,1069967,"shopping",1,true,1,22676,22676,108276072,"shopping",,,false,26,0,866208577,"WALK",0.17161148764545403,1732417153 +2640879,1069967,"shopping",1,false,1,22676,22676,108276072,"home",,,false,27,0,866208581,"DRIVEALONE",0.17161148764545403,1732417161 +2640879,1069967,"work",1,true,2,22811,22676,108276078,"parking",,,false,8,1,866208625,"DRIVEALONE",-0.5454033134073947,1732417249 +2640879,1069967,"work",2,true,2,22811,22811,108276078,"work",,,true,8,1,866208625,"WALK",2.9955771616115108,1732417250 +2640879,1069967,"work",1,false,2,22811,22811,108276078,"parking",,,true,23,1,866208629,"WALK",2.995584220814057,1732417257 +2640879,1069967,"work",2,false,2,22676,22811,108276078,"home",,,false,23,1,866208629,"DRIVEALONE",-0.4305990185449319,1732417258 +2640880,1069967,"othdiscr",1,true,1,22743,22676,108276105,"othdiscr",,,false,13,0,866208841,"DRIVEALONE",-0.8097498243611078,1732417681 +2640880,1069967,"othdiscr",1,false,1,22676,22743,108276105,"home",,,false,16,0,866208845,"DRIVEALONE",-0.7246524757544739,1732417689 +2645904,1072088,"social",1,true,2,22737,22711,108482100,"othmaint",7.814738943671942,,false,26,0,867856801,"DRIVEALONE",-1.4099478726631944,1735713601 +2645904,1072088,"social",2,true,2,22758,22737,108482100,"social",,,false,26,0,867856802,"DRIVEALONE",-0.5360854177766818,1735713602 +2645904,1072088,"social",1,false,1,22711,22758,108482100,"home",,,false,32,0,867856805,"DRIVEALONE",-0.7180224259537248,1735713609 +2645905,1072088,"othmaint",1,true,2,22795,22711,108482133,"parking",,,false,27,1,867857065,"DRIVEALONE",-0.8342963785932982,1735714129 +2645905,1072088,"othmaint",2,true,2,22808,22795,108482133,"othmaint",,,true,27,1,867857065,"WALK",4.143203548756641,1735714130 +2645905,1072088,"othmaint",1,false,2,22795,22808,108482133,"parking",,,true,31,1,867857069,"WALK",4.065724479690421,1735714137 +2645905,1072088,"othmaint",2,false,2,22711,22795,108482133,"home",,,false,31,1,867857069,"DRIVEALONE",-0.8413183337505313,1735714138 +2645905,1072088,"social",1,true,1,22714,22711,108482141,"social",,,false,33,0,867857129,"DRIVEALONE",-0.04994357209535508,1735714257 +2645905,1072088,"social",1,false,1,22711,22714,108482141,"home",,,false,34,0,867857133,"DRIVEALONE",-0.04994356771528009,1735714265 +2645906,1072088,"othmaint",1,true,1,22683,22711,108482174,"othmaint",,,false,19,0,867857393,"DRIVEALONE",-0.13092777228566677,1735714785 +2645906,1072088,"othmaint",1,false,4,22688,22683,108482174,"shopping",9.877646617096774,,false,29,0,867857397,"DRIVEALONE",0.021987670341255844,1735714793 +2645906,1072088,"othmaint",2,false,4,22683,22688,108482174,"shopping",9.99474165609772,,false,29,0,867857398,"WALK",0.02958108522301107,1735714794 +2645906,1072088,"othmaint",3,false,4,22711,22683,108482174,"shopping",9.615674159424772,,false,29,0,867857399,"DRIVEALONE",-0.13506201969852885,1735714795 +2645906,1072088,"othmaint",4,false,4,22711,22711,108482174,"home",,,false,29,0,867857400,"WALK",0.15219476711813038,1735714796 +2645906,1072088,"shopping",1,true,1,22745,22711,108482179,"shopping",,,false,16,0,867857433,"DRIVEALONE",-1.8275616246674673,1735714865 +2645906,1072088,"shopping",1,false,1,22711,22745,108482179,"home",,,false,19,0,867857437,"DRIVEALONE",-1.2985090645679782,1735714873 +2645907,1072088,"eatout",1,true,2,22766,22711,108482193,"parking",,,false,34,1,867857545,"DRIVEALONE",-0.9960586671404674,1735715089 +2645907,1072088,"eatout",2,true,2,22767,22766,108482193,"eatout",,,true,34,1,867857545,"WALK",6.210026630752176,1735715090 +2645907,1072088,"eatout",1,false,2,22766,22767,108482193,"parking",,,true,36,1,867857549,"WALK",6.210032999225622,1735715097 +2645907,1072088,"eatout",2,false,2,22711,22766,108482193,"home",,,false,36,1,867857549,"DRIVEALONE",-0.9957395146055389,1735715098 +2645907,1072088,"school",1,true,1,22716,22711,108482218,"school",,,false,11,0,867857745,"SCHOOLBUS",-1.320618029716206,1735715489 +2645907,1072088,"school",1,false,4,22724,22716,108482218,"social",-22.549809261930204,,false,26,0,867857749,"SHARED2",-9.188557941135578,1735715497 +2645907,1072088,"school",2,false,4,22712,22724,108482218,"othdiscr",-22.277427876366566,,false,26,0,867857750,"SHARED2",-9.32548153595726,1735715498 +2645907,1072088,"school",3,false,4,22711,22712,108482218,"escort",-21.90946101819234,,false,26,0,867857751,"WALK",-8.16070608833084,1735715499 +2645907,1072088,"school",4,false,4,22711,22711,108482218,"home",,,false,26,0,867857752,"SHARED2",-8.660375528783023,1735715500 +2671333,1083128,"school",1,true,2,22657,22637,109524684,"othmaint",-23.99886783982251,,false,9,0,876197473,"SHARED2",-9.259145285147683,1752394945 +2671333,1083128,"school",2,true,2,22640,22657,109524684,"school",,,false,9,0,876197474,"SCHOOLBUS",-1.312859221283506,1752394946 +2671333,1083128,"school",1,false,1,22637,22640,109524684,"home",,,false,29,0,876197477,"SHARED3",-9.608846466303847,1752394953 +2853513,1152948,"othdiscr",1,true,1,22766,22770,116994058,"othdiscr",,,false,14,0,935952465,"WALK",-0.9011100567629171,1871904929 +2853513,1152948,"othdiscr",1,false,1,22770,22766,116994058,"home",,,false,18,0,935952469,"WALK",-0.9051750552987663,1871904937 +2853513,1152948,"othdiscr",1,true,4,22767,22770,116994059,"eatout",14.667019798235065,,false,21,0,935952473,"WALK",2.2663942192594435,1871904945 +2853513,1152948,"othdiscr",2,true,4,22770,22767,116994059,"othmaint",15.154344404160929,,false,21,0,935952474,"WALK",0.9306047427247703,1871904946 +2853513,1152948,"othdiscr",3,true,4,22771,22770,116994059,"eatout",14.74628023877142,,false,21,0,935952475,"WALK",1.5044226646559185,1871904947 +2853513,1152948,"othdiscr",4,true,4,22773,22771,116994059,"othdiscr",,,false,21,0,935952476,"WALK",0.19292867000012684,1871904948 +2853513,1152948,"othdiscr",1,false,2,22767,22773,116994059,"escort",14.378785798118802,,false,21,0,935952477,"WALK",-0.05516003714691903,1871904953 +2853513,1152948,"othdiscr",2,false,2,22770,22767,116994059,"home",,,false,21,0,935952478,"WALK",2.2663942192594435,1871904954 +2853513,1152948,"othdiscr",1,true,1,22771,22770,116994060,"othdiscr",,,false,28,0,935952481,"WALK",1.5044226646559185,1871904961 +2853513,1152948,"othdiscr",1,false,1,22770,22771,116994060,"home",,,false,29,0,935952485,"WALK",1.5044226646559185,1871904969 +2853513,1152948,"work",1,true,2,22771,22770,116994072,"escort",27.34513603248586,,false,30,0,935952577,"WALK",4.823175204067197,1871905153 +2853513,1152948,"work",2,true,2,22773,22771,116994072,"work",,,false,32,0,935952578,"WALK",3.945458382874527,1871905154 +2853513,1152948,"work",1,false,1,22770,22773,116994072,"home",,,false,45,0,935952581,"WALK",4.0042155675325635,1871905161 +2856204,1154357,"othdiscr",1,true,1,22770,22815,117104389,"othdiscr",,,false,29,0,936835113,"WALK",-0.043175037831942764,1873670225 +2856204,1154357,"othdiscr",1,false,1,22815,22770,117104389,"home",,,false,29,0,936835117,"WALK",0.10104097189929005,1873670233 +2856660,1154635,"univ",1,true,1,22766,22815,117123091,"univ",,,false,13,0,936984729,"WALK_LOC",2.23181471014434,1873969457 +2856660,1154635,"univ",1,false,1,22815,22766,117123091,"home",,,false,27,0,936984733,"WALK",2.2179223176535414,1873969465 +2856661,1154635,"univ",1,true,1,22766,22815,117123132,"univ",,,false,14,0,936985057,"WALK",2.23181471014434,1873970113 +2856661,1154635,"univ",1,false,1,22815,22766,117123132,"home",,,false,26,0,936985061,"WALK",2.218652692553078,1873970121 +2861950,1156849,"univ",1,true,1,22766,22801,117339981,"univ",,,false,17,0,938719849,"WALK_LOC",2.3790349408815556,1877439697 +2861950,1156849,"univ",1,false,2,22764,22766,117339981,"univ",10.335069017511323,,false,17,0,938719853,"WALK_LOC",2.6617812905452896,1877439705 +2861950,1156849,"univ",2,false,2,22801,22764,117339981,"home",,,false,18,0,938719854,"WALK_LOC",2.35700318172487,1877439706 +2861950,1156849,"shopping",1,true,2,22795,22801,117339983,"parking",,,false,12,1,938719865,"WALK",-0.21895344461922386,1877439729 +2861950,1156849,"shopping",2,true,2,22767,22795,117339983,"shopping",,,true,12,1,938719865,"WALK",3.7909702907421834,1877439730 +2861950,1156849,"shopping",1,false,5,22795,22767,117339983,"parking",,,true,13,1,938719869,"WALK",3.4552717476523997,1877439737 +2861950,1156849,"shopping",2,false,5,22770,22795,117339983,"eatout",13.591054539597575,,false,13,1,938719869,"DRIVEALONE",-0.571477727779577,1877439738 +2861950,1156849,"shopping",3,false,5,22770,22770,117339983,"eatout",11.67129001032396,,false,13,1,938719870,"WALK",0.13094348045186022,1877439739 +2861950,1156849,"shopping",4,false,5,22795,22770,117339983,"parking",,,false,13,1,938719871,"DRIVEALONE",-0.34397236094497297,1877439740 +2861950,1156849,"shopping",5,false,5,22801,22795,117339983,"home",,,true,13,1,938719871,"WALK",1.6057419140820288,1877439741 +2861951,1156849,"escort",1,true,1,22639,22801,117340000,"escort",,,false,34,1,938720001,"SHARED3",-0.4216870410803827,1877440001 +2861951,1156849,"escort",1,false,2,22806,22639,117340000,"parking",,,false,36,1,938720005,"SHARED2",-0.6086337356771375,1877440009 +2861951,1156849,"escort",2,false,2,22801,22806,117340000,"home",,,true,36,1,938720005,"WALK",2.8917510947059446,1877440010 +2861951,1156849,"othdiscr",1,true,1,22783,22801,117340016,"othdiscr",,,false,10,0,938720129,"WALK",0.34049307850458627,1877440257 +2861951,1156849,"othdiscr",1,false,1,22801,22783,117340016,"home",,,false,11,0,938720133,"WALK",0.34049307850458627,1877440265 +2861951,1156849,"univ",1,true,1,22766,22801,117340022,"univ",,,false,11,0,938720177,"WALK",2.376796810859204,1877440353 +2861951,1156849,"univ",1,false,1,22801,22766,117340022,"home",,,false,33,0,938720181,"WALK",2.342231383429434,1877440361 +2861952,1156849,"univ",1,true,2,22802,22801,117340063,"work",14.623747590402552,,false,16,0,938720505,"WALK",2.3673504725748127,1877441009 +2861952,1156849,"univ",2,true,2,22809,22802,117340063,"univ",,,false,17,0,938720506,"WALK",-0.3649612631284906,1877441010 +2861952,1156849,"univ",1,false,1,22801,22809,117340063,"home",,,false,20,0,938720509,"WALK",-0.656569218265208,1877441017 +2862055,1156884,"eatout",1,true,1,22802,22804,117344268,"eatout",,,false,33,0,938754145,"WALK",2.709890128233274,1877508289 +2862055,1156884,"eatout",1,false,2,22806,22802,117344268,"shopping",17.35261269322678,,false,37,0,938754149,"WALK",2.7091008864601434,1877508297 +2862055,1156884,"eatout",2,false,2,22804,22806,117344268,"home",,,false,37,0,938754150,"WALK",0.73177661169581,1877508298 +2862055,1156884,"univ",1,true,1,22809,22804,117344286,"univ",,,false,12,0,938754289,"WALK",-0.44242952311569,1877508577 +2862055,1156884,"univ",1,false,1,22804,22809,117344286,"home",,,false,33,0,938754293,"WALK",-0.44242952311569,1877508585 +2862055,1156884,"social",1,true,3,22807,22804,117344291,"eatout",12.084243679384295,,false,37,0,938754329,"WALK",1.534047590758811,1877508657 +2862055,1156884,"social",2,true,3,22770,22807,117344291,"othdiscr",12.902879188828422,,false,38,0,938754330,"WALK",0.4553060051673671,1877508658 +2862055,1156884,"social",3,true,3,22794,22770,117344291,"social",,,false,38,0,938754331,"WALK",-0.4377870962366495,1877508659 +2862055,1156884,"social",1,false,1,22804,22794,117344291,"home",,,false,43,0,938754333,"WALK",-0.18058190179032135,1877508665 +2862056,1156884,"univ",1,true,1,22809,22804,117344327,"univ",,,false,9,0,938754617,"WALK",-0.44242952311569,1877509233 +2862056,1156884,"univ",1,false,3,22807,22809,117344327,"eatout",13.728406618828826,,false,26,0,938754621,"WALK",-0.22722993342809517,1877509241 +2862056,1156884,"univ",2,false,3,22767,22807,117344327,"shopping",17.17708856852041,,false,26,0,938754622,"WALK",2.329516762852266,1877509242 +2862056,1156884,"univ",3,false,3,22804,22767,117344327,"home",,,false,26,0,938754623,"WALK",1.8938019500606744,1877509243 +2862056,1156884,"shopping",1,true,1,22738,22804,117344329,"shopping",,,false,29,1,938754633,"WALK",-0.40920601532640766,1877509265 +2862056,1156884,"shopping",1,false,2,22766,22738,117344329,"parking",,,false,31,1,938754637,"DRIVEALONE",-0.4546401594188224,1877509273 +2862056,1156884,"shopping",2,false,2,22766,22766,117344329,"parking",,,true,31,1,938754637,"WALK",2.4879495513337813,1877509274 +2862057,1156884,"univ",1,true,3,22807,22804,117344368,"social",14.134449334392166,,false,8,0,938754945,"WALK",2.3010713861382164,1877509889 +2862057,1156884,"univ",2,true,3,22767,22807,117344368,"work",15.487934097337227,,false,16,0,938754946,"WALK",2.1918016019844577,1877509890 +2862057,1156884,"univ",3,true,3,22809,22767,117344368,"univ",,,false,22,0,938754947,"WALK",-0.50518420043921,1877509891 +2862057,1156884,"univ",1,false,1,22804,22809,117344368,"home",,,false,32,0,938754949,"WALK",-0.44242952311569,1877509897 +2863920,1157823,"univ",1,true,1,22766,22812,117420751,"univ",,,false,15,0,939366009,"WALK_LOC",2.0570235489392443,1878732017 +2863920,1157823,"univ",1,false,2,22767,22766,117420751,"eatout",23.659417692376945,,false,25,0,939366013,"WALK",2.494484873165744,1878732025 +2863920,1157823,"univ",2,false,2,22812,22767,117420751,"home",,,false,26,0,939366014,"WALK",4.953924530435282,1878732026 +2863921,1157823,"univ",1,true,1,22809,22812,117420792,"univ",,,false,13,0,939366337,"WALK",2.486268830878343,1878732673 +2863921,1157823,"univ",1,false,4,22809,22809,117420792,"univ",10.289883977911662,22809,false,14,0,939366341,"WALK",3.0064499347700666,1878732681 +2863921,1157823,"univ",2,false,4,22808,22809,117420792,"othdiscr",13.054363997677639,,false,14,0,939366342,"WALK",2.500770328794477,1878732682 +2863921,1157823,"univ",3,false,4,22766,22808,117420792,"univ",11.668617099548605,22766,false,14,0,939366343,"WALK",3.854292572099684,1878732683 +2863921,1157823,"univ",4,false,4,22812,22766,117420792,"home",,,false,14,0,939366344,"WALK",2.0522280506197337,1878732684 +2863922,1157823,"univ",1,true,1,22809,22812,117420833,"univ",,,false,7,0,939366665,"BIKE",-0.464473446954635,1878733329 +2863922,1157823,"univ",1,false,2,22809,22809,117420833,"univ",10.643972757226907,22809,false,23,0,939366669,"BIKE",-0.005181884649800808,1878733337 +2863922,1157823,"univ",2,false,2,22812,22809,117420833,"home",,,false,24,0,939366670,"BIKE",-0.464473446954635,1878733338 +2863922,1157823,"shopping",1,true,2,22806,22812,117420835,"parking",,,false,28,1,939366681,"WALK",-0.18297922233662095,1878733361 +2863922,1157823,"shopping",2,true,2,22800,22806,117420835,"shopping",,,true,28,1,939366681,"WALK",2.477773445267558,1878733362 +2863922,1157823,"shopping",1,false,3,22767,22800,117420835,"shopping",10.837169270242605,,true,35,1,939366685,"WALK",2.0313681136538952,1878733369 +2863922,1157823,"shopping",2,false,3,22806,22767,117420835,"parking",,,true,36,1,939366686,"WALK",4.831708055552031,1878733370 +2863922,1157823,"shopping",3,false,3,22812,22806,117420835,"home",,,false,36,1,939366686,"SHARED2",0.2332508156022282,1878733371 +2866914,1159236,"work",1,true,2,22774,22797,117543513,"parking",,,false,11,1,940348105,"WALK",0.3696369443918809,1880696209 +2866914,1159236,"work",2,true,2,22774,22774,117543513,"work",,,true,11,1,940348105,"WALK",3.0380641909412422,1880696210 +2866914,1159236,"work",1,false,1,22797,22774,117543513,"home",,,true,32,1,940348109,"WALK",1.6315258077049342,1880696217 +2866915,1159236,"work",1,true,1,22802,22797,117543554,"work",,,false,21,0,940348433,"WALK",1.4889378816413315,1880696865 +2866915,1159236,"work",1,false,1,22797,22802,117543554,"home",,,false,43,0,940348437,"WALK",1.4889378816413315,1880696873 +2866916,1159236,"atwork",1,true,1,22778,22760,117543579,"atwork",,,false,15,0,940348633,"WALK",-0.6017055929110162,1880697265 +2866916,1159236,"atwork",1,false,2,22771,22778,117543579,"escort",13.958619580608657,,false,18,0,940348637,"WALK",0.0015080519389326266,1880697273 +2866916,1159236,"atwork",2,false,2,22760,22771,117543579,"work",,,false,18,0,940348638,"WALK",1.131494654421563,1880697274 +2866916,1159236,"othdiscr",1,true,1,22787,22797,117543581,"othdiscr",,,false,29,0,940348649,"WALK",-0.7205273617337421,1880697297 +2866916,1159236,"othdiscr",1,false,1,22797,22787,117543581,"home",,,false,36,0,940348653,"WALK",-0.7205273617337421,1880697305 +2866916,1159236,"othmaint",1,true,4,22796,22797,117543584,"parking",,,false,28,1,940348673,"DRIVEALONE",-0.0203043486563632,1880697345 +2866916,1159236,"othmaint",2,true,4,22807,22796,117543584,"escort",12.200555556836377,,true,28,1,940348673,"WALK",5.136872721261722,1880697346 +2866916,1159236,"othmaint",3,true,4,22796,22807,117543584,"parking",,,true,29,1,940348674,"WALK",3.14893876731007,1880697347 +2866916,1159236,"othmaint",4,true,4,22770,22796,117543584,"othmaint",,,false,29,1,940348674,"DRIVEALONE",-0.15170981809506287,1880697348 +2866916,1159236,"othmaint",1,false,2,22796,22770,117543584,"parking",,,false,29,1,940348677,"DRIVEALONE",-0.1584988684239379,1880697353 +2866916,1159236,"othmaint",2,false,2,22796,22796,117543584,"parking",,,true,29,1,940348677,"WALK",3.903741766016903,1880697354 +2866916,1159236,"work",1,true,1,22760,22797,117543595,"work",,,false,6,0,940348761,"BIKE",1.411922122583906,1880697521 +2866916,1159236,"work",1,false,2,22767,22760,117543595,"escort",16.511766164218447,,false,23,0,940348765,"BIKE",1.8250316638262625,1880697529 +2866916,1159236,"work",2,false,2,22797,22767,117543595,"home",,,false,25,0,940348766,"BIKE",1.3197235435334995,1880697530 +2870654,1160939,"othdiscr",1,true,1,22747,22740,117696839,"othdiscr",,,false,31,0,941574713,"WALK",7.372170552771186,1883149425 +2870654,1160939,"othdiscr",1,false,1,22740,22747,117696839,"home",,,false,33,0,941574717,"WALK",7.372204141413259,1883149433 +2870654,1160939,"work",1,true,2,22761,22740,117696853,"parking",,,false,7,1,941574825,"DRIVEALONE",-0.16109255990094318,1883149649 +2870654,1160939,"work",2,true,2,22761,22761,117696853,"work",,,true,7,1,941574825,"WALK",3.799348055144246,1883149650 +2870654,1160939,"work",1,false,2,22761,22761,117696853,"parking",,,true,15,1,941574829,"WALK",3.7993411043161562,1883149657 +2870654,1160939,"work",2,false,2,22740,22761,117696853,"home",,,false,15,1,941574829,"SHARED2",0.14204552305427365,1883149658 +2870655,1160939,"work",1,true,1,22770,22740,117696894,"work",,,false,10,0,941575153,"DRIVEALONE",-0.4875582876915185,1883150305 +2870655,1160939,"work",1,false,1,22740,22770,117696894,"home",,,false,20,0,941575157,"WALK",-0.7149551826154896,1883150313 +2870656,1160939,"univ",1,true,3,22767,22740,117696927,"shopping",23.984363940879124,,false,11,0,941575417,"WALK",5.126445987379017,1883150833 +2870656,1160939,"univ",2,true,3,22767,22767,117696927,"eatout",25.472982562888472,,false,11,0,941575418,"WALK",5.62841361203664,1883150834 +2870656,1160939,"univ",3,true,3,22764,22767,117696927,"univ",,,false,19,0,941575419,"WALK",2.817924028836093,1883150835 +2870656,1160939,"univ",1,false,1,22740,22764,117696927,"home",,,false,26,0,941575421,"WALK",2.4571829368719613,1883150841 +2870656,1160939,"univ",1,true,1,22764,22740,117696928,"univ",,,false,26,0,941575425,"WALK",2.439713266027429,1883150849 +2870656,1160939,"univ",1,false,1,22740,22764,117696928,"home",,,false,31,0,941575429,"WALK_LOC",2.461543319337894,1883150857 +2874270,1162627,"univ",1,true,1,22764,22758,117845101,"univ",,,false,17,0,942760809,"WALK",2.3261410771168642,1885521617 +2874270,1162627,"univ",1,false,2,22764,22764,117845101,"univ",10.700792773839831,,false,26,0,942760813,"WALK",3.004468589374795,1885521625 +2874270,1162627,"univ",2,false,2,22758,22764,117845101,"home",,,false,26,0,942760814,"WALK",2.3239328769783794,1885521626 +2874270,1162627,"shopping",1,true,4,22796,22758,117845103,"parking",,,false,34,1,942760825,"DRIVEALONE",-0.38848558560287977,1885521649 +2874270,1162627,"shopping",2,true,4,22806,22796,117845103,"othmaint",12.259167876768386,,true,34,1,942760825,"WALK",2.5227339389241807,1885521650 +2874270,1162627,"shopping",3,true,4,22767,22806,117845103,"eatout",11.734621106527097,,true,34,1,942760826,"WALK",4.4647310506607925,1885521651 +2874270,1162627,"shopping",4,true,4,22805,22767,117845103,"shopping",,,true,35,1,942760827,"WALK",2.5148845790149537,1885521652 +2874270,1162627,"shopping",1,false,2,22796,22805,117845103,"parking",,,true,35,1,942760829,"WALK",2.018454499589163,1885521657 +2874270,1162627,"shopping",2,false,2,22758,22796,117845103,"home",,,false,35,1,942760829,"DRIVEALONE",-0.49542727241373813,1885521658 +2874271,1162627,"univ",1,true,2,22767,22758,117845142,"escort",13.256155130715978,,false,18,0,942761137,"WALK",2.1084560857351926,1885522273 +2874271,1162627,"univ",2,true,2,22764,22767,117845142,"univ",,,false,18,0,942761138,"WALK",-0.5148347167335139,1885522274 +2874271,1162627,"univ",1,false,2,22767,22764,117845142,"social",13.116659549955148,,false,30,0,942761141,"WALK",-0.5148347167335139,1885522281 +2874271,1162627,"univ",2,false,2,22758,22767,117845142,"home",,,false,30,0,942761142,"WALK",2.1084560857351926,1885522282 +4724316,1944022,"eatout",1,true,1,22771,22765,193696962,"eatout",,,false,38,0,1549575697,"WALK",0.9192956769258102,3099151393 +4724316,1944022,"eatout",1,false,1,22765,22771,193696962,"home",,,false,39,0,1549575701,"WALK",0.9192956769258102,3099151401 +4724316,1944022,"univ",1,true,1,22809,22765,193696987,"univ",,,false,11,0,1549575897,"WALK_LOC",2.504097442012484,3099151793 +4724316,1944022,"univ",1,false,1,22765,22809,193696987,"home",,,false,32,0,1549575901,"WALK",2.486613658307754,3099151801 +4724701,1944407,"univ",1,true,1,22809,22808,193712772,"univ",,,false,11,0,1549702177,"WALK",2.491800843008931,3099404353 +4724701,1944407,"univ",1,false,2,22764,22809,193712772,"univ",10.963078293466896,,false,33,0,1549702181,"WALK",2.428175371397628,3099404361 +4724701,1944407,"univ",2,false,2,22808,22764,193712772,"home",,,false,33,0,1549702182,"WALK",2.4386307350012473,3099404362 +4724720,1944426,"eatout",1,true,1,22739,22806,193713526,"eatout",,,false,15,0,1549708209,"WALK",-0.5363640585021157,3099416417 +4724720,1944426,"eatout",1,false,1,22806,22739,193713526,"home",,,false,18,0,1549708213,"WALK",-0.5363640585021157,3099416425 +4727094,1946800,"shopping",1,true,1,22770,22808,193810887,"shopping",,,false,14,0,1550487097,"WALK",0.3756438367025996,3100974193 +4727094,1946800,"shopping",1,false,1,22808,22770,193810887,"home",,,false,16,0,1550487101,"WALK",0.3756438367025996,3100974201 +4728027,1947733,"univ",1,true,1,22809,22806,193849138,"univ",,,false,12,0,1550793105,"WALK",2.8698246758237826,3101586209 +4728027,1947733,"univ",1,false,4,22769,22809,193849138,"escort",25.420351843799057,,false,27,0,1550793109,"WALK",2.7798007800315285,3101586217 +4728027,1947733,"univ",2,false,4,22807,22769,193849138,"social",27.304467847997117,,false,27,0,1550793110,"WALK",3.248014813584962,3101586218 +4728027,1947733,"univ",3,false,4,22767,22807,193849138,"eatout",30.20403082477785,,false,27,0,1550793111,"WALK_LOC",5.6477996875760095,3101586219 +4728027,1947733,"univ",4,false,4,22806,22767,193849138,"home",,,false,27,0,1550793112,"WALK_LOC",5.333545575515567,3101586220 diff --git a/activitysim/examples/production_semcog/test/test_semcog.py b/activitysim/examples/production_semcog/test/test_semcog.py index e247fd6451..ebf8ba4522 100644 --- a/activitysim/examples/production_semcog/test/test_semcog.py +++ b/activitysim/examples/production_semcog/test/test_semcog.py @@ -11,7 +11,7 @@ from activitysim.core.test._tools import assert_frame_substantively_equal -def run_test_semcog(multiprocess=False): +def run_test_semcog(multiprocess=False, use_explicit_error_terms=False): def example_path(dirname): resource = os.path.join("examples", "production_semcog", dirname) return str(importlib.resources.files("activitysim").joinpath(resource)) @@ -19,9 +19,9 @@ def example_path(dirname): def test_path(dirname): return os.path.join(os.path.dirname(__file__), dirname) - def regress(): + def regress(use_explicit_error_terms=False): regress_trips_df = pd.read_csv( - test_path("regress/final_trips.csv"), dtype={"depart": int} + test_path(f"regress/final{'_eet' if use_explicit_error_terms else ''}_trips.csv"), dtype={"depart": int} ) final_trips_df = pd.read_csv( test_path("output/final_trips.csv"), dtype={"depart": int} @@ -30,6 +30,12 @@ def regress(): file_path = os.path.join(os.path.dirname(__file__), "../simulation.py") + test_config_files = [] + if use_explicit_error_terms: + test_config_files = [ + "-c", + test_path("configs_eet"), + ] if multiprocess: subprocess.run( [ @@ -37,6 +43,7 @@ def regress(): "run", "-a", file_path, + *test_config_files, "-c", test_path("configs_mp"), "-c", @@ -59,6 +66,7 @@ def regress(): "run", "-a", file_path, + *test_config_files, "-c", test_path("configs"), "-c", @@ -73,7 +81,7 @@ def regress(): check=True, ) - regress() + regress(use_explicit_error_terms=use_explicit_error_terms) def test_semcog(): @@ -84,6 +92,16 @@ def test_semcog_mp(): run_test_semcog(multiprocess=True) +def test_semcog_eet(): + run_test_semcog(multiprocess=False, use_explicit_error_terms=True) + + +def test_semcog_mp_eet(): + run_test_semcog(multiprocess=True, use_explicit_error_terms=True) + + if __name__ == "__main__": run_test_semcog(multiprocess=False) run_test_semcog(multiprocess=True) + run_test_semcog(multiprocess=False, use_explicit_error_terms=True) + run_test_semcog(multiprocess=True, use_explicit_error_terms=True) From 720998556bdc8e80908b51b3adf0d4d6d2bd6150 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 3 Apr 2026 08:41:52 +1000 Subject: [PATCH 186/298] lint --- activitysim/abm/models/location_choice.py | 3 +-- activitysim/examples/production_semcog/test/test_semcog.py | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 645912e2af..7c8ef16db8 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -15,10 +15,10 @@ TourLocationComponentSettings, TourModeComponentSettings, ) +from activitysim.core.exceptions import DuplicateWorkflowTableError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.util import reindex -from activitysim.core.exceptions import DuplicateWorkflowTableError """ The school/workplace location model predicts the zones in which various people will @@ -1042,7 +1042,6 @@ def iterate_location_choice( ) state.get_rn_generator().reset_offsets_for_step(state.current_model_name) - choices_df_, save_sample_df = run_location_choice( state, persons_merged_df_, diff --git a/activitysim/examples/production_semcog/test/test_semcog.py b/activitysim/examples/production_semcog/test/test_semcog.py index ebf8ba4522..8b77a4e3a4 100644 --- a/activitysim/examples/production_semcog/test/test_semcog.py +++ b/activitysim/examples/production_semcog/test/test_semcog.py @@ -21,7 +21,10 @@ def test_path(dirname): def regress(use_explicit_error_terms=False): regress_trips_df = pd.read_csv( - test_path(f"regress/final{'_eet' if use_explicit_error_terms else ''}_trips.csv"), dtype={"depart": int} + test_path( + f"regress/final{'_eet' if use_explicit_error_terms else ''}_trips.csv" + ), + dtype={"depart": int}, ) final_trips_df = pd.read_csv( test_path("output/final_trips.csv"), dtype={"depart": int} From 22a4f6277d046920eb22efcd040ed74d7889552c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 3 Apr 2026 08:59:54 +1000 Subject: [PATCH 187/298] disable test unitl further investigation --- .../examples/production_semcog/test/test_semcog.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/activitysim/examples/production_semcog/test/test_semcog.py b/activitysim/examples/production_semcog/test/test_semcog.py index 8b77a4e3a4..c8d7bcb780 100644 --- a/activitysim/examples/production_semcog/test/test_semcog.py +++ b/activitysim/examples/production_semcog/test/test_semcog.py @@ -99,12 +99,14 @@ def test_semcog_eet(): run_test_semcog(multiprocess=False, use_explicit_error_terms=True) -def test_semcog_mp_eet(): - run_test_semcog(multiprocess=True, use_explicit_error_terms=True) +# TODO: currently running into problems with escort trips that park at +# university. Need to check extensions. +# def test_semcog_mp_eet(): +# run_test_semcog(multiprocess=True, use_explicit_error_terms=True) if __name__ == "__main__": run_test_semcog(multiprocess=False) run_test_semcog(multiprocess=True) run_test_semcog(multiprocess=False, use_explicit_error_terms=True) - run_test_semcog(multiprocess=True, use_explicit_error_terms=True) + # run_test_semcog(multiprocess=True, use_explicit_error_terms=True) From 220c5cfe50c0cb871017d1662d9a1e9b15e3f4c8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 3 Apr 2026 10:41:15 +1000 Subject: [PATCH 188/298] mp rng reset, disabling one new test for no --- activitysim/abm/models/location_choice.py | 3 ++- activitysim/core/random.py | 24 +++++++++++++++---- .../production_semcog/test/test_semcog.py | 5 ++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 7c8ef16db8..47f7ec2a0d 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -1040,7 +1040,8 @@ def iterate_location_choice( logger.debug( f"{trace_label} resetting random number generator offsets for iteration {iteration}" ) - state.get_rn_generator().reset_offsets_for_step(state.current_model_name) + # handle MP by only resetting offsets for all persons in this process. + state.get_rn_generator().reset_offsets_for_df(persons_merged_df) choices_df_, save_sample_df = run_location_choice( state, diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 5b17097c4f..ea42b24118 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -9,8 +9,8 @@ import numpy as np import pandas as pd -from activitysim.core.util import reindex from activitysim.core.exceptions import DuplicateLoadableObjectError, TableIndexError +from activitysim.core.util import reindex from .tracing import print_elapsed_time @@ -445,11 +445,9 @@ def get_channel_for_df(self, df): raise TableIndexError("No channel with index name '%s'" % df.index.name) return self.channels[channel_name] - # step handling - def reset_offsets_for_step(self, step_name): """ - Reset offsets for all channels for a new step + Reset offsets for all channels for a step Parameters ---------- @@ -462,6 +460,24 @@ def reset_offsets_for_step(self, step_name): for c in self.channels: self.channels[c].row_states["offset"] = 0 + def reset_offsets_for_df(self, df): + """ + Reset offsets for all choosers in df if the channel for a step + + Parameters + ---------- + step_name : str + pipeline step name for this step + df : pandas.DataFrame + df with index name and values corresponding to a registered channel + """ + channel = self.get_channel_for_df(df) + channel.row_states.loc[df.index, "offset"] = 0 + logger.info( + f"RNG: resetting random number generator offsets for channel '{channel.channel_name}' for {len(df)} rows" + + f" with index name '{df.index.name}'. Total lenght df: {len(channel.row_states)}" + ) + def begin_step(self, step_name): """ Register that the pipeline has entered a new step and that global and channel streams diff --git a/activitysim/examples/production_semcog/test/test_semcog.py b/activitysim/examples/production_semcog/test/test_semcog.py index c8d7bcb780..1e058c2719 100644 --- a/activitysim/examples/production_semcog/test/test_semcog.py +++ b/activitysim/examples/production_semcog/test/test_semcog.py @@ -99,8 +99,9 @@ def test_semcog_eet(): run_test_semcog(multiprocess=False, use_explicit_error_terms=True) -# TODO: currently running into problems with escort trips that park at -# university. Need to check extensions. +# TODO: currently running into problems some trips, looks like +# trip_purpose_and_destination, might need to look into resetting +# RNGs there. Leaving this test disabled for now. # def test_semcog_mp_eet(): # run_test_semcog(multiprocess=True, use_explicit_error_terms=True) From d50ad3c1a7d66ee515b00ba35ab89b258be5cfc4 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 3 Apr 2026 16:41:33 +1000 Subject: [PATCH 189/298] semcog test with different seed for eet mp to work around non-determinism in trip_scheduling (probabilistic, no EET branch) --- .../test/configs_eet/settings.yaml | 2 + .../test/regress/final_eet_trips.csv | 319 +++++++----------- .../production_semcog/test/test_semcog.py | 9 +- 3 files changed, 120 insertions(+), 210 deletions(-) diff --git a/activitysim/examples/production_semcog/test/configs_eet/settings.yaml b/activitysim/examples/production_semcog/test/configs_eet/settings.yaml index 08c06d702e..dcff83f5a1 100644 --- a/activitysim/examples/production_semcog/test/configs_eet/settings.yaml +++ b/activitysim/examples/production_semcog/test/configs_eet/settings.yaml @@ -1,3 +1,5 @@ inherit_settings: True use_explicit_error_terms: True + +rng_base_seed: 42 diff --git a/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv index 5c0b578235..cc98fe5d69 100644 --- a/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv +++ b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv @@ -1,205 +1,116 @@ "person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","original_school_zone_id","parked_at_university","depart","tour_includes_parking","trip_id_pre_parking","trip_mode","mode_choice_logsum","trip_id" -2632656,1066353,"othdiscr",1,true,1,22812,22688,107938911,"othdiscr",,,false,29,1,863511289,"SHARED2",-0.8488473021059283,1727022577 -2632656,1066353,"othdiscr",1,false,4,22795,22812,107938911,"parking",,,false,32,1,863511293,"SHARED2",-0.4222882135487892,1727022585 -2632656,1066353,"othdiscr",2,false,4,22767,22795,107938911,"shopping",8.2354996454247,,true,32,1,863511293,"WALK",2.1502572806761995,1727022586 -2632656,1066353,"othdiscr",3,false,4,22795,22767,107938911,"parking",,,true,33,1,863511294,"WALK",4.3546491508875285,1727022587 -2632656,1066353,"othdiscr",4,false,4,22688,22795,107938911,"home",,,false,33,1,863511294,"SHARED2",-0.7598314660410206,1727022588 -2632656,1066353,"work",1,true,1,22754,22688,107938935,"work",,,false,10,0,863511481,"DRIVEALONE",-0.49288893792864136,1727022961 -2632656,1066353,"work",1,false,1,22688,22754,107938935,"home",,,false,28,0,863511485,"DRIVEALONE",-0.524270408328608,1727022969 -2632657,1066353,"othdiscr",1,true,1,22688,22688,107938962,"othdiscr",,,false,28,0,863511697,"WALK",0.3324082937283966,1727023393 -2632657,1066353,"othdiscr",1,false,1,22688,22688,107938962,"home",,,false,38,0,863511701,"WALK",0.3324082937283966,1727023401 -2632657,1066353,"othdiscr",1,true,1,22688,22688,107938963,"othdiscr",,,false,10,0,863511705,"WALK",0.3324082937283966,1727023409 -2632657,1066353,"othdiscr",1,false,1,22688,22688,107938963,"home",,,false,10,0,863511709,"WALK",0.3324082937283966,1727023417 -2632657,1066353,"school",1,true,1,22694,22688,107938968,"school",,,false,10,0,863511745,"SCHOOLBUS",-1.3476633093405816,1727023489 -2632657,1066353,"school",1,false,2,22676,22694,107938968,"eatout",-23.434159325514656,,false,27,0,863511749,"WALK",-9.200009261635815,1727023497 -2632657,1066353,"school",2,false,2,22688,22676,107938968,"home",,,false,27,0,863511750,"WALK",-9.038579100395838,1727023498 -2632659,1066353,"othdiscr",1,true,2,22804,22688,107939044,"parking",,,false,17,1,863512353,"DRIVEALONE",-0.8467894486224761,1727024705 -2632659,1066353,"othdiscr",2,true,2,22802,22804,107939044,"othdiscr",,,true,17,1,863512353,"WALK",8.06152871202933,1727024706 -2632659,1066353,"othdiscr",1,false,2,22804,22802,107939044,"parking",,,true,19,1,863512357,"WALK",8.06152871202933,1727024713 -2632659,1066353,"othdiscr",2,false,2,22688,22804,107939044,"home",,,false,19,1,863512357,"DRIVEALONE",-0.8411471912616634,1727024714 -2632659,1066353,"shopping",1,true,2,22688,22688,107939052,"othmaint",11.802188841934507,,false,12,0,863512417,"SHARED2",0.45240909180099215,1727024833 -2632659,1066353,"shopping",2,true,2,22683,22688,107939052,"shopping",,,false,13,0,863512418,"SHARED2",0.2405447885277471,1727024834 -2632659,1066353,"shopping",1,false,1,22688,22683,107939052,"home",,,false,13,0,863512421,"SHARED2",0.24080195215313727,1727024841 -2632738,1066386,"eatout",1,true,1,22767,22688,107942264,"eatout",,,false,27,0,863538113,"BIKE",-0.6093298604450466,1727076225 -2632738,1066386,"eatout",1,false,1,22688,22767,107942264,"home",,,false,32,0,863538117,"BIKE",-0.6080172231452846,1727076233 -2632738,1066386,"school",1,true,1,22716,22688,107942289,"school",,,false,10,0,863538313,"SCHOOLBUS",-1.3229057306562648,1727076625 -2632738,1066386,"school",1,false,3,22716,22716,107942289,"escort",-22.29714629889315,,false,25,0,863538317,"WALK",-8.708489622905788,1727076633 -2632738,1066386,"school",2,false,3,22682,22716,107942289,"othmaint",-22.914110705787074,,false,25,0,863538318,"SHARED3",-9.231759948709062,1727076634 -2632738,1066386,"school",3,false,3,22688,22682,107942289,"home",,,false,25,0,863538319,"SHARED3",-9.193961566181608,1727076635 -2632739,1066386,"othmaint",1,true,1,22678,22688,107942327,"othmaint",,,false,12,0,863538617,"DRIVEALONE",0.05480173136111777,1727077233 -2632739,1066386,"othmaint",1,false,1,22688,22678,107942327,"home",,,false,13,0,863538621,"DRIVEALONE",0.05478595698539101,1727077241 -2632739,1066386,"shopping",1,true,1,22689,22688,107942332,"shopping",,,false,33,0,863538657,"DRIVEALONE",0.4655442011339064,1727077313 -2632739,1066386,"shopping",1,false,1,22688,22689,107942332,"home",,,false,35,0,863538661,"SHARED3",0.4655442011339064,1727077321 -2640879,1069967,"shopping",1,true,1,22676,22676,108276072,"shopping",,,false,26,0,866208577,"WALK",0.17161148764545403,1732417153 -2640879,1069967,"shopping",1,false,1,22676,22676,108276072,"home",,,false,27,0,866208581,"DRIVEALONE",0.17161148764545403,1732417161 -2640879,1069967,"work",1,true,2,22811,22676,108276078,"parking",,,false,8,1,866208625,"DRIVEALONE",-0.5454033134073947,1732417249 -2640879,1069967,"work",2,true,2,22811,22811,108276078,"work",,,true,8,1,866208625,"WALK",2.9955771616115108,1732417250 -2640879,1069967,"work",1,false,2,22811,22811,108276078,"parking",,,true,23,1,866208629,"WALK",2.995584220814057,1732417257 -2640879,1069967,"work",2,false,2,22676,22811,108276078,"home",,,false,23,1,866208629,"DRIVEALONE",-0.4305990185449319,1732417258 -2640880,1069967,"othdiscr",1,true,1,22743,22676,108276105,"othdiscr",,,false,13,0,866208841,"DRIVEALONE",-0.8097498243611078,1732417681 -2640880,1069967,"othdiscr",1,false,1,22676,22743,108276105,"home",,,false,16,0,866208845,"DRIVEALONE",-0.7246524757544739,1732417689 -2645904,1072088,"social",1,true,2,22737,22711,108482100,"othmaint",7.814738943671942,,false,26,0,867856801,"DRIVEALONE",-1.4099478726631944,1735713601 -2645904,1072088,"social",2,true,2,22758,22737,108482100,"social",,,false,26,0,867856802,"DRIVEALONE",-0.5360854177766818,1735713602 -2645904,1072088,"social",1,false,1,22711,22758,108482100,"home",,,false,32,0,867856805,"DRIVEALONE",-0.7180224259537248,1735713609 -2645905,1072088,"othmaint",1,true,2,22795,22711,108482133,"parking",,,false,27,1,867857065,"DRIVEALONE",-0.8342963785932982,1735714129 -2645905,1072088,"othmaint",2,true,2,22808,22795,108482133,"othmaint",,,true,27,1,867857065,"WALK",4.143203548756641,1735714130 -2645905,1072088,"othmaint",1,false,2,22795,22808,108482133,"parking",,,true,31,1,867857069,"WALK",4.065724479690421,1735714137 -2645905,1072088,"othmaint",2,false,2,22711,22795,108482133,"home",,,false,31,1,867857069,"DRIVEALONE",-0.8413183337505313,1735714138 -2645905,1072088,"social",1,true,1,22714,22711,108482141,"social",,,false,33,0,867857129,"DRIVEALONE",-0.04994357209535508,1735714257 -2645905,1072088,"social",1,false,1,22711,22714,108482141,"home",,,false,34,0,867857133,"DRIVEALONE",-0.04994356771528009,1735714265 -2645906,1072088,"othmaint",1,true,1,22683,22711,108482174,"othmaint",,,false,19,0,867857393,"DRIVEALONE",-0.13092777228566677,1735714785 -2645906,1072088,"othmaint",1,false,4,22688,22683,108482174,"shopping",9.877646617096774,,false,29,0,867857397,"DRIVEALONE",0.021987670341255844,1735714793 -2645906,1072088,"othmaint",2,false,4,22683,22688,108482174,"shopping",9.99474165609772,,false,29,0,867857398,"WALK",0.02958108522301107,1735714794 -2645906,1072088,"othmaint",3,false,4,22711,22683,108482174,"shopping",9.615674159424772,,false,29,0,867857399,"DRIVEALONE",-0.13506201969852885,1735714795 -2645906,1072088,"othmaint",4,false,4,22711,22711,108482174,"home",,,false,29,0,867857400,"WALK",0.15219476711813038,1735714796 -2645906,1072088,"shopping",1,true,1,22745,22711,108482179,"shopping",,,false,16,0,867857433,"DRIVEALONE",-1.8275616246674673,1735714865 -2645906,1072088,"shopping",1,false,1,22711,22745,108482179,"home",,,false,19,0,867857437,"DRIVEALONE",-1.2985090645679782,1735714873 -2645907,1072088,"eatout",1,true,2,22766,22711,108482193,"parking",,,false,34,1,867857545,"DRIVEALONE",-0.9960586671404674,1735715089 -2645907,1072088,"eatout",2,true,2,22767,22766,108482193,"eatout",,,true,34,1,867857545,"WALK",6.210026630752176,1735715090 -2645907,1072088,"eatout",1,false,2,22766,22767,108482193,"parking",,,true,36,1,867857549,"WALK",6.210032999225622,1735715097 -2645907,1072088,"eatout",2,false,2,22711,22766,108482193,"home",,,false,36,1,867857549,"DRIVEALONE",-0.9957395146055389,1735715098 -2645907,1072088,"school",1,true,1,22716,22711,108482218,"school",,,false,11,0,867857745,"SCHOOLBUS",-1.320618029716206,1735715489 -2645907,1072088,"school",1,false,4,22724,22716,108482218,"social",-22.549809261930204,,false,26,0,867857749,"SHARED2",-9.188557941135578,1735715497 -2645907,1072088,"school",2,false,4,22712,22724,108482218,"othdiscr",-22.277427876366566,,false,26,0,867857750,"SHARED2",-9.32548153595726,1735715498 -2645907,1072088,"school",3,false,4,22711,22712,108482218,"escort",-21.90946101819234,,false,26,0,867857751,"WALK",-8.16070608833084,1735715499 -2645907,1072088,"school",4,false,4,22711,22711,108482218,"home",,,false,26,0,867857752,"SHARED2",-8.660375528783023,1735715500 -2671333,1083128,"school",1,true,2,22657,22637,109524684,"othmaint",-23.99886783982251,,false,9,0,876197473,"SHARED2",-9.259145285147683,1752394945 -2671333,1083128,"school",2,true,2,22640,22657,109524684,"school",,,false,9,0,876197474,"SCHOOLBUS",-1.312859221283506,1752394946 -2671333,1083128,"school",1,false,1,22637,22640,109524684,"home",,,false,29,0,876197477,"SHARED3",-9.608846466303847,1752394953 -2853513,1152948,"othdiscr",1,true,1,22766,22770,116994058,"othdiscr",,,false,14,0,935952465,"WALK",-0.9011100567629171,1871904929 -2853513,1152948,"othdiscr",1,false,1,22770,22766,116994058,"home",,,false,18,0,935952469,"WALK",-0.9051750552987663,1871904937 -2853513,1152948,"othdiscr",1,true,4,22767,22770,116994059,"eatout",14.667019798235065,,false,21,0,935952473,"WALK",2.2663942192594435,1871904945 -2853513,1152948,"othdiscr",2,true,4,22770,22767,116994059,"othmaint",15.154344404160929,,false,21,0,935952474,"WALK",0.9306047427247703,1871904946 -2853513,1152948,"othdiscr",3,true,4,22771,22770,116994059,"eatout",14.74628023877142,,false,21,0,935952475,"WALK",1.5044226646559185,1871904947 -2853513,1152948,"othdiscr",4,true,4,22773,22771,116994059,"othdiscr",,,false,21,0,935952476,"WALK",0.19292867000012684,1871904948 -2853513,1152948,"othdiscr",1,false,2,22767,22773,116994059,"escort",14.378785798118802,,false,21,0,935952477,"WALK",-0.05516003714691903,1871904953 -2853513,1152948,"othdiscr",2,false,2,22770,22767,116994059,"home",,,false,21,0,935952478,"WALK",2.2663942192594435,1871904954 -2853513,1152948,"othdiscr",1,true,1,22771,22770,116994060,"othdiscr",,,false,28,0,935952481,"WALK",1.5044226646559185,1871904961 -2853513,1152948,"othdiscr",1,false,1,22770,22771,116994060,"home",,,false,29,0,935952485,"WALK",1.5044226646559185,1871904969 -2853513,1152948,"work",1,true,2,22771,22770,116994072,"escort",27.34513603248586,,false,30,0,935952577,"WALK",4.823175204067197,1871905153 -2853513,1152948,"work",2,true,2,22773,22771,116994072,"work",,,false,32,0,935952578,"WALK",3.945458382874527,1871905154 -2853513,1152948,"work",1,false,1,22770,22773,116994072,"home",,,false,45,0,935952581,"WALK",4.0042155675325635,1871905161 -2856204,1154357,"othdiscr",1,true,1,22770,22815,117104389,"othdiscr",,,false,29,0,936835113,"WALK",-0.043175037831942764,1873670225 -2856204,1154357,"othdiscr",1,false,1,22815,22770,117104389,"home",,,false,29,0,936835117,"WALK",0.10104097189929005,1873670233 -2856660,1154635,"univ",1,true,1,22766,22815,117123091,"univ",,,false,13,0,936984729,"WALK_LOC",2.23181471014434,1873969457 -2856660,1154635,"univ",1,false,1,22815,22766,117123091,"home",,,false,27,0,936984733,"WALK",2.2179223176535414,1873969465 -2856661,1154635,"univ",1,true,1,22766,22815,117123132,"univ",,,false,14,0,936985057,"WALK",2.23181471014434,1873970113 -2856661,1154635,"univ",1,false,1,22815,22766,117123132,"home",,,false,26,0,936985061,"WALK",2.218652692553078,1873970121 -2861950,1156849,"univ",1,true,1,22766,22801,117339981,"univ",,,false,17,0,938719849,"WALK_LOC",2.3790349408815556,1877439697 -2861950,1156849,"univ",1,false,2,22764,22766,117339981,"univ",10.335069017511323,,false,17,0,938719853,"WALK_LOC",2.6617812905452896,1877439705 -2861950,1156849,"univ",2,false,2,22801,22764,117339981,"home",,,false,18,0,938719854,"WALK_LOC",2.35700318172487,1877439706 -2861950,1156849,"shopping",1,true,2,22795,22801,117339983,"parking",,,false,12,1,938719865,"WALK",-0.21895344461922386,1877439729 -2861950,1156849,"shopping",2,true,2,22767,22795,117339983,"shopping",,,true,12,1,938719865,"WALK",3.7909702907421834,1877439730 -2861950,1156849,"shopping",1,false,5,22795,22767,117339983,"parking",,,true,13,1,938719869,"WALK",3.4552717476523997,1877439737 -2861950,1156849,"shopping",2,false,5,22770,22795,117339983,"eatout",13.591054539597575,,false,13,1,938719869,"DRIVEALONE",-0.571477727779577,1877439738 -2861950,1156849,"shopping",3,false,5,22770,22770,117339983,"eatout",11.67129001032396,,false,13,1,938719870,"WALK",0.13094348045186022,1877439739 -2861950,1156849,"shopping",4,false,5,22795,22770,117339983,"parking",,,false,13,1,938719871,"DRIVEALONE",-0.34397236094497297,1877439740 -2861950,1156849,"shopping",5,false,5,22801,22795,117339983,"home",,,true,13,1,938719871,"WALK",1.6057419140820288,1877439741 -2861951,1156849,"escort",1,true,1,22639,22801,117340000,"escort",,,false,34,1,938720001,"SHARED3",-0.4216870410803827,1877440001 -2861951,1156849,"escort",1,false,2,22806,22639,117340000,"parking",,,false,36,1,938720005,"SHARED2",-0.6086337356771375,1877440009 -2861951,1156849,"escort",2,false,2,22801,22806,117340000,"home",,,true,36,1,938720005,"WALK",2.8917510947059446,1877440010 -2861951,1156849,"othdiscr",1,true,1,22783,22801,117340016,"othdiscr",,,false,10,0,938720129,"WALK",0.34049307850458627,1877440257 -2861951,1156849,"othdiscr",1,false,1,22801,22783,117340016,"home",,,false,11,0,938720133,"WALK",0.34049307850458627,1877440265 -2861951,1156849,"univ",1,true,1,22766,22801,117340022,"univ",,,false,11,0,938720177,"WALK",2.376796810859204,1877440353 -2861951,1156849,"univ",1,false,1,22801,22766,117340022,"home",,,false,33,0,938720181,"WALK",2.342231383429434,1877440361 -2861952,1156849,"univ",1,true,2,22802,22801,117340063,"work",14.623747590402552,,false,16,0,938720505,"WALK",2.3673504725748127,1877441009 -2861952,1156849,"univ",2,true,2,22809,22802,117340063,"univ",,,false,17,0,938720506,"WALK",-0.3649612631284906,1877441010 -2861952,1156849,"univ",1,false,1,22801,22809,117340063,"home",,,false,20,0,938720509,"WALK",-0.656569218265208,1877441017 -2862055,1156884,"eatout",1,true,1,22802,22804,117344268,"eatout",,,false,33,0,938754145,"WALK",2.709890128233274,1877508289 -2862055,1156884,"eatout",1,false,2,22806,22802,117344268,"shopping",17.35261269322678,,false,37,0,938754149,"WALK",2.7091008864601434,1877508297 -2862055,1156884,"eatout",2,false,2,22804,22806,117344268,"home",,,false,37,0,938754150,"WALK",0.73177661169581,1877508298 -2862055,1156884,"univ",1,true,1,22809,22804,117344286,"univ",,,false,12,0,938754289,"WALK",-0.44242952311569,1877508577 -2862055,1156884,"univ",1,false,1,22804,22809,117344286,"home",,,false,33,0,938754293,"WALK",-0.44242952311569,1877508585 -2862055,1156884,"social",1,true,3,22807,22804,117344291,"eatout",12.084243679384295,,false,37,0,938754329,"WALK",1.534047590758811,1877508657 -2862055,1156884,"social",2,true,3,22770,22807,117344291,"othdiscr",12.902879188828422,,false,38,0,938754330,"WALK",0.4553060051673671,1877508658 -2862055,1156884,"social",3,true,3,22794,22770,117344291,"social",,,false,38,0,938754331,"WALK",-0.4377870962366495,1877508659 -2862055,1156884,"social",1,false,1,22804,22794,117344291,"home",,,false,43,0,938754333,"WALK",-0.18058190179032135,1877508665 -2862056,1156884,"univ",1,true,1,22809,22804,117344327,"univ",,,false,9,0,938754617,"WALK",-0.44242952311569,1877509233 -2862056,1156884,"univ",1,false,3,22807,22809,117344327,"eatout",13.728406618828826,,false,26,0,938754621,"WALK",-0.22722993342809517,1877509241 -2862056,1156884,"univ",2,false,3,22767,22807,117344327,"shopping",17.17708856852041,,false,26,0,938754622,"WALK",2.329516762852266,1877509242 -2862056,1156884,"univ",3,false,3,22804,22767,117344327,"home",,,false,26,0,938754623,"WALK",1.8938019500606744,1877509243 -2862056,1156884,"shopping",1,true,1,22738,22804,117344329,"shopping",,,false,29,1,938754633,"WALK",-0.40920601532640766,1877509265 -2862056,1156884,"shopping",1,false,2,22766,22738,117344329,"parking",,,false,31,1,938754637,"DRIVEALONE",-0.4546401594188224,1877509273 -2862056,1156884,"shopping",2,false,2,22766,22766,117344329,"parking",,,true,31,1,938754637,"WALK",2.4879495513337813,1877509274 -2862057,1156884,"univ",1,true,3,22807,22804,117344368,"social",14.134449334392166,,false,8,0,938754945,"WALK",2.3010713861382164,1877509889 -2862057,1156884,"univ",2,true,3,22767,22807,117344368,"work",15.487934097337227,,false,16,0,938754946,"WALK",2.1918016019844577,1877509890 -2862057,1156884,"univ",3,true,3,22809,22767,117344368,"univ",,,false,22,0,938754947,"WALK",-0.50518420043921,1877509891 -2862057,1156884,"univ",1,false,1,22804,22809,117344368,"home",,,false,32,0,938754949,"WALK",-0.44242952311569,1877509897 -2863920,1157823,"univ",1,true,1,22766,22812,117420751,"univ",,,false,15,0,939366009,"WALK_LOC",2.0570235489392443,1878732017 -2863920,1157823,"univ",1,false,2,22767,22766,117420751,"eatout",23.659417692376945,,false,25,0,939366013,"WALK",2.494484873165744,1878732025 -2863920,1157823,"univ",2,false,2,22812,22767,117420751,"home",,,false,26,0,939366014,"WALK",4.953924530435282,1878732026 -2863921,1157823,"univ",1,true,1,22809,22812,117420792,"univ",,,false,13,0,939366337,"WALK",2.486268830878343,1878732673 -2863921,1157823,"univ",1,false,4,22809,22809,117420792,"univ",10.289883977911662,22809,false,14,0,939366341,"WALK",3.0064499347700666,1878732681 -2863921,1157823,"univ",2,false,4,22808,22809,117420792,"othdiscr",13.054363997677639,,false,14,0,939366342,"WALK",2.500770328794477,1878732682 -2863921,1157823,"univ",3,false,4,22766,22808,117420792,"univ",11.668617099548605,22766,false,14,0,939366343,"WALK",3.854292572099684,1878732683 -2863921,1157823,"univ",4,false,4,22812,22766,117420792,"home",,,false,14,0,939366344,"WALK",2.0522280506197337,1878732684 -2863922,1157823,"univ",1,true,1,22809,22812,117420833,"univ",,,false,7,0,939366665,"BIKE",-0.464473446954635,1878733329 -2863922,1157823,"univ",1,false,2,22809,22809,117420833,"univ",10.643972757226907,22809,false,23,0,939366669,"BIKE",-0.005181884649800808,1878733337 -2863922,1157823,"univ",2,false,2,22812,22809,117420833,"home",,,false,24,0,939366670,"BIKE",-0.464473446954635,1878733338 -2863922,1157823,"shopping",1,true,2,22806,22812,117420835,"parking",,,false,28,1,939366681,"WALK",-0.18297922233662095,1878733361 -2863922,1157823,"shopping",2,true,2,22800,22806,117420835,"shopping",,,true,28,1,939366681,"WALK",2.477773445267558,1878733362 -2863922,1157823,"shopping",1,false,3,22767,22800,117420835,"shopping",10.837169270242605,,true,35,1,939366685,"WALK",2.0313681136538952,1878733369 -2863922,1157823,"shopping",2,false,3,22806,22767,117420835,"parking",,,true,36,1,939366686,"WALK",4.831708055552031,1878733370 -2863922,1157823,"shopping",3,false,3,22812,22806,117420835,"home",,,false,36,1,939366686,"SHARED2",0.2332508156022282,1878733371 -2866914,1159236,"work",1,true,2,22774,22797,117543513,"parking",,,false,11,1,940348105,"WALK",0.3696369443918809,1880696209 -2866914,1159236,"work",2,true,2,22774,22774,117543513,"work",,,true,11,1,940348105,"WALK",3.0380641909412422,1880696210 -2866914,1159236,"work",1,false,1,22797,22774,117543513,"home",,,true,32,1,940348109,"WALK",1.6315258077049342,1880696217 -2866915,1159236,"work",1,true,1,22802,22797,117543554,"work",,,false,21,0,940348433,"WALK",1.4889378816413315,1880696865 -2866915,1159236,"work",1,false,1,22797,22802,117543554,"home",,,false,43,0,940348437,"WALK",1.4889378816413315,1880696873 -2866916,1159236,"atwork",1,true,1,22778,22760,117543579,"atwork",,,false,15,0,940348633,"WALK",-0.6017055929110162,1880697265 -2866916,1159236,"atwork",1,false,2,22771,22778,117543579,"escort",13.958619580608657,,false,18,0,940348637,"WALK",0.0015080519389326266,1880697273 -2866916,1159236,"atwork",2,false,2,22760,22771,117543579,"work",,,false,18,0,940348638,"WALK",1.131494654421563,1880697274 -2866916,1159236,"othdiscr",1,true,1,22787,22797,117543581,"othdiscr",,,false,29,0,940348649,"WALK",-0.7205273617337421,1880697297 -2866916,1159236,"othdiscr",1,false,1,22797,22787,117543581,"home",,,false,36,0,940348653,"WALK",-0.7205273617337421,1880697305 -2866916,1159236,"othmaint",1,true,4,22796,22797,117543584,"parking",,,false,28,1,940348673,"DRIVEALONE",-0.0203043486563632,1880697345 -2866916,1159236,"othmaint",2,true,4,22807,22796,117543584,"escort",12.200555556836377,,true,28,1,940348673,"WALK",5.136872721261722,1880697346 -2866916,1159236,"othmaint",3,true,4,22796,22807,117543584,"parking",,,true,29,1,940348674,"WALK",3.14893876731007,1880697347 -2866916,1159236,"othmaint",4,true,4,22770,22796,117543584,"othmaint",,,false,29,1,940348674,"DRIVEALONE",-0.15170981809506287,1880697348 -2866916,1159236,"othmaint",1,false,2,22796,22770,117543584,"parking",,,false,29,1,940348677,"DRIVEALONE",-0.1584988684239379,1880697353 -2866916,1159236,"othmaint",2,false,2,22796,22796,117543584,"parking",,,true,29,1,940348677,"WALK",3.903741766016903,1880697354 -2866916,1159236,"work",1,true,1,22760,22797,117543595,"work",,,false,6,0,940348761,"BIKE",1.411922122583906,1880697521 -2866916,1159236,"work",1,false,2,22767,22760,117543595,"escort",16.511766164218447,,false,23,0,940348765,"BIKE",1.8250316638262625,1880697529 -2866916,1159236,"work",2,false,2,22797,22767,117543595,"home",,,false,25,0,940348766,"BIKE",1.3197235435334995,1880697530 -2870654,1160939,"othdiscr",1,true,1,22747,22740,117696839,"othdiscr",,,false,31,0,941574713,"WALK",7.372170552771186,1883149425 -2870654,1160939,"othdiscr",1,false,1,22740,22747,117696839,"home",,,false,33,0,941574717,"WALK",7.372204141413259,1883149433 -2870654,1160939,"work",1,true,2,22761,22740,117696853,"parking",,,false,7,1,941574825,"DRIVEALONE",-0.16109255990094318,1883149649 -2870654,1160939,"work",2,true,2,22761,22761,117696853,"work",,,true,7,1,941574825,"WALK",3.799348055144246,1883149650 -2870654,1160939,"work",1,false,2,22761,22761,117696853,"parking",,,true,15,1,941574829,"WALK",3.7993411043161562,1883149657 -2870654,1160939,"work",2,false,2,22740,22761,117696853,"home",,,false,15,1,941574829,"SHARED2",0.14204552305427365,1883149658 -2870655,1160939,"work",1,true,1,22770,22740,117696894,"work",,,false,10,0,941575153,"DRIVEALONE",-0.4875582876915185,1883150305 -2870655,1160939,"work",1,false,1,22740,22770,117696894,"home",,,false,20,0,941575157,"WALK",-0.7149551826154896,1883150313 -2870656,1160939,"univ",1,true,3,22767,22740,117696927,"shopping",23.984363940879124,,false,11,0,941575417,"WALK",5.126445987379017,1883150833 -2870656,1160939,"univ",2,true,3,22767,22767,117696927,"eatout",25.472982562888472,,false,11,0,941575418,"WALK",5.62841361203664,1883150834 -2870656,1160939,"univ",3,true,3,22764,22767,117696927,"univ",,,false,19,0,941575419,"WALK",2.817924028836093,1883150835 -2870656,1160939,"univ",1,false,1,22740,22764,117696927,"home",,,false,26,0,941575421,"WALK",2.4571829368719613,1883150841 -2870656,1160939,"univ",1,true,1,22764,22740,117696928,"univ",,,false,26,0,941575425,"WALK",2.439713266027429,1883150849 -2870656,1160939,"univ",1,false,1,22740,22764,117696928,"home",,,false,31,0,941575429,"WALK_LOC",2.461543319337894,1883150857 -2874270,1162627,"univ",1,true,1,22764,22758,117845101,"univ",,,false,17,0,942760809,"WALK",2.3261410771168642,1885521617 -2874270,1162627,"univ",1,false,2,22764,22764,117845101,"univ",10.700792773839831,,false,26,0,942760813,"WALK",3.004468589374795,1885521625 -2874270,1162627,"univ",2,false,2,22758,22764,117845101,"home",,,false,26,0,942760814,"WALK",2.3239328769783794,1885521626 -2874270,1162627,"shopping",1,true,4,22796,22758,117845103,"parking",,,false,34,1,942760825,"DRIVEALONE",-0.38848558560287977,1885521649 -2874270,1162627,"shopping",2,true,4,22806,22796,117845103,"othmaint",12.259167876768386,,true,34,1,942760825,"WALK",2.5227339389241807,1885521650 -2874270,1162627,"shopping",3,true,4,22767,22806,117845103,"eatout",11.734621106527097,,true,34,1,942760826,"WALK",4.4647310506607925,1885521651 -2874270,1162627,"shopping",4,true,4,22805,22767,117845103,"shopping",,,true,35,1,942760827,"WALK",2.5148845790149537,1885521652 -2874270,1162627,"shopping",1,false,2,22796,22805,117845103,"parking",,,true,35,1,942760829,"WALK",2.018454499589163,1885521657 -2874270,1162627,"shopping",2,false,2,22758,22796,117845103,"home",,,false,35,1,942760829,"DRIVEALONE",-0.49542727241373813,1885521658 -2874271,1162627,"univ",1,true,2,22767,22758,117845142,"escort",13.256155130715978,,false,18,0,942761137,"WALK",2.1084560857351926,1885522273 -2874271,1162627,"univ",2,true,2,22764,22767,117845142,"univ",,,false,18,0,942761138,"WALK",-0.5148347167335139,1885522274 -2874271,1162627,"univ",1,false,2,22767,22764,117845142,"social",13.116659549955148,,false,30,0,942761141,"WALK",-0.5148347167335139,1885522281 -2874271,1162627,"univ",2,false,2,22758,22767,117845142,"home",,,false,30,0,942761142,"WALK",2.1084560857351926,1885522282 -4724316,1944022,"eatout",1,true,1,22771,22765,193696962,"eatout",,,false,38,0,1549575697,"WALK",0.9192956769258102,3099151393 -4724316,1944022,"eatout",1,false,1,22765,22771,193696962,"home",,,false,39,0,1549575701,"WALK",0.9192956769258102,3099151401 -4724316,1944022,"univ",1,true,1,22809,22765,193696987,"univ",,,false,11,0,1549575897,"WALK_LOC",2.504097442012484,3099151793 -4724316,1944022,"univ",1,false,1,22765,22809,193696987,"home",,,false,32,0,1549575901,"WALK",2.486613658307754,3099151801 -4724701,1944407,"univ",1,true,1,22809,22808,193712772,"univ",,,false,11,0,1549702177,"WALK",2.491800843008931,3099404353 -4724701,1944407,"univ",1,false,2,22764,22809,193712772,"univ",10.963078293466896,,false,33,0,1549702181,"WALK",2.428175371397628,3099404361 -4724701,1944407,"univ",2,false,2,22808,22764,193712772,"home",,,false,33,0,1549702182,"WALK",2.4386307350012473,3099404362 -4724720,1944426,"eatout",1,true,1,22739,22806,193713526,"eatout",,,false,15,0,1549708209,"WALK",-0.5363640585021157,3099416417 -4724720,1944426,"eatout",1,false,1,22806,22739,193713526,"home",,,false,18,0,1549708213,"WALK",-0.5363640585021157,3099416425 -4727094,1946800,"shopping",1,true,1,22770,22808,193810887,"shopping",,,false,14,0,1550487097,"WALK",0.3756438367025996,3100974193 -4727094,1946800,"shopping",1,false,1,22808,22770,193810887,"home",,,false,16,0,1550487101,"WALK",0.3756438367025996,3100974201 -4728027,1947733,"univ",1,true,1,22809,22806,193849138,"univ",,,false,12,0,1550793105,"WALK",2.8698246758237826,3101586209 -4728027,1947733,"univ",1,false,4,22769,22809,193849138,"escort",25.420351843799057,,false,27,0,1550793109,"WALK",2.7798007800315285,3101586217 -4728027,1947733,"univ",2,false,4,22807,22769,193849138,"social",27.304467847997117,,false,27,0,1550793110,"WALK",3.248014813584962,3101586218 -4728027,1947733,"univ",3,false,4,22767,22807,193849138,"eatout",30.20403082477785,,false,27,0,1550793111,"WALK_LOC",5.6477996875760095,3101586219 -4728027,1947733,"univ",4,false,4,22806,22767,193849138,"home",,,false,27,0,1550793112,"WALK_LOC",5.333545575515567,3101586220 +2632461,1066212,"eatout",1,true,1,22688,22687,107930907,"eatout",,,false,24,0,863447257,"WALK",0.3324082937283966,1726894513 +2632461,1066212,"eatout",1,false,1,22687,22688,107930907,"home",,,false,32,0,863447261,"WALK",0.3324082937283966,1726894521 +2632461,1066212,"social",1,true,1,22676,22687,107930937,"social",,,false,38,0,863447497,"WALK",-0.372506247777352,1726894993 +2632461,1066212,"social",1,false,1,22687,22676,107930937,"home",,,false,38,0,863447501,"WALK",-0.372506247777352,1726895001 +2632461,1066212,"work",1,true,1,22770,22687,107930940,"work",,,false,11,0,863447521,"DRIVEALONE",-0.9006268476080008,1726895041 +2632461,1066212,"work",1,false,1,22687,22770,107930940,"home",,,false,23,0,863447525,"DRIVEALONE",-0.5528040173584109,1726895049 +2632746,1066390,"school",1,true,2,22684,22688,107942617,"shopping",10.301822957849977,,false,13,0,863540937,"SHARED3",0.08788155056513884,1727081873 +2632746,1066390,"school",2,true,2,22716,22684,107942617,"school",,,false,13,0,863540938,"SHARED3",0.21128282107010274,1727081874 +2632746,1066390,"school",1,false,1,22688,22716,107942617,"home",,,false,20,0,863540941,"SHARED3",-0.12094657865851986,1727081881 +2632746,1066390,"work",1,true,2,22798,22688,107942625,"parking",,,false,21,1,863541001,"DRIVEALONE",-1.0935617741756212,1727082001 +2632746,1066390,"work",2,true,2,22798,22798,107942625,"work",,,true,21,1,863541001,"WALK",2.688813549798029,1727082002 +2632746,1066390,"work",1,false,2,22798,22798,107942625,"parking",,,true,26,1,863541005,"WALK",2.6888134385754383,1727082009 +2632746,1066390,"work",2,false,2,22688,22798,107942625,"home",,,false,26,1,863541005,"DRIVEALONE",-1.285961232813202,1727082010 +2643231,1070862,"work",1,true,2,22767,22701,108372510,"parking",,,false,12,1,866980081,"DRIVEALONE",-2.254013060998411,1733960161 +2643231,1070862,"work",2,true,2,22767,22767,108372510,"work",,,true,12,1,866980081,"WALK",3.750337710238621,1733960162 +2643231,1070862,"work",1,false,2,22767,22767,108372510,"parking",,,true,27,1,866980085,"WALK",3.75033686292241,1733960169 +2643231,1070862,"work",2,false,2,22701,22767,108372510,"home",,,false,27,1,866980085,"DRIVEALONE",-1.0195938099395256,1733960170 +2851663,1151807,"work",1,true,2,22808,22768,116918222,"parking",,,false,8,1,935345777,"WALK",0.5794744566652396,1870691553 +2851663,1151807,"work",2,true,2,22808,22808,116918222,"work",,,true,8,1,935345777,"WALK",3.9202266680627016,1870691554 +2851663,1151807,"work",1,false,2,22808,22808,116918222,"parking",,,true,23,1,935345781,"WALK",3.9202264187221654,1870691561 +2851663,1151807,"work",2,false,2,22768,22808,116918222,"home",,,false,23,1,935345781,"WALK",0.5811901896672964,1870691562 +2851664,1151807,"atwork",1,true,1,22795,22795,116918247,"atwork",,,false,8,0,935345977,"WALK",0,1870691953 +2851664,1151807,"atwork",1,false,2,22807,22795,116918247,"eatout",11.697803529864785,,false,9,0,935345981,"WALK",-0.6403075075080801,1870691961 +2851664,1151807,"atwork",2,false,2,22795,22807,116918247,"work",,,false,9,0,935345982,"WALK",1.9742275881306344,1870691962 +2851664,1151807,"work",1,true,2,22795,22768,116918263,"parking",,,false,8,1,935346105,"DRIVEALONE",-0.1700734379058779,1870692209 +2851664,1151807,"work",2,true,2,22795,22795,116918263,"work",,,true,8,1,935346105,"WALK",2.014596847010505,1870692210 +2851664,1151807,"work",1,false,2,22795,22795,116918263,"parking",,,true,9,1,935346109,"WALK",2.014596847010505,1870692217 +2851664,1151807,"work",2,false,2,22768,22795,116918263,"home",,,false,9,1,935346109,"DRIVEALONE",-0.17669442402412502,1870692218 +2851664,1151807,"work",1,true,2,22795,22768,116918264,"parking",,,false,10,1,935346113,"SHARED2",0.18223026147932736,1870692225 +2851664,1151807,"work",2,true,2,22795,22795,116918264,"work",,,true,10,1,935346113,"WALK",3.0721786555313417,1870692226 +2851664,1151807,"work",1,false,3,22767,22795,116918264,"eatout",13.361606283751318,,true,12,1,935346117,"WALK",2.1699105206573512,1870692233 +2851664,1151807,"work",2,false,3,22795,22767,116918264,"parking",,,true,12,1,935346118,"WALK",3.660264542941122,1870692234 +2851664,1151807,"work",3,false,3,22768,22795,116918264,"home",,,false,12,1,935346118,"DRIVEALONE",0.19501777547255042,1870692235 +2851665,1151807,"school",1,true,1,22738,22768,116918296,"school",,,false,9,0,935346369,"WALK",-0.3380929737459932,1870692737 +2851665,1151807,"school",1,false,1,22768,22738,116918296,"home",,,false,25,0,935346373,"WALK",-0.3380929737459932,1870692745 +2851666,1151807,"school",1,true,1,22738,22768,116918337,"school",,,false,9,0,935346697,"WALK",-0.23394837977299351,1870693393 +2851666,1151807,"school",1,false,2,22768,22738,116918337,"eatout",12.976839556161908,,false,26,0,935346701,"WALK",-0.30724534671072457,1870693401 +2851666,1151807,"school",2,false,2,22768,22768,116918337,"home",,,false,26,0,935346702,"WALK",1.4569271228419698,1870693402 +2853258,1152693,"work",1,true,1,22808,22767,116983617,"work",,,false,20,0,935868937,"WALK",4.2361228435911125,1871737873 +2853258,1152693,"work",1,false,1,22767,22808,116983617,"home",,,false,42,0,935868941,"WALK",4.2355632459345705,1871737881 +2864033,1157863,"work",1,true,1,22766,22818,117425392,"work",,,false,22,0,939403137,"WALK",-0.5747999444276104,1878806273 +2864033,1157863,"work",1,false,3,22801,22766,117425392,"othmaint",11.425225674825322,,false,43,0,939403141,"WALK",-0.7024510798800492,1878806281 +2864033,1157863,"work",2,false,3,22802,22801,117425392,"othmaint",13.28624241505493,,false,43,0,939403142,"WALK",0.28664476657433274,1878806282 +2864033,1157863,"work",3,false,3,22818,22802,117425392,"home",,,false,44,0,939403143,"WALK",1.5286197350024198,1878806283 +2867650,1159450,"work",1,true,1,22740,22791,117573689,"work",,,false,5,0,940589513,"DRIVEALONE",-0.670801522478196,1881179025 +2867650,1159450,"work",1,false,1,22791,22740,117573689,"home",,,false,28,0,940589517,"SHARED2",0.03856943979091073,1881179033 +2867652,1159450,"school",1,true,1,22798,22791,117573763,"school",,,false,11,0,940590105,"WALK",-0.14197028764914804,1881180209 +2867652,1159450,"school",1,false,2,22807,22798,117573763,"escort",12.102989575726829,,false,26,0,940590109,"WALK",0.3099529390965043,1881180217 +2867652,1159450,"school",2,false,2,22791,22807,117573763,"home",,,false,27,0,940590110,"WALK",1.1921458680932129,1881180218 +2867653,1159450,"school",1,true,1,22716,22791,117573804,"school",,,false,9,0,940590433,"SHARED3",-0.7165798080815713,1881180865 +2867653,1159450,"school",1,false,1,22791,22716,117573804,"home",,,false,23,0,940590437,"SHARED3",-0.7056869394647015,1881180873 +2869308,1160345,"escort",1,true,4,22806,22788,117641637,"parking",,,false,37,1,941133097,"SHARED2",-0.35468797889700127,1882266193 +2869308,1160345,"escort",2,true,4,22761,22806,117641637,"escort",9.809199303175808,,true,37,1,941133097,"WALK",1.1693447862605972,1882266194 +2869308,1160345,"escort",3,true,4,22806,22761,117641637,"parking",,,true,38,1,941133098,"WALK",1.0527105195710942,1882266195 +2869308,1160345,"escort",4,true,4,22738,22806,117641637,"escort",,,false,38,1,941133098,"SHARED2",-0.7899590349500466,1882266196 +2869308,1160345,"escort",1,false,2,22762,22738,117641637,"escort",11.267844899645352,,false,39,1,941133101,"DRIVEALONE",-0.33121883758411125,1882266201 +2869308,1160345,"escort",2,false,2,22788,22762,117641637,"home",,,false,40,1,941133102,"SHARED2",-0.21686205931765942,1882266202 +2869308,1160345,"work",1,true,1,22769,22788,117641667,"work",,,false,11,1,941133337,"SHARED2",-0.24887791851324914,1882266673 +2869308,1160345,"work",1,false,6,22769,22769,117641667,"othmaint",11.968949912548455,,false,27,1,941133341,"SHARED3",-0.004404805067726633,1882266681 +2869308,1160345,"work",2,false,6,22761,22769,117641667,"parking",,,false,28,1,941133342,"WALK",-0.6678721152911544,1882266682 +2869308,1160345,"work",3,false,6,22767,22761,117641667,"shopping",10.633629340799134,,true,28,1,941133342,"WALK",3.0199993221581605,1882266683 +2869308,1160345,"work",4,false,6,22807,22767,117641667,"escort",13.512213256227986,,true,29,1,941133343,"WALK",4.2137726609909425,1882266684 +2869308,1160345,"work",5,false,6,22761,22807,117641667,"parking",,,true,30,1,941133344,"WALK",3.869947742844953,1882266685 +2869308,1160345,"work",6,false,6,22788,22761,117641667,"home",,,false,30,1,941133344,"SHARED3",-0.41885728895985064,1882266686 +2869309,1160345,"univ",1,true,2,22795,22788,117641700,"parking",,,false,13,1,941133601,"DRIVEALONE",-0.15235107523409816,1882267201 +2869309,1160345,"univ",2,true,2,22766,22795,117641700,"univ",,,true,13,1,941133601,"WALK_LOC",1.202786557349171,1882267202 +2869309,1160345,"univ",1,false,3,22766,22766,117641700,"othdiscr",12.456311079956105,,true,24,1,941133605,"WALK",2.0068506545834075,1882267209 +2869309,1160345,"univ",2,false,3,22795,22766,117641700,"parking",,,true,24,1,941133606,"WALK_LOC",1.142188272503556,1882267210 +2869309,1160345,"univ",3,false,3,22788,22795,117641700,"home",,,false,24,1,941133606,"DRIVEALONE",-0.15842120768012627,1882267211 +2869392,1160408,"shopping",1,true,1,22769,22784,117645105,"shopping",,,false,26,0,941160841,"DRIVEALONE",-0.6680935247002481,1882321681 +2869392,1160408,"shopping",1,false,2,22770,22769,117645105,"othmaint",11.503374294479649,,false,36,0,941160845,"WALK",-0.5869025084004701,1882321689 +2869392,1160408,"shopping",2,false,2,22784,22770,117645105,"home",,,false,37,0,941160846,"WALK",-0.14561343082958378,1882321690 +2871041,1161101,"work",1,true,1,22770,22747,117712720,"work",,,false,10,0,941701761,"WALK",4.37274480605373,1883403521 +2871041,1161101,"work",1,false,1,22747,22770,117712720,"home",,,false,30,0,941701765,"WALK",4.374474053696968,1883403529 +2871042,1161101,"work",1,true,2,22802,22747,117712761,"parking",,,false,6,1,941702089,"DRIVEALONE",0.31437493739186884,1883404177 +2871042,1161101,"work",2,true,2,22802,22802,117712761,"work",,,true,6,1,941702089,"WALK",3.98103278438962,1883404178 +2871042,1161101,"work",1,false,2,22802,22802,117712761,"parking",,,true,31,1,941702093,"WALK",3.9810287626204213,1883404185 +2871042,1161101,"work",2,false,2,22747,22802,117712761,"home",,,false,31,1,941702093,"WALK",0.29964022247838484,1883404186 +4717826,1936565,"univ",1,true,1,22809,22808,193430897,"univ",,,false,25,0,1547447177,"WALK",2.48948699138067,3094894353 +4717826,1936565,"univ",1,false,4,22809,22809,193430897,"univ",10.85837416878764,22809,false,42,0,1547447181,"WALK",3.0000160707611045,3094894361 +4717826,1936565,"univ",2,false,4,22802,22809,193430897,"social",14.420134553925665,,false,43,0,1547447182,"WALK",2.8898362057163802,3094894362 +4717826,1936565,"univ",3,false,4,22807,22802,193430897,"eatout",18.598339591406937,,false,44,0,1547447183,"WALK_LOC",5.851209408094483,3094894363 +4717826,1936565,"univ",4,false,4,22808,22807,193430897,"home",,,false,44,0,1547447184,"WALK",5.537675529040812,3094894364 +4718747,1937486,"univ",1,true,3,22807,22765,193468658,"eatout",25.835053255003054,,false,14,0,1547749265,"WALK_LOC",5.394119748970986,3095498529 +4718747,1937486,"univ",2,true,3,22807,22807,193468658,"social",26.07487490221835,,false,16,0,1547749266,"WALK",5.765967272606238,3095498530 +4718747,1937486,"univ",3,true,3,22809,22807,193468658,"univ",,,false,19,0,1547749267,"WALK",3.0089831584168625,3095498531 +4718747,1937486,"univ",1,false,1,22765,22809,193468658,"home",,,false,42,0,1547749269,"WALK",2.48457681340577,3095498537 +4718747,1937486,"shopping",1,true,2,22767,22765,193468660,"shopping",30.837861614853992,,false,12,0,1547749281,"WALK",6.438600267913209,3095498561 +4718747,1937486,"shopping",2,true,2,22770,22767,193468660,"shopping",,,false,13,0,1547749282,"WALK",5.192455869479483,3095498562 +4718747,1937486,"shopping",1,false,1,22765,22770,193468660,"home",,,false,13,0,1547749285,"WALK",4.807792080345957,3095498569 +4720352,1939091,"univ",1,true,1,22809,22765,193534463,"univ",,,false,9,0,1548275705,"WALK",-0.9117642771058314,3096551409 +4720352,1939091,"univ",1,false,3,22767,22809,193534463,"shopping",11.843847663623558,,false,9,0,1548275709,"WALK",-0.50518420043921,3096551417 +4720352,1939091,"univ",2,false,3,22760,22767,193534463,"othdiscr",19.589050848806597,,false,9,0,1548275710,"WALK",2.07708617142782,3096551418 +4720352,1939091,"univ",3,false,3,22765,22760,193534463,"home",,,false,9,0,1548275711,"WALK",2.8041844809824235,3096551419 +4720352,1939091,"univ",1,true,1,22809,22765,193534464,"univ",,,false,23,0,1548275713,"WALK",2.507472441202307,3096551425 +4720352,1939091,"univ",1,false,2,22766,22809,193534464,"univ",10.595098453730076,22766,false,27,0,1548275717,"WALK",2.554225976269817,3096551433 +4720352,1939091,"univ",2,false,2,22765,22766,193534464,"home",,,false,28,0,1548275718,"WALK",2.711686716364389,3096551434 +4722297,1942003,"univ",1,true,1,22809,22810,193614208,"univ",,,false,11,0,1548913665,"WALK",2.4667125356379236,3097827329 +4722297,1942003,"univ",1,false,1,22810,22809,193614208,"home",,,false,37,0,1548913669,"WALK",2.4563973988486754,3097827337 +4726458,1946164,"eatout",1,true,1,22770,22808,193784784,"eatout",,,false,27,0,1550278273,"WALK",0.3756438367025996,3100556545 +4726458,1946164,"eatout",1,false,1,22808,22770,193784784,"home",,,false,29,0,1550278277,"WALK",0.3756438367025996,3100556553 +4726458,1946164,"eatout",1,true,1,22771,22808,193784785,"eatout",,,false,29,0,1550278281,"WALK",0.6461148549373952,3100556561 +4726458,1946164,"eatout",1,false,1,22808,22771,193784785,"home",,,false,30,0,1550278285,"WALK",0.6461148549373952,3100556569 +4726458,1946164,"shopping",1,true,1,22770,22808,193784811,"shopping",,,false,14,0,1550278489,"WALK",0.3756438367025996,3100556977 +4726458,1946164,"shopping",1,false,1,22808,22770,193784811,"home",,,false,17,0,1550278493,"WALK",0.3756438367025996,3100556985 +4727363,1947069,"univ",1,true,1,22809,22765,193821914,"univ",,,false,14,0,1550575313,"WALK",-0.9117642771058314,3101150625 +4727363,1947069,"univ",1,false,3,22767,22809,193821914,"escort",13.861849979093286,,false,26,0,1550575317,"WALK",-0.50518420043921,3101150633 +4727363,1947069,"univ",2,false,3,22767,22767,193821914,"shopping",18.14486120913688,,false,26,0,1550575318,"WALK",2.62825193059268,3101150634 +4727363,1947069,"univ",3,false,3,22765,22767,193821914,"home",,,false,27,0,1550575319,"WALK",2.1708672114306493,3101150635 +4729458,1949164,"univ",1,true,2,22767,22745,193907809,"eatout",13.431035125581994,,false,11,0,1551262473,"WALK",2.0891749086454086,3102524945 +4729458,1949164,"univ",2,true,2,22764,22767,193907809,"univ",,,false,11,0,1551262474,"WALK",-0.5148347167335139,3102524946 +4729458,1949164,"univ",1,false,2,22767,22764,193907809,"othdiscr",14.563044668763776,,false,27,0,1551262477,"WALK",-0.5148347167335139,3102524953 +4729458,1949164,"univ",2,false,2,22745,22767,193907809,"home",,,false,28,0,1551262478,"WALK",2.0891749086454086,3102524954 +4729679,1949385,"eatout",1,true,1,22745,22745,193916845,"eatout",,,false,26,0,1551334761,"WALK",0.7839251911505445,3102669521 +4729679,1949385,"eatout",1,false,1,22745,22745,193916845,"home",,,false,27,0,1551334765,"WALK",0.7839251911505445,3102669529 diff --git a/activitysim/examples/production_semcog/test/test_semcog.py b/activitysim/examples/production_semcog/test/test_semcog.py index 1e058c2719..8b77a4e3a4 100644 --- a/activitysim/examples/production_semcog/test/test_semcog.py +++ b/activitysim/examples/production_semcog/test/test_semcog.py @@ -99,15 +99,12 @@ def test_semcog_eet(): run_test_semcog(multiprocess=False, use_explicit_error_terms=True) -# TODO: currently running into problems some trips, looks like -# trip_purpose_and_destination, might need to look into resetting -# RNGs there. Leaving this test disabled for now. -# def test_semcog_mp_eet(): -# run_test_semcog(multiprocess=True, use_explicit_error_terms=True) +def test_semcog_mp_eet(): + run_test_semcog(multiprocess=True, use_explicit_error_terms=True) if __name__ == "__main__": run_test_semcog(multiprocess=False) run_test_semcog(multiprocess=True) run_test_semcog(multiprocess=False, use_explicit_error_terms=True) - # run_test_semcog(multiprocess=True, use_explicit_error_terms=True) + run_test_semcog(multiprocess=True, use_explicit_error_terms=True) From d5a8fd46fe046f69f508eafe37f1edc0498f345e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 3 Apr 2026 20:47:48 -0300 Subject: [PATCH 190/298] no need to restrict to specific choosers for reset, each process has its own state --- activitysim/abm/models/location_choice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 47f7ec2a0d..7c8ef16db8 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -1040,8 +1040,7 @@ def iterate_location_choice( logger.debug( f"{trace_label} resetting random number generator offsets for iteration {iteration}" ) - # handle MP by only resetting offsets for all persons in this process. - state.get_rn_generator().reset_offsets_for_df(persons_merged_df) + state.get_rn_generator().reset_offsets_for_step(state.current_model_name) choices_df_, save_sample_df = run_location_choice( state, From 033f08a997607178c32898056b96579b7464885b Mon Sep 17 00:00:00 2001 From: Tyler Pearn Date: Tue, 7 Apr 2026 12:42:10 +1000 Subject: [PATCH 191/298] Add eet multiple zone test for zone=2 --- .../test/configs_eet/settings.yaml | 5 + .../test/regress/final_eet_tours_2_zone.csv | 97 +++++++ .../test/regress/final_eet_trips_2_zone.csv | 238 ++++++++++++++++++ .../test/test_multiple_zone.py | 58 +++-- 4 files changed, 372 insertions(+), 26 deletions(-) create mode 100644 activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml create mode 100644 activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv create mode 100644 activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv diff --git a/activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml b/activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml new file mode 100644 index 0000000000..dcff83f5a1 --- /dev/null +++ b/activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml @@ -0,0 +1,5 @@ +inherit_settings: True + +use_explicit_error_terms: True + +rng_base_seed: 42 diff --git a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv new file mode 100644 index 0000000000..c9002cb44a --- /dev/null +++ b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv @@ -0,0 +1,97 @@ +person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,tour_category,number_of_participants,destination,origin,household_id,tdd,start,end,duration,composition,destination_logsum,tour_mode,mode_choice_logsum,atwork_subtour_frequency,parent_tour_id,stop_frequency,primary_purpose,tour_id +25872,eatout,1,1,1,1,non_mandatory,1,25000,7000,25872,151,15,21,6,,15.324608541661584,WALK,3.925083327622137,,,0out_0in,eatout,1060758 +26103,shopping,1,1,1,1,non_mandatory,1,8000,8000,26103,158,16,20,4,,13.631528588460649,WALK,2.172338551666808,,,0out_0in,shopping,1070256 +26143,shopping,1,1,1,1,non_mandatory,1,19000,8000,26143,113,12,13,1,,14.112489043827686,WALK_LOC,1.756679185311168,,,0out_1in,shopping,1071896 +27412,school,2,1,1,2,mandatory,1,9000,9000,27412,71,9,10,1,,,WALK,5.839003987151259,,,0out_0in,univ,1123923 +27412,school,2,2,2,2,mandatory,1,9000,9000,27412,162,17,17,0,,,BIKE,5.839367147058163,,,3out_0in,univ,1123924 +27415,othmaint,1,1,1,1,non_mandatory,1,16000,9000,27415,44,7,14,7,,15.167583473532224,BIKE,1.4085740306092538,,,0out_0in,othmaint,1124043 +27683,othdiscr,1,1,1,1,non_mandatory,1,9000,10000,27683,74,9,13,4,,15.186442161459588,WALK_LOC,2.846096860674467,,,0out_3in,othdiscr,1135028 +107628,work,1,1,1,1,mandatory,1,12000,6000,107628,92,10,17,7,,,WALK_LOC,5.416481898231418,no_subtours,,0out_0in,work,4412787 +112666,othmaint,1,1,1,1,non_mandatory,1,1000,17000,112666,113,12,13,1,,14.889102700398078,WALK,1.92232729195762,,,0out_0in,othmaint,4619334 +112666,work,1,1,1,1,mandatory,1,2000,17000,112666,166,17,21,4,,,WALK_LRF,5.570170712509852,no_subtours,,0out_0in,work,4619345 +112785,eatout,1,1,1,1,non_mandatory,1,11000,19000,112785,181,20,21,1,,12.710725643768637,SHARED3FREE,0.2552055851917118,,,0out_0in,eatout,4624191 +112785,work,1,1,1,1,mandatory,1,21000,19000,112785,31,6,18,12,,,DRIVEALONEFREE,0.2016161432182198,no_subtours,,0out_0in,work,4624224 +112977,work,1,1,1,1,mandatory,1,12000,21000,112977,63,8,17,9,,,WALK,5.4491289992033805,no_subtours,,2out_3in,work,4632096 +264055,escort,1,1,1,1,non_mandatory,1,20000,9000,226843,19,6,6,0,,12.41661858443438,SHARED3FREE,-0.767103496383878,,,0out_0in,escort,10826264 +264056,work,1,1,1,1,mandatory,1,10000,9000,226843,9,5,14,9,,,WALK_LOC,0.2984357626179245,no_subtours,,0out_0in,work,10826335 +323181,shopping,1,1,1,1,joint,2,11000,10000,256406,127,13,16,3,adults,13.837057469779358,WALK,-0.0206798576468464,,,0out_1in,shopping,13250440 +323181,work,1,1,1,1,mandatory,1,9000,10000,256406,42,7,12,5,,,TNC_SINGLE,5.991138629031954,no_subtours,,0out_0in,work,13250460 +323182,work,1,1,1,1,mandatory,1,2000,10000,256406,158,16,20,4,,,WALK_LRF,6.136056448795052,no_subtours,,0out_0in,work,13250501 +325309,work,1,1,1,1,mandatory,1,19000,16000,257470,26,6,13,7,,,WALK_LOC,3.4843919117072377,no_subtours,,0out_0in,work,13337708 +325429,work,1,1,1,1,mandatory,1,5000,16000,257530,64,8,18,10,,,WALK,6.140255328078286,no_subtours,,1out_0in,work,13342628 +325430,eat,1,1,1,1,atwork,1,1000,23000,257530,101,11,13,2,,14.870319018560831,TNC_SHARED,5.355407771372916,,13342669.0,0out_0in,atwork,13342634 +325430,othdiscr,1,1,1,1,non_mandatory,1,16000,16000,257530,158,16,20,4,,15.333041399718818,WALK,2.930679229042425,,,0out_0in,othdiscr,13342655 +325430,work,1,1,1,1,mandatory,1,23000,16000,257530,29,6,16,10,,,TNC_SINGLE,6.28069576419515,eat,,0out_0in,work,13342669 +644437,work,1,1,1,1,mandatory,1,24000,9000,386748,65,8,19,11,,,WALK_LRF,5.654556608152202,no_subtours,,0out_0in,work,26421956 +644438,shopping,1,1,1,1,non_mandatory,1,16000,9000,386748,91,10,16,6,,13.77366143367142,WALK_LRF,0.9593933036319412,,,1out_0in,shopping,26421991 +644439,work,1,1,1,1,mandatory,1,22000,9000,386748,10,5,15,10,,,WALK_LRF,5.664941612162526,no_subtours,,0out_0in,work,26422038 +1264881,othdiscr,1,1,1,1,non_mandatory,1,18000,10000,567768,159,16,21,5,,15.225973212134,WALK_LRF,2.039609656048619,,,0out_0in,othdiscr,51860146 +1265935,escort,1,1,1,2,non_mandatory,1,8000,17000,568822,170,18,19,1,,12.359706844091942,WALK,-1.630686893624885,,,0out_0in,escort,51903344 +1265935,othmaint,1,1,2,2,non_mandatory,1,21000,17000,568822,102,11,14,3,,13.737651385285645,WALK_LRF,-0.4649705484292334,,,0out_0in,othmaint,51903363 +1356554,shopping,1,1,1,1,non_mandatory,1,13000,22000,659441,64,8,18,10,,13.88685165306392,BIKE,1.5870038941022344,,,0out_0in,shopping,55618747 +1632055,eat,1,1,1,1,atwork,1,12000,11000,823275,85,10,10,0,,15.6610529510408,WALK,5.897877376563244,,66914294.0,0out_0in,atwork,66914259 +1632055,work,1,1,1,1,mandatory,1,11000,10000,823275,45,7,15,8,,,WALK,6.0466798507502375,eat,,0out_0in,work,66914294 +1632179,work,1,1,1,1,mandatory,1,23000,11000,823399,109,11,21,10,,,WALK_LOC,-0.069261093997962,no_subtours,,1out_1in,work,66919378 +1774491,work,1,1,1,1,mandatory,1,7000,9000,932260,45,7,15,8,,,WALK_LOC,6.153116921757978,no_subtours,,0out_0in,work,72754170 +1774492,shopping,1,1,1,1,non_mandatory,1,16000,9000,932260,113,12,13,1,,14.197083405037375,WALK_HVY,2.643876882714501,,,1out_0in,shopping,72754205 +1876395,work,1,1,1,1,mandatory,1,13000,21000,983212,31,6,18,12,,,TNC_SINGLE,5.289238909714063,no_subtours,,0out_1in,work,76932234 +2071280,work,1,1,1,1,mandatory,1,16000,21000,1070367,62,8,16,8,,,WALK,1.7053738749378673,no_subtours,,0out_0in,work,84922519 +2071281,work,1,1,1,1,mandatory,1,5000,21000,1070367,74,9,13,4,,,WALK,2.0147118600827216,no_subtours,,0out_1in,work,84922560 +2220332,work,1,1,1,1,mandatory,1,11000,20000,1120051,77,9,16,7,,,SHARED3FREE,1.7010760839664214,no_subtours,,1out_1in,work,91033651 +2220333,othmaint,1,1,1,1,non_mandatory,1,7000,20000,1120051,151,15,21,6,,14.014606142053264,TNC_SINGLE,0.149491264831688,,,0out_2in,othmaint,91033681 +2220333,work,1,1,1,1,mandatory,1,14000,20000,1120051,28,6,15,9,,,WALK_HVY,1.5213713475726636,no_subtours,,0out_0in,work,91033692 +2220334,school,1,1,1,1,mandatory,1,9000,20000,1120051,66,8,20,12,,,WALK_LOC,1.991771241543342,,,0out_0in,school,91033725 +2746929,eat,1,1,1,1,atwork,1,10000,21000,1234121,162,17,17,0,,12.71430384191495,WALK,-0.5268164634483682,,112624128.0,0out_0in,atwork,112624093 +2746932,shopping,1,1,1,1,joint,2,1000,11000,1234121,162,17,17,0,children,13.38338375038534,SHARED2FREE,-0.6867048669480782,,,0out_0in,shopping,112624108 +2746929,work,1,1,1,1,mandatory,1,21000,11000,1234121,30,6,17,11,,,WALK,1.6514348701205426,eat,,0out_0in,work,112624128 +2746930,eatout,1,1,1,1,non_mandatory,1,17000,11000,1234121,90,10,15,5,,14.10283915060394,WALK,1.3369363207414489,,,0out_0in,eatout,112624136 +2746931,work,1,1,1,1,mandatory,1,20000,11000,1234121,50,7,20,13,,,WALK,1.4578058558199158,no_subtours,,0out_0in,work,112624210 +2746932,school,1,1,1,1,mandatory,1,8000,11000,1234121,47,7,17,10,,,WALK,2.0202226257018245,,,0out_0in,school,112624243 +2746934,school,1,1,1,1,mandatory,1,21000,11000,1234121,92,10,17,7,,,WALK,1.767745192104871,,,0out_0in,school,112624325 +2936912,shopping,1,1,1,1,non_mandatory,1,16000,17000,1286621,143,14,22,8,,12.720501141423998,WALK_LRF,-0.1971228190146473,,,0out_0in,shopping,120413425 +2957530,work,1,1,1,1,mandatory,1,19000,21000,1307239,107,11,19,8,,,WALK_LOC,5.394901446058748,no_subtours,,0out_0in,work,121258769 +3112851,work,1,1,1,1,mandatory,1,2000,25000,1384946,108,11,20,9,,,TNC_SINGLE,5.857655520298795,no_subtours,,0out_1in,work,127626930 +3328643,shopping,1,1,1,1,non_mandatory,1,19000,9000,1511271,62,8,16,8,,12.599796366663016,WALK,-0.9841036472591292,,,0out_0in,shopping,136474396 +3495346,eat,1,1,1,1,atwork,1,11000,5000,1594623,135,14,14,0,,15.796876863416736,TNC_SINGLE,5.699539647746939,,143309225.0,0out_0in,atwork,143309190 +3495346,work,1,1,1,1,mandatory,1,5000,10000,1594623,51,7,21,14,,,WALK,5.718922382923123,eat,,0out_0in,work,143309225 +3495347,othmaint,1,1,2,2,non_mandatory,1,12000,10000,1594623,86,10,11,1,,15.25941622676957,TAXI,1.8490728888043824,,,0out_0in,othmaint,143309255 +3495347,shopping,1,1,1,2,non_mandatory,1,1000,10000,1594623,99,11,11,0,,13.9649849918008,WALK_HVY,2.463240708785933,,,0out_0in,shopping,143309260 +3495347,work,1,1,1,1,mandatory,1,9000,10000,1594623,119,12,19,7,,,WALK,6.084928426498565,no_subtours,,0out_0in,work,143309266 +3496420,eat,1,1,1,1,atwork,1,12000,16000,1595160,87,10,12,2,,12.795015949891248,WALK,-0.259760437162128,,143353259.0,0out_0in,atwork,143353224 +3496420,work,1,1,1,1,mandatory,1,16000,17000,1595160,15,5,20,15,,,WALK,2.101751268888541,eat,,0out_0in,work,143353259 +3496421,work,1,1,1,1,mandatory,1,16000,17000,1595160,48,7,18,11,,,WALK_LOC,2.142007829019355,no_subtours,,0out_0in,work,143353300 +3608536,work,1,1,1,1,mandatory,1,13000,9000,1651218,47,7,17,10,,,WALK_LOC,6.27632317344463,no_subtours,,0out_0in,work,147950015 +3608537,school,1,1,1,1,mandatory,1,13000,9000,1651218,47,7,17,10,,,WALK_LRF,22.088164625699857,,,0out_0in,school,147950048 +4762866,school,1,1,1,1,mandatory,1,17000,16000,1931922,59,8,13,5,,,WALK_LRF,19.77600466914122,,,0out_0in,school,195277537 +4762869,school,1,1,1,1,mandatory,1,17000,16000,1931922,61,8,15,7,,,WALK_LRF,19.776004668973645,,,0out_0in,school,195277660 +4762871,shopping,1,1,1,1,non_mandatory,1,13000,16000,1931922,113,12,13,1,,13.8200995095236,WALK,2.023921299435018,,,0out_0in,shopping,195277744 +4762872,work,1,1,1,1,mandatory,1,1000,16000,1931922,80,9,19,10,,,WALK,5.6129430623808885,no_subtours,,0out_0in,work,195277791 +4762874,school,1,1,1,1,mandatory,1,16000,16000,1931922,44,7,14,7,,,WALK,17.69297933376892,,,0out_0in,school,195277865 +4762875,school,1,1,1,1,mandatory,1,16000,16000,1931922,57,8,11,3,,,WALK,17.692979337157823,,,0out_0in,school,195277906 +4817011,social,2,1,1,2,non_mandatory,1,7000,8000,1946006,113,12,13,1,,14.214418678632692,WALK,2.01539190342834,,,0out_0in,social,197497487 +4817011,social,2,2,2,2,non_mandatory,1,1000,8000,1946006,137,14,16,2,,14.19926277967054,WALK_LOC,1.1067525270395056,,,0out_0in,social,197497488 +4950606,school,1,1,1,1,mandatory,1,7000,8000,2010083,67,8,21,13,,,WALK_LOC,20.09206099292637,,,0out_0in,school,202974877 +5057667,work,1,1,1,1,mandatory,1,5000,11000,2048711,65,8,19,11,,,WALK,-0.3438284257928611,no_subtours,,0out_0in,work,207364386 +5386916,eat,1,1,1,1,atwork,1,14000,10000,2222604,124,13,13,0,,15.23972998434972,WALK_HVY,4.763449153784629,,220863595.0,0out_1in,atwork,220863560 +5386916,shopping,1,1,1,1,non_mandatory,1,20000,7000,2222604,177,19,21,2,,13.804209608835082,TNC_SINGLE,1.5539907867200995,,,1out_2in,shopping,220863589 +5386916,work,1,1,1,1,mandatory,1,10000,7000,2222604,64,8,18,10,,,WALK_LOC,5.700023979202434,eat,,0out_0in,work,220863595 +5386917,work,1,1,1,1,mandatory,1,19000,7000,2222604,64,8,18,10,,,TAXI,5.19733294875638,no_subtours,,0out_1in,work,220863636 +5387114,othdiscr,2,1,1,2,non_mandatory,1,22000,7000,2222703,31,6,18,12,,15.39365106527831,WALK_LRF,2.6851963090850903,,,1out_0in,othdiscr,220871699 +5387114,othdiscr,2,2,2,2,non_mandatory,1,6000,7000,2222703,169,18,18,0,,15.500122017723845,WALK,3.332822704108827,,,0out_0in,othdiscr,220871700 +5387114,work,1,1,1,1,mandatory,1,4000,7000,2222703,178,19,22,3,,,TNC_SINGLE,6.115658033572405,no_subtours,,0out_0in,work,220871713 +5387115,eatout,1,1,1,1,non_mandatory,1,14000,7000,2222703,184,21,21,0,,15.608709839039491,TNC_SHARED,3.486447403367392,,,0out_0in,eatout,220871721 +5387115,work,1,1,1,1,mandatory,1,13000,7000,2222703,81,9,20,11,,,TNC_SINGLE,5.995379715129444,no_subtours,,1out_1in,work,220871754 +5388246,work,1,1,1,1,mandatory,1,4000,10000,2223269,80,9,19,10,,,WALK_HVY,1.8988665278205443,no_subtours,,0out_1in,work,220918125 +5388247,eat,1,1,1,1,atwork,1,2000,24000,2223269,125,13,14,1,,12.733618518169584,WALK,0.5490051925973694,,220918166.0,0out_0in,atwork,220918131 +5388247,work,1,1,1,1,mandatory,1,24000,10000,2223269,67,8,21,13,,,WALK,1.4444587506481184,eat,,0out_1in,work,220918166 +5388456,work,2,1,1,2,mandatory,1,16000,11000,2223374,79,9,18,9,,,WALK,1.6463809900728428,no_subtours,,0out_0in,work,220926735 +5388456,work,2,2,2,2,mandatory,1,16000,11000,2223374,170,18,19,1,,,WALK,1.7200593221990077,no_subtours,,1out_1in,work,220926736 +5388457,work,1,1,1,1,mandatory,1,23000,11000,2223374,80,9,19,10,,,WALK_LOC,1.3918688641625712,no_subtours,,0out_0in,work,220926776 +5391136,eat,1,1,1,1,atwork,1,13000,16000,2224714,101,11,13,2,,12.823885233906973,WALK,0.1028337439895324,,221036615.0,0out_0in,atwork,221036580 +5391136,eatout,1,1,1,1,non_mandatory,1,22000,17000,2224714,181,20,21,1,,13.796840766625918,WALK_LRF,1.3381911203468422,,,0out_0in,eatout,221036582 +5391136,work,1,1,1,1,mandatory,1,16000,17000,2224714,63,8,17,9,,,WALK_LRF,2.230397835408683,eat,,3out_0in,work,221036615 +5391137,work,1,1,1,1,mandatory,1,2000,17000,2224714,13,5,18,13,,,WALK_LRF,1.993723549745754,no_subtours,,0out_3in,work,221036656 +7452651,othdiscr,1,1,2,2,non_mandatory,1,14000,10000,2761316,160,16,22,6,,14.980075296614151,WALK_LRF,2.5510853722692675,,,0out_0in,othdiscr,305558716 +7452651,othmaint,1,1,1,2,non_mandatory,1,2000,10000,2761316,8,5,13,8,,15.212337325011434,WALK_LRF,2.7654116278187377,,,0out_0in,othmaint,305558719 +7452726,shopping,1,1,1,1,non_mandatory,1,16000,10000,2761391,115,12,15,3,,13.809166893945894,SHARED2FREE,1.244280339251842,,,1out_1in,shopping,305561799 +7453556,othdiscr,1,1,1,1,non_mandatory,1,5000,21000,2762221,139,14,18,4,,13.940429525972068,WALK_LOC,0.4224525901427229,,,0out_0in,othdiscr,305595821 diff --git a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv new file mode 100644 index 0000000000..0b7aeef939 --- /dev/null +++ b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv @@ -0,0 +1,238 @@ +person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum,trip_id +25872,25872,eatout,1,True,1,25000,7000,1060758,eatout,,15,WALK,12.622881703075194,8486065 +25872,25872,eatout,1,False,1,7000,25000,1060758,home,,21,WALK,12.807021628425415,8486069 +26103,26103,shopping,1,True,1,8000,8000,1070256,shopping,,16,WALK,12.5029740499786,8562049 +26103,26103,shopping,1,False,1,8000,8000,1070256,home,,20,WALK,12.5029740499786,8562053 +26143,26143,shopping,1,True,1,19000,8000,1071896,shopping,,12,WALK,-0.028786720385919,8575169 +26143,26143,shopping,1,False,2,7000,19000,1071896,escort,36.069170215525695,13,WALK_LOC,0.0596550723757274,8575173 +26143,26143,shopping,2,False,2,8000,7000,1071896,home,,13,WALK,14.544360704356594,8575174 +27412,27412,univ,1,True,1,9000,9000,1123923,univ,,9,WALK,10.238432618676171,8991385 +27412,27412,univ,1,False,1,9000,9000,1123923,home,,10,WALK,10.238432618676171,8991389 +27412,27412,univ,1,True,4,7000,9000,1123924,escort,52.4294183195496,17,BIKE,13.493362410549922,8991393 +27412,27412,univ,2,True,4,7000,7000,1123924,shopping,53.01926176997787,17,BIKE,13.911132276096,8991394 +27412,27412,univ,3,True,4,7000,7000,1123924,escort,53.23086426572027,17,WALK,13.911132276096,8991395 +27412,27412,univ,4,True,4,9000,7000,1123924,univ,,17,WALK,9.965741143813498,8991396 +27412,27412,univ,1,False,1,9000,9000,1123924,home,,17,BIKE,10.482611643665615,8991397 +27415,27415,othmaint,1,True,1,16000,9000,1124043,othmaint,,7,BIKE,3.991435227151364,8992345 +27415,27415,othmaint,1,False,1,9000,16000,1124043,home,,14,BIKE,3.977759441323569,8992349 +27683,27683,othdiscr,1,True,1,9000,10000,1135028,othdiscr,,9,WALK_LOC,11.109665478014662,9080225 +27683,27683,othdiscr,1,False,4,7000,9000,1135028,escort,55.92888040973646,13,WALK,11.035231764382528,9080229 +27683,27683,othdiscr,2,False,4,9000,7000,1135028,othmaint,57.67662388100479,13,WALK_LOC,14.172826390440834,9080230 +27683,27683,othdiscr,3,False,4,7000,9000,1135028,shopping,55.11890671874478,13,WALK,11.035231856411922,9080231 +27683,27683,othdiscr,4,False,4,10000,7000,1135028,home,,13,WALK_LOC,14.097237153881805,9080232 +107628,107628,work,1,True,1,12000,6000,4412787,work,,10,WALK_LOC,4.23738210975023,35302297 +107628,107628,work,1,False,1,6000,12000,4412787,home,,17,WALK,4.177398023206957,35302301 +112666,112666,othmaint,1,True,1,1000,17000,4619334,othmaint,,12,WALK,-1.1983965087409965,36954673 +112666,112666,othmaint,1,False,1,17000,1000,4619334,home,,13,WALK,-1.312439942707106,36954677 +112666,112666,work,1,True,1,2000,17000,4619345,work,,17,WALK,0.151322967222737,36954761 +112666,112666,work,1,False,1,17000,2000,4619345,home,,21,WALK,0.4697850077158731,36954765 +112785,112785,eatout,1,True,1,11000,19000,4624191,eatout,,20,WALK,4.328113197335523,36993529 +112785,112785,eatout,1,False,1,19000,11000,4624191,home,,21,WALK,4.313101182265273,36993533 +112785,112785,work,1,True,1,21000,19000,4624224,work,,6,WALK,1.8420501444349504,36993793 +112785,112785,work,1,False,1,19000,21000,4624224,home,,18,WALK,1.8334411011838323,36993797 +112977,112977,work,1,True,3,8000,21000,4632096,escort,33.04133838114583,8,WALK,9.009776222045147,37056769 +112977,112977,work,2,True,3,7000,8000,4632096,escort,35.28678968135897,8,WALK,10.948561446864396,37056770 +112977,112977,work,3,True,3,12000,7000,4632096,work,,8,WALK,3.208903318908808,37056771 +112977,112977,work,1,False,4,9000,12000,4632096,othmaint,30.539889894402076,17,WALK,2.632506122325052,37056773 +112977,112977,work,2,False,4,7000,9000,4632096,escort,42.63129816637797,17,WALK,7.8276425435229235,37056774 +112977,112977,work,3,False,4,7000,7000,4632096,othmaint,49.379996481297965,17,WALK,11.036561446414703,37056775 +112977,112977,work,4,False,4,21000,7000,4632096,home,,17,WALK,10.394161433315231,37056776 +264055,226843,escort,1,True,1,20000,9000,10826264,escort,,6,WALK,1.609303603527435,86610113 +264055,226843,escort,1,False,1,9000,20000,10826264,home,,6,WALK,1.500843214540278,86610117 +264056,226843,work,1,True,1,10000,9000,10826335,work,,5,WALK_LOC,8.474132523587011,86610681 +264056,226843,work,1,False,1,9000,10000,10826335,home,,14,WALK_LOC,8.451196386482865,86610685 +323181,256406,shopping,1,True,1,11000,10000,13250440,shopping,,13,WALK,4.898455205211476,106003521 +323181,256406,shopping,1,False,2,9000,11000,13250440,escort,40.31798624577948,16,WALK,4.720179189781661,106003525 +323181,256406,shopping,2,False,2,10000,9000,13250440,home,,16,WALK,10.780621013348998,106003526 +323181,256406,work,1,True,1,9000,10000,13250460,work,,7,WALK_LOC,8.866606849192987,106003681 +323181,256406,work,1,False,1,10000,9000,13250460,home,,12,WALK,8.943956112104715,106003685 +323182,256406,work,1,True,1,2000,10000,13250501,work,,16,WALK_LRF,0.4866278431422626,106004009 +323182,256406,work,1,False,1,10000,2000,13250501,home,,20,WALK_LRF,0.4920280753234634,106004013 +325309,257470,work,1,True,1,19000,16000,13337708,work,,6,WALK,-1.2965720830153529,106701665 +325309,257470,work,1,False,1,16000,19000,13337708,home,,13,WALK,-1.3173517958919845,106701669 +325429,257530,work,1,True,2,7000,16000,13342628,escort,33.60698362494382,8,WALK,9.804561444298288,106741025 +325429,257530,work,2,True,2,5000,7000,13342628,work,,9,WALK,3.3081329142919915,106741026 +325429,257530,work,1,False,1,16000,5000,13342628,home,,18,WALK,2.832946137131945,106741029 +325430,257530,atwork,1,True,1,1000,23000,13342634,atwork,,11,TNC_SINGLE,1.285198550672105,106741073 +325430,257530,atwork,1,False,1,23000,1000,13342634,work,,13,TNC_SINGLE,1.2886153300442418,106741077 +325430,257530,othdiscr,1,True,1,16000,16000,13342655,othdiscr,,16,WALK,7.330879884940061,106741241 +325430,257530,othdiscr,1,False,1,16000,16000,13342655,home,,20,WALK,7.33087988512484,106741245 +325430,257530,work,1,True,1,23000,16000,13342669,work,,6,WALK_LOC,2.0406987569036743,106741353 +325430,257530,work,1,False,1,16000,23000,13342669,home,,16,WALK_LOC,2.1205858583347994,106741357 +644437,386748,work,1,True,1,24000,9000,26421956,work,,8,WALK,2.398575768520878,211375649 +644437,386748,work,1,False,1,9000,24000,26421956,home,,19,WALK_LRF,2.6720960087106285,211375653 +644438,386748,shopping,1,True,2,8000,9000,26421991,othdiscr,43.91352211511756,10,WALK,12.032732830156991,211375929 +644438,386748,shopping,2,True,2,16000,8000,26421991,shopping,,12,WALK_LOC,5.795035984789261,211375930 +644438,386748,shopping,1,False,1,9000,16000,26421991,home,,16,WALK_LRF,7.823496294062292,211375933 +644439,386748,work,1,True,1,22000,9000,26422038,work,,5,WALK_LRF,1.7428771545130517,211376305 +644439,386748,work,1,False,1,9000,22000,26422038,home,,15,WALK,1.86885070744339,211376309 +1264881,567768,othdiscr,1,True,1,18000,10000,51860146,othdiscr,,16,WALK_LOC,1.233984053082079,414881169 +1264881,567768,othdiscr,1,False,1,10000,18000,51860146,home,,21,WALK_LRF,1.5716192527699957,414881173 +1265935,568822,escort,1,True,1,8000,17000,51903344,escort,,18,WALK,9.818994025451875,415226753 +1265935,568822,escort,1,False,1,17000,8000,51903344,home,,19,WALK,9.958494232509716,415226757 +1265935,568822,othmaint,1,True,1,21000,17000,51903363,othmaint,,11,WALK,3.4811515445467536,415226905 +1265935,568822,othmaint,1,False,1,17000,21000,51903363,home,,14,WALK_LRF,3.799259767554127,415226909 +1356554,659441,shopping,1,True,1,13000,22000,55618747,shopping,,8,BIKE,-0.7588316695466023,444949977 +1356554,659441,shopping,1,False,1,22000,13000,55618747,home,,18,BIKE,-0.9265992357824824,444949981 +1632055,823275,atwork,1,True,1,12000,11000,66914259,atwork,,10,WALK,4.956248158173995,535314073 +1632055,823275,atwork,1,False,1,11000,12000,66914259,work,,10,WALK,4.956248078984152,535314077 +1632055,823275,work,1,True,1,11000,10000,66914294,work,,7,WALK,3.53078088614594,535314353 +1632055,823275,work,1,False,1,10000,11000,66914294,home,,15,WALK,3.53078055875979,535314357 +1632179,823399,work,1,True,2,6000,11000,66919378,shopping,30.226326066605,11,WALK,10.141295202967775,535355025 +1632179,823399,work,2,True,2,23000,6000,66919378,work,,11,WALK_LOC,1.211843196595506,535355026 +1632179,823399,work,1,False,2,7000,23000,66919378,shopping,33.39551329594212,21,WALK_LOC,1.6167681518696837,535355029 +1632179,823399,work,2,False,2,11000,7000,66919378,home,,21,WALK,11.52111911817073,535355030 +1774491,932260,work,1,True,1,7000,9000,72754170,work,,7,WALK_LOC,11.509109011754376,582033361 +1774491,932260,work,1,False,1,9000,7000,72754170,home,,15,WALK_LOC,11.434340926744566,582033365 +1774492,932260,shopping,1,True,2,7000,9000,72754205,othdiscr,50.515004160168914,12,WALK,14.438474985591771,582033641 +1774492,932260,shopping,2,True,2,16000,7000,72754205,shopping,,13,WALK_LOC,7.1497500612965466,582033642 +1774492,932260,shopping,1,False,1,9000,16000,72754205,home,,13,WALK_LRF,8.003866393547536,582033645 +1876395,983212,work,1,True,1,13000,21000,76932234,work,,6,WALK_LOC,0.0082845863634515,615457873 +1876395,983212,work,1,False,2,7000,13000,76932234,othdiscr,31.18255131388486,17,WALK_LOC,0.0374276967114544,615457877 +1876395,983212,work,2,False,2,21000,7000,76932234,home,,18,WALK,11.208251589738415,615457878 +2071280,1070367,work,1,True,1,16000,21000,84922519,work,,8,WALK,4.975425515919179,679380153 +2071280,1070367,work,1,False,1,21000,16000,84922519,home,,16,WALK,4.7554261455753934,679380157 +2071281,1070367,work,1,True,1,5000,21000,84922560,work,,9,WALK,3.035330184189096,679380481 +2071281,1070367,work,1,False,2,7000,5000,84922560,othdiscr,35.54470921265402,13,WALK,3.360924868679592,679380485 +2071281,1070367,work,2,False,2,21000,7000,84922560,home,,13,WALK,10.39416144383609,679380486 +2220332,1120051,work,1,True,2,7000,20000,91033651,work,37.708683174130016,9,WALK,10.219696407315745,728269209 +2220332,1120051,work,2,True,2,11000,7000,91033651,work,,11,WALK,3.629020464703427,728269210 +2220332,1120051,work,1,False,2,7000,11000,91033651,othmaint,35.51810478534547,16,WALK,3.6126450866505695,728269213 +2220332,1120051,work,2,False,2,20000,7000,91033651,home,,16,WALK,10.02169230676222,728269214 +2220333,1120051,othmaint,1,True,1,7000,20000,91033681,othmaint,,15,WALK_LOC,8.628357530180933,728269449 +2220333,1120051,othmaint,1,False,3,8000,7000,91033681,eatout,40.27006262947925,21,WALK_LOC,8.531134315535892,728269453 +2220333,1120051,othmaint,2,False,3,10000,8000,91033681,eatout,37.25832680880913,21,WALK_LOC,7.532098294705944,728269454 +2220333,1120051,othmaint,3,False,3,20000,10000,91033681,home,,21,WALK_LOC,6.240170553049018,728269455 +2220333,1120051,work,1,True,1,14000,20000,91033692,work,,6,WALK_LRF,1.5161043166984922,728269537 +2220333,1120051,work,1,False,1,20000,14000,91033692,home,,15,WALK_LOC,0.7434266921881713,728269541 +2220334,1120051,school,1,True,1,9000,20000,91033725,school,,8,WALK_LOC,10.058753507459729,728269801 +2220334,1120051,school,1,False,1,20000,9000,91033725,home,,20,WALK,9.814636763939884,728269805 +2746929,1234121,atwork,1,True,1,10000,21000,112624093,atwork,,17,WALK,9.58423331125647,900992745 +2746929,1234121,atwork,1,False,1,21000,10000,112624093,work,,17,WALK,9.59175323294754,900992749 +2746932,1234121,shopping,1,True,1,1000,11000,112624108,shopping,,17,SHARED2FREE,-0.0068224692201906,900992865 +2746932,1234121,shopping,1,False,1,11000,1000,112624108,home,,17,SHARED2FREE,-0.0077875668598362,900992869 +2746929,1234121,work,1,True,1,21000,11000,112624128,work,,6,WALK,2.9694319601524466,900993025 +2746929,1234121,work,1,False,1,11000,21000,112624128,home,,17,WALK,2.9694319554520967,900993029 +2746930,1234121,eatout,1,True,1,17000,11000,112624136,eatout,,10,WALK,4.435362336682579,900993089 +2746930,1234121,eatout,1,False,1,11000,17000,112624136,home,,15,WALK,4.463262318129899,900993093 +2746931,1234121,work,1,True,1,20000,11000,112624210,work,,7,WALK,1.1933443038963714,900993681 +2746931,1234121,work,1,False,1,11000,20000,112624210,home,,20,WALK,1.2637449668352003,900993685 +2746932,1234121,school,1,True,1,8000,11000,112624243,school,,7,WALK,11.3802464089204,900993945 +2746932,1234121,school,1,False,1,11000,8000,112624243,home,,17,WALK,11.282686396640877,900993949 +2746934,1234121,school,1,True,1,21000,11000,112624325,school,,10,WALK,3.657792220061346,900994601 +2746934,1234121,school,1,False,1,11000,21000,112624325,home,,17,WALK,3.657792219228462,900994605 +2936912,1286621,shopping,1,True,1,16000,17000,120413425,shopping,,14,WALK,7.855163989505554,963307401 +2936912,1286621,shopping,1,False,1,17000,16000,120413425,home,,22,WALK,7.853841131587542,963307405 +2957530,1307239,work,1,True,1,19000,21000,121258769,work,,11,WALK,0.3637138294765611,970070153 +2957530,1307239,work,1,False,1,21000,19000,121258769,home,,19,WALK,0.2307332766636538,970070157 +3112851,1384946,work,1,True,1,2000,25000,127626930,work,,11,WALK_LOC,0.6211997338372249,1021015441 +3112851,1384946,work,1,False,2,8000,2000,127626930,work,29.9712324638375,19,WALK_LOC,0.255211438812688,1021015445 +3112851,1384946,work,2,False,2,25000,8000,127626930,home,,20,WALK,9.061488007962073,1021015446 +3328643,1511271,shopping,1,True,1,19000,9000,136474396,shopping,,8,WALK,-0.8879590795653944,1091795169 +3328643,1511271,shopping,1,False,1,9000,19000,136474396,home,,16,WALK,-1.083191160238464,1091795173 +3495346,1594623,atwork,1,True,1,11000,5000,143309190,atwork,,14,WALK,5.543648655831851,1146473521 +3495346,1594623,atwork,1,False,1,5000,11000,143309190,work,,14,WALK_LOC,5.61918499405176,1146473525 +3495346,1594623,work,1,True,1,5000,10000,143309225,work,,7,WALK,2.524931061757108,1146473801 +3495346,1594623,work,1,False,1,10000,5000,143309225,home,,21,WALK,2.744927712292272,1146473805 +3495347,1594623,othmaint,1,True,1,12000,10000,143309255,othmaint,,10,WALK_LOC,3.183629274414097,1146474041 +3495347,1594623,othmaint,1,False,1,10000,12000,143309255,home,,11,WALK_LOC,3.183277552596278,1146474045 +3495347,1594623,shopping,1,True,1,1000,10000,143309260,shopping,,11,WALK_LRF,0.3379285592978597,1146474081 +3495347,1594623,shopping,1,False,1,10000,1000,143309260,home,,11,WALK_LRF,0.4541508537591313,1146474085 +3495347,1594623,work,1,True,1,9000,10000,143309266,work,,12,WALK,8.03004262714356,1146474129 +3495347,1594623,work,1,False,1,10000,9000,143309266,home,,19,WALK,8.170842629634851,1146474133 +3496420,1595160,atwork,1,True,1,12000,16000,143353224,atwork,,10,WALK,4.843447137861254,1146825793 +3496420,1595160,atwork,1,False,1,16000,12000,143353224,work,,12,WALK,4.843447111509163,1146825797 +3496420,1595160,work,1,True,1,16000,17000,143353259,work,,5,WALK,5.0458247003147685,1146826073 +3496420,1595160,work,1,False,1,17000,16000,143353259,home,,20,WALK,5.116224566837883,1146826077 +3496421,1595160,work,1,True,1,16000,17000,143353300,work,,7,WALK_LOC,5.977645296996196,1146826401 +3496421,1595160,work,1,False,1,17000,16000,143353300,home,,18,WALK,5.989179722709104,1146826405 +3608536,1651218,work,1,True,1,13000,9000,147950015,work,,7,WALK_LOC,-0.2844070906738741,1183600121 +3608536,1651218,work,1,False,1,9000,13000,147950015,home,,17,WALK,-0.2557713080959184,1183600125 +3608537,1651218,school,1,True,1,13000,9000,147950048,school,,7,WALK_LRF,1.0049003799424374,1183600385 +3608537,1651218,school,1,False,1,9000,13000,147950048,home,,17,WALK_LRF,1.000810478044286,1183600389 +4762866,1931922,school,1,True,1,17000,16000,195277537,school,,8,WALK_LRF,6.292251313972136,1562220297 +4762866,1931922,school,1,False,1,16000,17000,195277537,home,,13,WALK_LRF,6.2578013689770495,1562220301 +4762869,1931922,school,1,True,1,17000,16000,195277660,school,,8,WALK_LRF,6.292251313972136,1562221281 +4762869,1931922,school,1,False,1,16000,17000,195277660,home,,15,WALK_LRF,6.257797388209449,1562221285 +4762871,1931922,shopping,1,True,1,13000,16000,195277744,shopping,,12,WALK,-0.6022649376600622,1562221953 +4762871,1931922,shopping,1,False,1,16000,13000,195277744,home,,13,WALK,-0.6741441677126537,1562221957 +4762872,1931922,work,1,True,1,1000,16000,195277791,work,,9,WALK,-1.1893690966144852,1562222329 +4762872,1931922,work,1,False,1,16000,1000,195277791,home,,19,WALK,-1.3613354348131683,1562222333 +4762874,1931922,school,1,True,1,16000,16000,195277865,school,,7,WALK,6.714175485305698,1562222921 +4762874,1931922,school,1,False,1,16000,16000,195277865,home,,14,WALK,6.714175485873147,1562222925 +4762875,1931922,school,1,True,1,16000,16000,195277906,school,,8,WALK,6.714175485305698,1562223249 +4762875,1931922,school,1,False,1,16000,16000,195277906,home,,11,WALK,6.714175485305698,1562223253 +4817011,1946006,social,1,True,1,7000,8000,197497487,social,,12,WALK,8.97158312503046,1579979897 +4817011,1946006,social,1,False,1,8000,7000,197497487,home,,13,WALK,8.789583139086504,1579979901 +4817011,1946006,social,1,True,1,1000,8000,197497488,social,,14,WALK_LOC,-0.1838315538475003,1579979905 +4817011,1946006,social,1,False,1,8000,1000,197497488,home,,16,WALK_LOC,-0.2144249677066041,1579979909 +4950606,2010083,school,1,True,1,7000,8000,202974877,school,,8,WALK_LOC,13.226901524964711,1623799017 +4950606,2010083,school,1,False,1,8000,7000,202974877,home,,21,WALK,13.015690560204396,1623799021 +5057667,2048711,work,1,True,1,5000,11000,207364386,work,,8,WALK,3.132108367818242,1658915089 +5057667,2048711,work,1,False,1,11000,5000,207364386,home,,19,WALK,3.176108669344638,1658915093 +5386916,2222604,atwork,1,True,1,14000,10000,220863560,atwork,,13,WALK_LRF,2.6214243141937126,1766908481 +5386916,2222604,atwork,1,False,2,7000,14000,220863560,eatout,40.76162984079656,13,WALK_LOC,1.966082194929056,1766908485 +5386916,2222604,atwork,2,False,2,10000,7000,220863560,work,,13,WALK,14.875749907565677,1766908486 +5386916,2222604,shopping,1,True,2,9000,7000,220863589,social,35.52503266455083,19,WALK_LOC,10.635824434089114,1766908713 +5386916,2222604,shopping,2,True,2,20000,9000,220863589,shopping,,20,WALK_LOC,2.234018194162152,1766908714 +5386916,2222604,shopping,1,False,3,25000,20000,220863589,shopping,35.84551495448149,21,WALK_LRF,1.330786295384866,1766908717 +5386916,2222604,shopping,2,False,3,10000,25000,220863589,eatout,52.3743087314661,21,WALK_LOC,11.678738756115026,1766908718 +5386916,2222604,shopping,3,False,3,7000,10000,220863589,home,,21,WALK_LOC,9.976614394034591,1766908719 +5386916,2222604,work,1,True,1,10000,7000,220863595,work,,8,WALK_LOC,8.192659828887404,1766908761 +5386916,2222604,work,1,False,1,7000,10000,220863595,home,,18,WALK_LOC,8.188826447054582,1766908765 +5386917,2222604,work,1,True,1,19000,7000,220863636,work,,8,WALK_LOC,0.1911770675662809,1766909089 +5386917,2222604,work,1,False,2,7000,19000,220863636,othmaint,31.6003322795588,18,WALK,0.1437692426814362,1766909093 +5386917,2222604,work,2,False,2,7000,7000,220863636,home,,18,WALK,11.32236685212627,1766909094 +5387114,2222703,othdiscr,1,True,2,8000,7000,220871699,shopping,38.02242004489941,6,WALK,12.564286884276584,1766973593 +5387114,2222703,othdiscr,2,True,2,22000,8000,220871699,othdiscr,,7,WALK_LRF,2.805407913482188,1766973594 +5387114,2222703,othdiscr,1,False,1,7000,22000,220871699,home,,18,WALK_LOC,1.9273722416677583,1766973597 +5387114,2222703,othdiscr,1,True,1,6000,7000,220871700,othdiscr,,18,WALK,12.768489014109347,1766973601 +5387114,2222703,othdiscr,1,False,1,7000,6000,220871700,home,,18,WALK,12.712689042504248,1766973605 +5387114,2222703,work,1,True,1,4000,7000,220871713,work,,19,TNC_SINGLE,1.0831157444746895,1766973705 +5387114,2222703,work,1,False,1,7000,4000,220871713,home,,22,WALK,1.0793285014212155,1766973709 +5387115,2222703,eatout,1,True,1,14000,7000,220871721,eatout,,21,WALK_LOC,1.6853501652386769,1766973769 +5387115,2222703,eatout,1,False,1,7000,14000,220871721,home,,21,WALK_LOC,1.6973275752566237,1766973773 +5387115,2222703,work,1,True,2,7000,7000,220871754,shopping,31.69798103935113,9,WALK,11.322377732641469,1766974033 +5387115,2222703,work,2,True,2,13000,7000,220871754,work,,10,TNC_SINGLE,0.7778453750585694,1766974034 +5387115,2222703,work,1,False,2,9000,13000,220871754,othmaint,32.707495666775806,17,TNC_SINGLE,0.8285588262291184,1766974037 +5387115,2222703,work,2,False,2,7000,9000,220871754,home,,20,WALK,8.909402790957092,1766974038 +5388246,2223269,work,1,True,1,4000,10000,220918125,work,,9,WALK_LRF,0.7089583202667562,1767345001 +5388246,2223269,work,1,False,2,7000,4000,220918125,escort,31.994989861076217,15,WALK,0.5871608261082515,1767345005 +5388246,2223269,work,2,False,2,10000,7000,220918125,home,,19,WALK_LOC,11.34645373317459,1767345006 +5388247,2223269,atwork,1,True,1,2000,24000,220918131,atwork,,13,WALK,0.4219166577214059,1767345049 +5388247,2223269,atwork,1,False,1,24000,2000,220918131,work,,14,WALK,0.4444722615783372,1767345053 +5388247,2223269,work,1,True,1,24000,10000,220918166,work,,8,WALK,-0.0203809805190121,1767345329 +5388247,2223269,work,1,False,2,25000,24000,220918166,social,29.31818466200153,16,WALK,2.0913678350366944,1767345333 +5388247,2223269,work,2,False,2,10000,25000,220918166,home,,21,WALK,8.369527092415394,1767345334 +5388456,2223374,work,1,True,1,16000,11000,220926735,work,,9,WALK,4.434228526694411,1767413881 +5388456,2223374,work,1,False,1,11000,16000,220926735,home,,18,WALK,4.434228455705195,1767413885 +5388456,2223374,work,1,True,2,7000,11000,220926736,escort,36.15676938773793,18,WALK,10.587761425978275,1767413889 +5388456,2223374,work,2,True,2,16000,7000,220926736,work,,18,WALK,4.01623140579655,1767413890 +5388456,2223374,work,1,False,2,6000,16000,220926736,work,37.00996280650867,19,WALK,4.082231146080614,1767413893 +5388456,2223374,work,2,False,2,11000,6000,220926736,home,,19,WALK,9.254342916685156,1767413894 +5388457,2223374,work,1,True,1,23000,11000,220926776,work,,9,WALK_LOC,1.5921957770735595,1767414209 +5388457,2223374,work,1,False,1,11000,23000,220926776,home,,19,WALK_LOC,1.5919645118866783,1767414213 +5391136,2224714,atwork,1,True,1,13000,16000,221036580,atwork,,11,WALK,-0.4561262528247776,1768292641 +5391136,2224714,atwork,1,False,1,16000,13000,221036580,work,,13,WALK,-0.5045352445065882,1768292645 +5391136,2224714,eatout,1,True,1,22000,17000,221036582,eatout,,20,WALK_LRF,2.6683208551888327,1768292657 +5391136,2224714,eatout,1,False,1,17000,22000,221036582,home,,21,WALK_LRF,2.9626477168303618,1768292661 +5391136,2224714,work,1,True,4,6000,17000,221036615,work,42.3757416289184,8,WALK,9.44870723053955,1768292921 +5391136,2224714,work,2,True,4,8000,6000,221036615,escort,39.33637730721234,8,WALK,10.098789204079656,1768292922 +5391136,2224714,work,3,True,4,25000,8000,221036615,escort,38.8253097945831,9,WALK,10.453953179773286,1768292923 +5391136,2224714,work,4,True,4,16000,25000,221036615,work,,11,WALK_LOC,5.609497297787802,1768292924 +5391136,2224714,work,1,False,1,17000,16000,221036615,home,,17,WALK_LRF,6.375393830472105,1768292925 +5391137,2224714,work,1,True,1,2000,17000,221036656,work,,5,WALK_LRF,0.4679223024855918,1768293249 +5391137,2224714,work,1,False,4,6000,2000,221036656,othdiscr,29.24742791838357,16,WALK,0.3715206518006981,1768293253 +5391137,2224714,work,2,False,4,10000,6000,221036656,shopping,42.6597481033155,17,WALK_LOC,10.068026158792334,1768293254 +5391137,2224714,work,3,False,4,7000,10000,221036656,shopping,44.11765347097764,17,WALK,8.287106531455793,1768293255 +5391137,2224714,work,4,False,4,17000,7000,221036656,home,,18,WALK,10.823184515975516,1768293256 +7452651,2761316,othdiscr,1,True,1,14000,10000,305558716,othdiscr,,16,WALK_LRF,2.1434224937878565,2444469729 +7452651,2761316,othdiscr,1,False,1,10000,14000,305558716,home,,22,WALK_LRF,2.09912851886251,2444469733 +7452651,2761316,othmaint,1,True,1,2000,10000,305558719,othmaint,,5,WALK_LRF,0.8813635609701315,2444469753 +7452651,2761316,othmaint,1,False,1,10000,2000,305558719,home,,13,WALK_LRF,0.9656586006325034,2444469757 +7452726,2761391,shopping,1,True,2,8000,10000,305561799,shopping,41.14610613733738,12,WALK,11.654724087483617,2444494393 +7452726,2761391,shopping,2,True,2,16000,8000,305561799,shopping,,12,WALK,5.348165111115939,2444494394 +7452726,2761391,shopping,1,False,2,7000,16000,305561799,escort,44.24836557044917,15,WALK,5.680632173681755,2444494397 +7452726,2761391,shopping,2,False,2,10000,7000,305561799,home,,15,WALK,13.42152726365645,2444494398 +7453556,2762221,othdiscr,1,True,1,5000,21000,305595821,othdiscr,,14,WALK_LOC,4.690570517977736,2444766569 +7453556,2762221,othdiscr,1,False,1,21000,5000,305595821,home,,18,WALK,4.678949130107614,2444766573 diff --git a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py index 3e956301e9..08ff1d6c89 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py +++ b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py @@ -39,40 +39,36 @@ def data(): build_data() -def run_test(zone, multiprocess=False): +def run_test(zone, multiprocess=False, use_explicit_error_terms=False): def test_path(dirname): return os.path.join(os.path.dirname(__file__), dirname) - def regress(zone): + def regress(zone, use_explicit_error_terms=False): # regress tours - regress_tours_df = pd.read_csv( - test_path(f"regress/final_tours_{zone}_zone.csv") - ) + regress_tours_df = pd.read_csv(test_path(f"regress/final{'_eet' if use_explicit_error_terms else ''}_tours_{zone}_zone.csv")) tours_df = pd.read_csv(test_path("output/final_tours.csv")) - tours_df.to_csv( - test_path(f"regress/final_tours_{zone}_zone_last_run.csv"), index=False - ) + tours_df.to_csv(test_path(f"regress/final_tours_{zone}_zone_last_run.csv"), index=False) print("regress tours") - test.assert_frame_substantively_equal( - tours_df, regress_tours_df, rtol=1e-03, check_dtype=False - ) + test.assert_frame_substantively_equal(tours_df, regress_tours_df, rtol=1e-03, check_dtype=False) # regress trips - regress_trips_df = pd.read_csv( - test_path(f"regress/final_trips_{zone}_zone.csv") - ) + regress_trips_df = pd.read_csv(test_path(f"regress/final{'_eet' if use_explicit_error_terms else ''}_trips_{zone}_zone.csv")) trips_df = pd.read_csv(test_path("output/final_trips.csv")) - trips_df.to_csv( - test_path(f"regress/final_trips_{zone}_zone_last_run.csv"), index=False - ) + trips_df.to_csv(test_path(f"regress/final_trips_{zone}_zone_last_run.csv"), index=False) print("regress trips") - test.assert_frame_substantively_equal( - trips_df, regress_trips_df, rtol=1e-03, check_dtype=False - ) + test.assert_frame_substantively_equal(trips_df, regress_trips_df, rtol=1e-03, check_dtype=False) file_path = os.path.join(os.path.dirname(__file__), "simulation.py") + test_config_files = [] + if use_explicit_error_terms: + test_config_files = [ + "-c", + test_path("configs_eet"), + ] + run_args = [ + *test_config_files, "-c", test_path(f"configs_{zone}_zone"), "-c", @@ -95,7 +91,7 @@ def regress(zone): else: subprocess.run([sys.executable, file_path] + run_args, check=True) - regress(zone) + regress(zone, use_explicit_error_terms=use_explicit_error_terms) def test_2_zone(data): @@ -106,6 +102,14 @@ def test_2_zone_mp(data): run_test(zone="2", multiprocess=True) +def test_2_zone_eet(data): + run_test(zone="2", multiprocess=False, use_explicit_error_terms=True) + + +def test_2_zone_mp_eet(data): + run_test(zone="2", multiprocess=True, use_explicit_error_terms=True) + + def test_3_zone(data): # python simulation.py -c configs_3_zone -c ../configs_3_zone -c \ # ../../prototype_mtc/configs -d ../data_3 -o output -s settings_mp @@ -184,9 +188,7 @@ def test_path(dirname): assert state.settings.sharrow == False state.settings.trace_hh_id = 1099626 - state.tracing.validation_directory = ( - Path(__file__).parent / "reference_trace_2_zone" - ) + state.tracing.validation_directory = Path(__file__).parent / "reference_trace_2_zone" for step_name in EXPECTED_MODELS: state.run.by_name(step_name) @@ -204,8 +206,12 @@ def test_path(dirname): if __name__ == "__main__": build_data() + run_test(zone="2", multiprocess=False) run_test(zone="2", multiprocess=True) - run_test(zone="3", multiprocess=False) - run_test(zone="3", multiprocess=True) + run_test(zone="2", multiprocess=False, use_explicit_error_terms=True) + run_test(zone="2", multiprocess=True, use_explicit_error_terms=True) + + # run_test(zone="3", multiprocess=False) + # run_test(zone="3", multiprocess=True) From f5b63014859a43c390f37bf6c1e54508f07efbfc Mon Sep 17 00:00:00 2001 From: Tyler Pearn Date: Tue, 7 Apr 2026 12:47:25 +1000 Subject: [PATCH 192/298] Change formatting from using ruff to black --- .../test/test_multiple_zone.py | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py index 08ff1d6c89..475c6df752 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py +++ b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py @@ -45,18 +45,34 @@ def test_path(dirname): def regress(zone, use_explicit_error_terms=False): # regress tours - regress_tours_df = pd.read_csv(test_path(f"regress/final{'_eet' if use_explicit_error_terms else ''}_tours_{zone}_zone.csv")) + regress_tours_df = pd.read_csv( + test_path( + f"regress/final{'_eet' if use_explicit_error_terms else ''}_tours_{zone}_zone.csv" + ) + ) tours_df = pd.read_csv(test_path("output/final_tours.csv")) - tours_df.to_csv(test_path(f"regress/final_tours_{zone}_zone_last_run.csv"), index=False) + tours_df.to_csv( + test_path(f"regress/final_tours_{zone}_zone_last_run.csv"), index=False + ) print("regress tours") - test.assert_frame_substantively_equal(tours_df, regress_tours_df, rtol=1e-03, check_dtype=False) + test.assert_frame_substantively_equal( + tours_df, regress_tours_df, rtol=1e-03, check_dtype=False + ) # regress trips - regress_trips_df = pd.read_csv(test_path(f"regress/final{'_eet' if use_explicit_error_terms else ''}_trips_{zone}_zone.csv")) + regress_trips_df = pd.read_csv( + test_path( + f"regress/final{'_eet' if use_explicit_error_terms else ''}_trips_{zone}_zone.csv" + ) + ) trips_df = pd.read_csv(test_path("output/final_trips.csv")) - trips_df.to_csv(test_path(f"regress/final_trips_{zone}_zone_last_run.csv"), index=False) + trips_df.to_csv( + test_path(f"regress/final_trips_{zone}_zone_last_run.csv"), index=False + ) print("regress trips") - test.assert_frame_substantively_equal(trips_df, regress_trips_df, rtol=1e-03, check_dtype=False) + test.assert_frame_substantively_equal( + trips_df, regress_trips_df, rtol=1e-03, check_dtype=False + ) file_path = os.path.join(os.path.dirname(__file__), "simulation.py") @@ -162,8 +178,6 @@ def test_3_zone_mp(data): def test_multizone_progressive(zone="2"): zone = str(zone) - import activitysim.abm # register components - def test_path(dirname): return os.path.join(os.path.dirname(__file__), dirname) @@ -188,7 +202,9 @@ def test_path(dirname): assert state.settings.sharrow == False state.settings.trace_hh_id = 1099626 - state.tracing.validation_directory = Path(__file__).parent / "reference_trace_2_zone" + state.tracing.validation_directory = ( + Path(__file__).parent / "reference_trace_2_zone" + ) for step_name in EXPECTED_MODELS: state.run.by_name(step_name) From 5b24d44ec4d3ed50d902b244995f0f4eda6e2843 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 7 Apr 2026 19:11:51 +1000 Subject: [PATCH 193/298] add back accidentally removed import --- .../placeholder_multiple_zone/test/test_multiple_zone.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py index 475c6df752..daf24aac72 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py +++ b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py @@ -178,6 +178,8 @@ def test_3_zone_mp(data): def test_multizone_progressive(zone="2"): zone = str(zone) + import activitysim.abm # register components + def test_path(dirname): return os.path.join(os.path.dirname(__file__), dirname) From 9661d01c9c7c5802e42fbbedaa9668b6e7fbda8f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 7 Apr 2026 19:17:37 +1000 Subject: [PATCH 194/298] re-add 3-zone tests --- .../placeholder_multiple_zone/test/test_multiple_zone.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py index daf24aac72..aa4ccf174a 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py +++ b/activitysim/examples/placeholder_multiple_zone/test/test_multiple_zone.py @@ -231,5 +231,5 @@ def test_path(dirname): run_test(zone="2", multiprocess=False, use_explicit_error_terms=True) run_test(zone="2", multiprocess=True, use_explicit_error_terms=True) - # run_test(zone="3", multiprocess=False) - # run_test(zone="3", multiprocess=True) + run_test(zone="3", multiprocess=False) + run_test(zone="3", multiprocess=True) From 7c96cbc69c8c273cc2f6ef48fc1836c7acdad124 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 7 Apr 2026 20:08:58 +1000 Subject: [PATCH 195/298] base seed same as non-eet, update regress eet data --- .../test/configs_eet/settings.yaml | 2 - .../test/regress/final_eet_tours_2_zone.csv | 203 +++---- .../test/regress/final_eet_trips_2_zone.csv | 499 +++++++++--------- 3 files changed, 367 insertions(+), 337 deletions(-) diff --git a/activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml b/activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml index dcff83f5a1..08c06d702e 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml +++ b/activitysim/examples/placeholder_multiple_zone/test/configs_eet/settings.yaml @@ -1,5 +1,3 @@ inherit_settings: True use_explicit_error_terms: True - -rng_base_seed: 42 diff --git a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv index c9002cb44a..f11982ae5f 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv +++ b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv @@ -1,97 +1,106 @@ -person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,tour_category,number_of_participants,destination,origin,household_id,tdd,start,end,duration,composition,destination_logsum,tour_mode,mode_choice_logsum,atwork_subtour_frequency,parent_tour_id,stop_frequency,primary_purpose,tour_id -25872,eatout,1,1,1,1,non_mandatory,1,25000,7000,25872,151,15,21,6,,15.324608541661584,WALK,3.925083327622137,,,0out_0in,eatout,1060758 -26103,shopping,1,1,1,1,non_mandatory,1,8000,8000,26103,158,16,20,4,,13.631528588460649,WALK,2.172338551666808,,,0out_0in,shopping,1070256 -26143,shopping,1,1,1,1,non_mandatory,1,19000,8000,26143,113,12,13,1,,14.112489043827686,WALK_LOC,1.756679185311168,,,0out_1in,shopping,1071896 -27412,school,2,1,1,2,mandatory,1,9000,9000,27412,71,9,10,1,,,WALK,5.839003987151259,,,0out_0in,univ,1123923 -27412,school,2,2,2,2,mandatory,1,9000,9000,27412,162,17,17,0,,,BIKE,5.839367147058163,,,3out_0in,univ,1123924 -27415,othmaint,1,1,1,1,non_mandatory,1,16000,9000,27415,44,7,14,7,,15.167583473532224,BIKE,1.4085740306092538,,,0out_0in,othmaint,1124043 -27683,othdiscr,1,1,1,1,non_mandatory,1,9000,10000,27683,74,9,13,4,,15.186442161459588,WALK_LOC,2.846096860674467,,,0out_3in,othdiscr,1135028 -107628,work,1,1,1,1,mandatory,1,12000,6000,107628,92,10,17,7,,,WALK_LOC,5.416481898231418,no_subtours,,0out_0in,work,4412787 -112666,othmaint,1,1,1,1,non_mandatory,1,1000,17000,112666,113,12,13,1,,14.889102700398078,WALK,1.92232729195762,,,0out_0in,othmaint,4619334 -112666,work,1,1,1,1,mandatory,1,2000,17000,112666,166,17,21,4,,,WALK_LRF,5.570170712509852,no_subtours,,0out_0in,work,4619345 -112785,eatout,1,1,1,1,non_mandatory,1,11000,19000,112785,181,20,21,1,,12.710725643768637,SHARED3FREE,0.2552055851917118,,,0out_0in,eatout,4624191 -112785,work,1,1,1,1,mandatory,1,21000,19000,112785,31,6,18,12,,,DRIVEALONEFREE,0.2016161432182198,no_subtours,,0out_0in,work,4624224 -112977,work,1,1,1,1,mandatory,1,12000,21000,112977,63,8,17,9,,,WALK,5.4491289992033805,no_subtours,,2out_3in,work,4632096 -264055,escort,1,1,1,1,non_mandatory,1,20000,9000,226843,19,6,6,0,,12.41661858443438,SHARED3FREE,-0.767103496383878,,,0out_0in,escort,10826264 -264056,work,1,1,1,1,mandatory,1,10000,9000,226843,9,5,14,9,,,WALK_LOC,0.2984357626179245,no_subtours,,0out_0in,work,10826335 -323181,shopping,1,1,1,1,joint,2,11000,10000,256406,127,13,16,3,adults,13.837057469779358,WALK,-0.0206798576468464,,,0out_1in,shopping,13250440 -323181,work,1,1,1,1,mandatory,1,9000,10000,256406,42,7,12,5,,,TNC_SINGLE,5.991138629031954,no_subtours,,0out_0in,work,13250460 -323182,work,1,1,1,1,mandatory,1,2000,10000,256406,158,16,20,4,,,WALK_LRF,6.136056448795052,no_subtours,,0out_0in,work,13250501 -325309,work,1,1,1,1,mandatory,1,19000,16000,257470,26,6,13,7,,,WALK_LOC,3.4843919117072377,no_subtours,,0out_0in,work,13337708 -325429,work,1,1,1,1,mandatory,1,5000,16000,257530,64,8,18,10,,,WALK,6.140255328078286,no_subtours,,1out_0in,work,13342628 -325430,eat,1,1,1,1,atwork,1,1000,23000,257530,101,11,13,2,,14.870319018560831,TNC_SHARED,5.355407771372916,,13342669.0,0out_0in,atwork,13342634 -325430,othdiscr,1,1,1,1,non_mandatory,1,16000,16000,257530,158,16,20,4,,15.333041399718818,WALK,2.930679229042425,,,0out_0in,othdiscr,13342655 -325430,work,1,1,1,1,mandatory,1,23000,16000,257530,29,6,16,10,,,TNC_SINGLE,6.28069576419515,eat,,0out_0in,work,13342669 -644437,work,1,1,1,1,mandatory,1,24000,9000,386748,65,8,19,11,,,WALK_LRF,5.654556608152202,no_subtours,,0out_0in,work,26421956 -644438,shopping,1,1,1,1,non_mandatory,1,16000,9000,386748,91,10,16,6,,13.77366143367142,WALK_LRF,0.9593933036319412,,,1out_0in,shopping,26421991 -644439,work,1,1,1,1,mandatory,1,22000,9000,386748,10,5,15,10,,,WALK_LRF,5.664941612162526,no_subtours,,0out_0in,work,26422038 -1264881,othdiscr,1,1,1,1,non_mandatory,1,18000,10000,567768,159,16,21,5,,15.225973212134,WALK_LRF,2.039609656048619,,,0out_0in,othdiscr,51860146 -1265935,escort,1,1,1,2,non_mandatory,1,8000,17000,568822,170,18,19,1,,12.359706844091942,WALK,-1.630686893624885,,,0out_0in,escort,51903344 -1265935,othmaint,1,1,2,2,non_mandatory,1,21000,17000,568822,102,11,14,3,,13.737651385285645,WALK_LRF,-0.4649705484292334,,,0out_0in,othmaint,51903363 -1356554,shopping,1,1,1,1,non_mandatory,1,13000,22000,659441,64,8,18,10,,13.88685165306392,BIKE,1.5870038941022344,,,0out_0in,shopping,55618747 -1632055,eat,1,1,1,1,atwork,1,12000,11000,823275,85,10,10,0,,15.6610529510408,WALK,5.897877376563244,,66914294.0,0out_0in,atwork,66914259 -1632055,work,1,1,1,1,mandatory,1,11000,10000,823275,45,7,15,8,,,WALK,6.0466798507502375,eat,,0out_0in,work,66914294 -1632179,work,1,1,1,1,mandatory,1,23000,11000,823399,109,11,21,10,,,WALK_LOC,-0.069261093997962,no_subtours,,1out_1in,work,66919378 -1774491,work,1,1,1,1,mandatory,1,7000,9000,932260,45,7,15,8,,,WALK_LOC,6.153116921757978,no_subtours,,0out_0in,work,72754170 -1774492,shopping,1,1,1,1,non_mandatory,1,16000,9000,932260,113,12,13,1,,14.197083405037375,WALK_HVY,2.643876882714501,,,1out_0in,shopping,72754205 -1876395,work,1,1,1,1,mandatory,1,13000,21000,983212,31,6,18,12,,,TNC_SINGLE,5.289238909714063,no_subtours,,0out_1in,work,76932234 -2071280,work,1,1,1,1,mandatory,1,16000,21000,1070367,62,8,16,8,,,WALK,1.7053738749378673,no_subtours,,0out_0in,work,84922519 -2071281,work,1,1,1,1,mandatory,1,5000,21000,1070367,74,9,13,4,,,WALK,2.0147118600827216,no_subtours,,0out_1in,work,84922560 -2220332,work,1,1,1,1,mandatory,1,11000,20000,1120051,77,9,16,7,,,SHARED3FREE,1.7010760839664214,no_subtours,,1out_1in,work,91033651 -2220333,othmaint,1,1,1,1,non_mandatory,1,7000,20000,1120051,151,15,21,6,,14.014606142053264,TNC_SINGLE,0.149491264831688,,,0out_2in,othmaint,91033681 -2220333,work,1,1,1,1,mandatory,1,14000,20000,1120051,28,6,15,9,,,WALK_HVY,1.5213713475726636,no_subtours,,0out_0in,work,91033692 -2220334,school,1,1,1,1,mandatory,1,9000,20000,1120051,66,8,20,12,,,WALK_LOC,1.991771241543342,,,0out_0in,school,91033725 -2746929,eat,1,1,1,1,atwork,1,10000,21000,1234121,162,17,17,0,,12.71430384191495,WALK,-0.5268164634483682,,112624128.0,0out_0in,atwork,112624093 -2746932,shopping,1,1,1,1,joint,2,1000,11000,1234121,162,17,17,0,children,13.38338375038534,SHARED2FREE,-0.6867048669480782,,,0out_0in,shopping,112624108 -2746929,work,1,1,1,1,mandatory,1,21000,11000,1234121,30,6,17,11,,,WALK,1.6514348701205426,eat,,0out_0in,work,112624128 -2746930,eatout,1,1,1,1,non_mandatory,1,17000,11000,1234121,90,10,15,5,,14.10283915060394,WALK,1.3369363207414489,,,0out_0in,eatout,112624136 -2746931,work,1,1,1,1,mandatory,1,20000,11000,1234121,50,7,20,13,,,WALK,1.4578058558199158,no_subtours,,0out_0in,work,112624210 -2746932,school,1,1,1,1,mandatory,1,8000,11000,1234121,47,7,17,10,,,WALK,2.0202226257018245,,,0out_0in,school,112624243 -2746934,school,1,1,1,1,mandatory,1,21000,11000,1234121,92,10,17,7,,,WALK,1.767745192104871,,,0out_0in,school,112624325 -2936912,shopping,1,1,1,1,non_mandatory,1,16000,17000,1286621,143,14,22,8,,12.720501141423998,WALK_LRF,-0.1971228190146473,,,0out_0in,shopping,120413425 -2957530,work,1,1,1,1,mandatory,1,19000,21000,1307239,107,11,19,8,,,WALK_LOC,5.394901446058748,no_subtours,,0out_0in,work,121258769 -3112851,work,1,1,1,1,mandatory,1,2000,25000,1384946,108,11,20,9,,,TNC_SINGLE,5.857655520298795,no_subtours,,0out_1in,work,127626930 -3328643,shopping,1,1,1,1,non_mandatory,1,19000,9000,1511271,62,8,16,8,,12.599796366663016,WALK,-0.9841036472591292,,,0out_0in,shopping,136474396 -3495346,eat,1,1,1,1,atwork,1,11000,5000,1594623,135,14,14,0,,15.796876863416736,TNC_SINGLE,5.699539647746939,,143309225.0,0out_0in,atwork,143309190 -3495346,work,1,1,1,1,mandatory,1,5000,10000,1594623,51,7,21,14,,,WALK,5.718922382923123,eat,,0out_0in,work,143309225 -3495347,othmaint,1,1,2,2,non_mandatory,1,12000,10000,1594623,86,10,11,1,,15.25941622676957,TAXI,1.8490728888043824,,,0out_0in,othmaint,143309255 -3495347,shopping,1,1,1,2,non_mandatory,1,1000,10000,1594623,99,11,11,0,,13.9649849918008,WALK_HVY,2.463240708785933,,,0out_0in,shopping,143309260 -3495347,work,1,1,1,1,mandatory,1,9000,10000,1594623,119,12,19,7,,,WALK,6.084928426498565,no_subtours,,0out_0in,work,143309266 -3496420,eat,1,1,1,1,atwork,1,12000,16000,1595160,87,10,12,2,,12.795015949891248,WALK,-0.259760437162128,,143353259.0,0out_0in,atwork,143353224 -3496420,work,1,1,1,1,mandatory,1,16000,17000,1595160,15,5,20,15,,,WALK,2.101751268888541,eat,,0out_0in,work,143353259 -3496421,work,1,1,1,1,mandatory,1,16000,17000,1595160,48,7,18,11,,,WALK_LOC,2.142007829019355,no_subtours,,0out_0in,work,143353300 -3608536,work,1,1,1,1,mandatory,1,13000,9000,1651218,47,7,17,10,,,WALK_LOC,6.27632317344463,no_subtours,,0out_0in,work,147950015 -3608537,school,1,1,1,1,mandatory,1,13000,9000,1651218,47,7,17,10,,,WALK_LRF,22.088164625699857,,,0out_0in,school,147950048 -4762866,school,1,1,1,1,mandatory,1,17000,16000,1931922,59,8,13,5,,,WALK_LRF,19.77600466914122,,,0out_0in,school,195277537 -4762869,school,1,1,1,1,mandatory,1,17000,16000,1931922,61,8,15,7,,,WALK_LRF,19.776004668973645,,,0out_0in,school,195277660 -4762871,shopping,1,1,1,1,non_mandatory,1,13000,16000,1931922,113,12,13,1,,13.8200995095236,WALK,2.023921299435018,,,0out_0in,shopping,195277744 -4762872,work,1,1,1,1,mandatory,1,1000,16000,1931922,80,9,19,10,,,WALK,5.6129430623808885,no_subtours,,0out_0in,work,195277791 -4762874,school,1,1,1,1,mandatory,1,16000,16000,1931922,44,7,14,7,,,WALK,17.69297933376892,,,0out_0in,school,195277865 -4762875,school,1,1,1,1,mandatory,1,16000,16000,1931922,57,8,11,3,,,WALK,17.692979337157823,,,0out_0in,school,195277906 -4817011,social,2,1,1,2,non_mandatory,1,7000,8000,1946006,113,12,13,1,,14.214418678632692,WALK,2.01539190342834,,,0out_0in,social,197497487 -4817011,social,2,2,2,2,non_mandatory,1,1000,8000,1946006,137,14,16,2,,14.19926277967054,WALK_LOC,1.1067525270395056,,,0out_0in,social,197497488 -4950606,school,1,1,1,1,mandatory,1,7000,8000,2010083,67,8,21,13,,,WALK_LOC,20.09206099292637,,,0out_0in,school,202974877 -5057667,work,1,1,1,1,mandatory,1,5000,11000,2048711,65,8,19,11,,,WALK,-0.3438284257928611,no_subtours,,0out_0in,work,207364386 -5386916,eat,1,1,1,1,atwork,1,14000,10000,2222604,124,13,13,0,,15.23972998434972,WALK_HVY,4.763449153784629,,220863595.0,0out_1in,atwork,220863560 -5386916,shopping,1,1,1,1,non_mandatory,1,20000,7000,2222604,177,19,21,2,,13.804209608835082,TNC_SINGLE,1.5539907867200995,,,1out_2in,shopping,220863589 -5386916,work,1,1,1,1,mandatory,1,10000,7000,2222604,64,8,18,10,,,WALK_LOC,5.700023979202434,eat,,0out_0in,work,220863595 -5386917,work,1,1,1,1,mandatory,1,19000,7000,2222604,64,8,18,10,,,TAXI,5.19733294875638,no_subtours,,0out_1in,work,220863636 -5387114,othdiscr,2,1,1,2,non_mandatory,1,22000,7000,2222703,31,6,18,12,,15.39365106527831,WALK_LRF,2.6851963090850903,,,1out_0in,othdiscr,220871699 -5387114,othdiscr,2,2,2,2,non_mandatory,1,6000,7000,2222703,169,18,18,0,,15.500122017723845,WALK,3.332822704108827,,,0out_0in,othdiscr,220871700 -5387114,work,1,1,1,1,mandatory,1,4000,7000,2222703,178,19,22,3,,,TNC_SINGLE,6.115658033572405,no_subtours,,0out_0in,work,220871713 -5387115,eatout,1,1,1,1,non_mandatory,1,14000,7000,2222703,184,21,21,0,,15.608709839039491,TNC_SHARED,3.486447403367392,,,0out_0in,eatout,220871721 -5387115,work,1,1,1,1,mandatory,1,13000,7000,2222703,81,9,20,11,,,TNC_SINGLE,5.995379715129444,no_subtours,,1out_1in,work,220871754 -5388246,work,1,1,1,1,mandatory,1,4000,10000,2223269,80,9,19,10,,,WALK_HVY,1.8988665278205443,no_subtours,,0out_1in,work,220918125 -5388247,eat,1,1,1,1,atwork,1,2000,24000,2223269,125,13,14,1,,12.733618518169584,WALK,0.5490051925973694,,220918166.0,0out_0in,atwork,220918131 -5388247,work,1,1,1,1,mandatory,1,24000,10000,2223269,67,8,21,13,,,WALK,1.4444587506481184,eat,,0out_1in,work,220918166 -5388456,work,2,1,1,2,mandatory,1,16000,11000,2223374,79,9,18,9,,,WALK,1.6463809900728428,no_subtours,,0out_0in,work,220926735 -5388456,work,2,2,2,2,mandatory,1,16000,11000,2223374,170,18,19,1,,,WALK,1.7200593221990077,no_subtours,,1out_1in,work,220926736 -5388457,work,1,1,1,1,mandatory,1,23000,11000,2223374,80,9,19,10,,,WALK_LOC,1.3918688641625712,no_subtours,,0out_0in,work,220926776 -5391136,eat,1,1,1,1,atwork,1,13000,16000,2224714,101,11,13,2,,12.823885233906973,WALK,0.1028337439895324,,221036615.0,0out_0in,atwork,221036580 -5391136,eatout,1,1,1,1,non_mandatory,1,22000,17000,2224714,181,20,21,1,,13.796840766625918,WALK_LRF,1.3381911203468422,,,0out_0in,eatout,221036582 -5391136,work,1,1,1,1,mandatory,1,16000,17000,2224714,63,8,17,9,,,WALK_LRF,2.230397835408683,eat,,3out_0in,work,221036615 -5391137,work,1,1,1,1,mandatory,1,2000,17000,2224714,13,5,18,13,,,WALK_LRF,1.993723549745754,no_subtours,,0out_3in,work,221036656 -7452651,othdiscr,1,1,2,2,non_mandatory,1,14000,10000,2761316,160,16,22,6,,14.980075296614151,WALK_LRF,2.5510853722692675,,,0out_0in,othdiscr,305558716 -7452651,othmaint,1,1,1,2,non_mandatory,1,2000,10000,2761316,8,5,13,8,,15.212337325011434,WALK_LRF,2.7654116278187377,,,0out_0in,othmaint,305558719 -7452726,shopping,1,1,1,1,non_mandatory,1,16000,10000,2761391,115,12,15,3,,13.809166893945894,SHARED2FREE,1.244280339251842,,,1out_1in,shopping,305561799 -7453556,othdiscr,1,1,1,1,non_mandatory,1,5000,21000,2762221,139,14,18,4,,13.940429525972068,WALK_LOC,0.4224525901427229,,,0out_0in,othdiscr,305595821 +"person_id","tour_type","tour_type_count","tour_type_num","tour_num","tour_count","tour_category","number_of_participants","destination","origin","household_id","tdd","start","end","duration","composition","destination_logsum","tour_mode","mode_choice_logsum","atwork_subtour_frequency","parent_tour_id","stop_frequency","primary_purpose","tour_id" +26686,"shopping",1,1,1,1,"non_mandatory",1,11000,8000,26686,113,12,13,1,"",13.652449170814883,"WALK_LOC",1.7684180579607391,"",,"0out_0in","shopping",1094159 +26844,"othmaint",1,1,2,2,"non_mandatory",1,22000,8000,26844,55,8,9,1,"",15.525049977674522,"WALK_HVY",3.1854706132644903,"",,"0out_0in","othmaint",1100632 +26844,"shopping",1,1,1,2,"non_mandatory",1,5000,8000,26844,75,9,14,5,"",14.275587915746392,"TNC_SINGLE",2.732845477677904,"",,"1out_0in","shopping",1100637 +27726,"eatout",1,1,1,1,"non_mandatory",1,5000,10000,27726,140,14,19,5,"",15.203007240171102,"WALK",3.7363777452833706,"",,"0out_0in","eatout",1136772 +110675,"work",1,1,1,1,"mandatory",1,15000,16000,110675,13,5,18,13,"",,"WALK",-0.42159955462256554,"no_subtours",,"0out_0in","work",4537714 +112064,"work",1,1,1,1,"mandatory",1,13000,16000,112064,131,13,20,7,"",,"WALK",5.407218415220296,"no_subtours",,"0out_0in","work",4594663 +264108,"eatout",1,1,1,1,"non_mandatory",1,8000,9000,226869,135,14,14,0,"",13.203967734929993,"WALK",1.0687903457949945,"",,"0out_0in","eatout",10828434 +323689,"work",1,1,1,1,"mandatory",1,2000,10000,256660,151,15,21,6,"",,"WALK_LRF",5.943591391097562,"no_subtours",,"1out_0in","work",13271288 +323690,"work",1,1,1,1,"mandatory",1,2000,10000,256660,117,12,17,5,"",,"WALK_LRF",5.965798646055105,"no_subtours",,"0out_1in","work",13271329 +325431,"othdiscr",1,1,1,1,"non_mandatory",1,16000,16000,257531,126,13,15,2,"",15.268405514775877,"WALK",2.844184852437722,"",,"0out_2in","othdiscr",13342696 +325431,"work",1,1,1,1,"mandatory",1,14000,16000,257531,157,16,19,3,"",,"WALK_LOC",5.979950151025447,"no_subtours",,"0out_0in","work",13342710 +325432,"work",1,1,1,1,"mandatory",1,15000,16000,257531,45,7,15,8,"",,"WALK_LOC",5.9577256558570015,"no_subtours",,"0out_0in","work",13342751 +595684,"escort",1,1,1,1,"non_mandatory",1,8000,21000,370497,54,8,8,0,"",12.420811407080112,"SHARED2FREE",-0.9470591367042621,"",,"0out_0in","escort",24423053 +595684,"work",1,1,1,1,"mandatory",1,2000,21000,370497,167,17,22,5,"",,"SHARED2FREE",-0.5009192212754412,"no_subtours",,"3out_0in","work",24423083 +595685,"school",1,1,1,1,"mandatory",1,13000,21000,370497,61,8,15,7,"",,"WALK_LOC",-0.9348277771487147,"",,"0out_0in","school",24423116 +595686,"school",1,1,1,1,"mandatory",1,8000,21000,370497,41,7,11,4,"",,"WALK",-0.3860599444642998,"",,"0out_0in","school",24423157 +644292,"school",1,1,1,1,"mandatory",1,7000,7000,386699,43,7,13,6,"",,"WALK",18.278176688596677,"",,"0out_0in","school",26416003 +644476,"work",1,1,1,1,"mandatory",1,24000,16000,386761,47,7,17,10,"",,"WALK_LOC",5.562608108861799,"no_subtours",,"0out_0in","work",26423555 +644477,"work",1,1,1,1,"mandatory",1,4000,16000,386761,64,8,18,10,"",,"WALK_LOC",5.662108100914558,"no_subtours",,"0out_2in","work",26423596 +644478,"school",1,1,1,1,"mandatory",1,10000,16000,386761,69,8,23,15,"",,"WALK_LRF",20.092538140691808,"",,"1out_2in","school",26423629 +1267567,"eatout",1,1,1,1,"non_mandatory",1,9000,21000,570454,99,11,11,0,"",15.277431294707508,"WALK",3.492840343241245,"",,"0out_0in","eatout",51970253 +1427193,"shopping",1,1,1,1,"non_mandatory",1,25000,25000,703381,151,15,21,6,"",13.171561237606278,"BIKE",2.3848167165054392,"",,"0out_0in","shopping",58514946 +1427194,"othmaint",3,1,1,3,"non_mandatory",1,15000,25000,703381,74,9,13,4,"",14.416957607852858,"BIKE",0.7223665236701724,"",,"0out_0in","othmaint",58514982 +1427194,"othmaint",3,2,2,3,"non_mandatory",1,8000,25000,703381,137,14,16,2,"",14.365283875141941,"BIKE",0.6989899059768152,"",,"0out_0in","othmaint",58514983 +1427194,"othmaint",3,3,3,3,"non_mandatory",1,22000,25000,703381,158,16,20,4,"",14.373474385937751,"WALK_LOC",1.0739647167756625,"",,"0out_0in","othmaint",58514984 +1572659,"othdiscr",1,1,1,1,"non_mandatory",1,7000,6000,763879,8,5,13,8,"",15.269403956266437,"WALK",3.1705302835155984,"",,"0out_0in","othdiscr",64479044 +1572930,"eatout",1,1,1,1,"non_mandatory",1,9000,9000,764150,46,7,16,9,"",15.42100489856711,"WALK",4.672130640677754,"",,"0out_0in","eatout",64490136 +1632206,"work",1,1,1,1,"mandatory",1,1000,11000,823426,48,7,18,11,"",,"WALK",-0.3641280958116978,"no_subtours",,"0out_0in","work",66920485 +1632281,"work",1,1,1,1,"mandatory",1,9000,12000,823501,64,8,18,10,"",,"WALK_LRF",0.29606718088836376,"no_subtours",,"0out_0in","work",66923560 +1632987,"eat",1,1,1,1,"atwork",1,17000,13000,824207,85,10,10,0,"",15.623314633798255,"TNC_SINGLE",5.632004217633366,"",66952506,"0out_0in","atwork",66952471 +1632987,"work",1,1,1,1,"mandatory",1,13000,18000,824207,50,7,20,13,"",,"WALK_LOC",5.661127847871568,"eat",,"0out_0in","work",66952506 +1875721,"work",1,1,1,1,"mandatory",1,13000,16000,982875,49,7,19,12,"",,"SHARED3FREE",1.8204392988937443,"no_subtours",,"0out_0in","work",76904600 +1875722,"work",1,1,1,1,"mandatory",1,10000,16000,982875,48,7,18,11,"",,"WALK",0.9560253327586818,"no_subtours",,"0out_0in","work",76904641 +2159057,"work",1,1,1,1,"mandatory",1,2000,20000,1099626,47,7,17,10,"",,"BIKE",-0.018005979566901125,"no_subtours",,"0out_0in","work",88521376 +2159058,"school",1,1,1,1,"mandatory",1,9000,20000,1099626,44,7,14,7,"",,"WALK_LOC",0.41321524491499617,"",,"0out_0in","univ",88521409 +2159059,"school",1,1,1,1,"mandatory",1,17000,20000,1099626,61,8,15,7,"",,"SHARED2FREE",-0.5795062930092344,"",,"0out_0in","school",88521450 +2458500,"othdiscr",1,1,1,1,"non_mandatory",1,8000,8000,1173905,126,13,15,2,"",15.003025752404032,"TAXI",2.93488859286016,"",,"0out_0in","othdiscr",100798525 +2458502,"school",1,1,1,1,"mandatory",1,21000,8000,1173905,76,9,15,6,"",,"WALK_LOC",18.701678075333955,"",,"0out_0in","school",100798613 +2458503,"school",1,1,1,1,"mandatory",1,9000,8000,1173905,63,8,17,9,"",,"WALK",17.970541962349422,"",,"0out_0in","school",100798654 +2566698,"othmaint",1,1,1,1,"non_mandatory",1,17000,25000,1196298,136,14,15,1,"",13.904676146693486,"WALK",-0.224827552799558,"",,"0out_0in","othmaint",105234646 +2566698,"work",1,1,1,1,"mandatory",1,11000,25000,1196298,42,7,12,5,"",,"DRIVEALONEFREE",0.08799545142642082,"no_subtours",,"1out_2in","work",105234657 +2566699,"escort",2,1,1,4,"non_mandatory",1,5000,25000,1196298,55,8,9,1,"",12.487156714808382,"SHARED3FREE",-0.740305374605937,"",,"0out_0in","escort",105234668 +2566699,"escort",2,2,2,4,"non_mandatory",1,2000,25000,1196298,172,18,21,3,"",12.473008939270755,"WALK",-0.8431219959781476,"",,"0out_0in","escort",105234669 +2566699,"othdiscr",1,1,4,4,"non_mandatory",1,2000,25000,1196298,87,10,12,2,"",13.96308058011918,"WALK",0.8834911738561101,"",,"0out_0in","othdiscr",105234684 +2566699,"shopping",1,1,3,4,"non_mandatory",1,12000,25000,1196298,117,12,17,5,"",12.746190439180568,"WALK",-0.6347253092148584,"",,"0out_0in","shopping",105234692 +2566700,"school",1,1,1,1,"mandatory",1,17000,25000,1196298,61,8,15,7,"",,"WALK",-0.6435644948169205,"",,"0out_0in","school",105234731 +2566701,"escort",1,1,1,1,"non_mandatory",1,15000,25000,1196298,170,18,19,1,"",12.587432709712925,"SHARED3FREE",-0.5068681651861634,"",,"0out_0in","escort",105234750 +2566701,"school",1,1,1,1,"mandatory",1,8000,25000,1196298,43,7,13,6,"",,"WALK",-0.8719788590362192,"",,"0out_0in","school",105234772 +2566702,"othdiscr",1,1,1,1,"non_mandatory",1,17000,25000,1196298,171,18,20,2,"",14.120222605326392,"SHARED2FREE",0.2853016580628795,"",,"0out_2in","othdiscr",105234807 +2936848,"eatout",1,1,3,3,"non_mandatory",1,21000,11000,1286557,169,18,18,0,"",15.413580157739284,"WALK",3.946990174468817,"",,"0out_0in","eatout",120410774 +2936848,"othmaint",1,1,2,3,"non_mandatory",1,7000,11000,1286557,127,13,16,3,"",14.936437315067716,"WALK_LOC",1.6736771570576359,"",,"2out_1in","othmaint",120410796 +2936848,"shopping",1,1,1,3,"non_mandatory",1,8000,11000,1286557,170,18,19,1,"",13.737502885247409,"WALK",2.0065958574703022,"",,"0out_2in","shopping",120410801 +3061894,"othmaint",1,1,2,2,"non_mandatory",1,4000,24000,1363467,56,8,10,2,"",15.189637319752025,"WALK_LOC",2.347486200541679,"",,"0out_0in","othmaint",125537682 +3061894,"shopping",1,1,1,2,"non_mandatory",1,2000,24000,1363467,54,8,8,0,"",13.926378681444465,"TNC_SINGLE",2.513333119287107,"",,"1out_1in","shopping",125537687 +3061895,"othmaint",1,1,2,2,"non_mandatory",1,11000,24000,1363467,54,8,8,0,"",15.281994100444798,"WALK_LOC",1.724234637554872,"",,"0out_0in","othmaint",125537723 +3061895,"shopping",1,1,1,2,"non_mandatory",1,5000,24000,1363467,66,8,20,12,"",13.894949372888025,"TNC_SINGLE",2.1551607879298276,"",,"1out_0in","shopping",125537728 +3188483,"othmaint",1,1,2,2,"non_mandatory",1,9000,25000,1402945,86,10,11,1,"",14.231882110325735,"TNC_SINGLE",0.20219949435818624,"",,"0out_3in","othmaint",130727831 +3188483,"shopping",1,1,1,2,"non_mandatory",1,11000,25000,1402945,136,14,15,1,"",13.406265391553307,"BIKE",0.8851140793723273,"",,"0out_0in","shopping",130727836 +3188484,"work",1,1,1,1,"mandatory",1,14000,25000,1402945,147,15,17,2,"",,"SHARED2FREE",1.6082683353444291,"no_subtours",,"0out_0in","work",130727883 +3188485,"work",1,1,1,1,"mandatory",1,5000,25000,1402945,64,8,18,10,"",,"WALK",2.034000565768078,"no_subtours",,"0out_0in","work",130727924 +3232955,"escort",1,1,1,1,"non_mandatory",1,16000,14000,1444715,146,15,16,1,"",12.435690330338216,"WALK",-1.0982307160568445,"",,"0out_0in","escort",132551164 +3232955,"work",2,1,1,2,"mandatory",1,13000,14000,1444715,24,6,11,5,"",,"WALK",-0.430667159830106,"no_subtours",,"0out_0in","work",132551194 +3232955,"work",2,2,2,2,"mandatory",1,13000,14000,1444715,102,11,14,3,"",,"SHARED2FREE",-0.24782137298208812,"no_subtours",,"0out_0in","work",132551195 +3233462,"eat",1,1,1,1,"atwork",1,5000,21000,1445222,85,10,10,0,"",20.27043345711025,"WALK",0.33266492408706866,"",132571981,"0out_1in","atwork",132571946 +3233462,"work",1,1,1,1,"mandatory",1,21000,17000,1445222,81,9,20,11,"",,"DRIVEALONEFREE",0.4569557520698559,"eat",,"0out_3in","work",132571981 +3328568,"work",1,1,1,1,"mandatory",1,13000,8000,1511234,68,8,22,14,"",,"WALK_LRF",5.81180830136589,"no_subtours",,"0out_1in","work",136471327 +3328569,"school",1,1,1,1,"mandatory",1,9000,8000,1511234,62,8,16,8,"",,"WALK_LOC",7.51008635253054,"",,"0out_0in","univ",136471360 +3495342,"eat",1,1,1,1,"atwork",1,7000,8000,1594621,99,11,11,0,"",15.61810066049433,"WALK",6.365911569934135,"",143309061,"3out_0in","atwork",143309026 +3495342,"work",1,1,1,1,"mandatory",1,8000,10000,1594621,63,8,17,9,"",,"TNC_SINGLE",6.1798059555254525,"eat",,"0out_0in","work",143309061 +3495343,"shopping",1,1,1,1,"non_mandatory",1,2000,10000,1594621,146,15,16,1,"",14.147994174455755,"WALK",2.746770198798283,"",,"1out_1in","shopping",143309096 +3596364,"school",1,1,1,1,"mandatory",1,9000,9000,1645132,99,11,11,0,"",,"WALK",0.9922761728862803,"",,"0out_0in","univ",147450955 +3596364,"shopping",1,1,1,1,"non_mandatory",1,13000,9000,1645132,86,10,11,1,"",12.702108843408501,"DRIVEALONEFREE",-0.45273981886762027,"",,"1out_1in","shopping",147450957 +3596365,"school",1,1,1,1,"mandatory",1,11000,9000,1645132,92,10,17,7,"",,"WALK",0.060761191263363074,"",,"0out_2in","school",147450996 +3891102,"eat",1,1,1,1,"atwork",1,8000,10000,1747467,99,11,11,0,"",12.632028864768314,"WALK",-0.10570481904186844,"",159535221,"0out_1in","atwork",159535186 +3891102,"work",1,1,1,1,"mandatory",1,10000,16000,1747467,67,8,21,13,"",,"WALK_LRF",1.7308954449141203,"eat",,"1out_1in","work",159535221 +3891104,"othdiscr",1,1,1,1,"non_mandatory",1,17000,16000,1747467,52,7,22,15,"",14.783602512881732,"WALK",1.8681070245632654,"",,"0out_0in","othdiscr",159535289 +4171615,"school",1,1,1,1,"mandatory",1,14000,16000,1810015,169,18,18,0,"",,"TAXI",3.386100724122899,"",,"0out_0in","univ",171036246 +4171616,"shopping",1,1,1,1,"non_mandatory",1,14000,16000,1810015,89,10,14,4,"",13.351914976059247,"WALK",1.2336467654702536,"",,"0out_0in","shopping",171036289 +4171617,"eat",1,1,1,1,"atwork",1,2000,1000,1810015,127,13,16,3,"",12.928464655659191,"WALK",0.6246125445958517,"",171036336,"0out_1in","atwork",171036301 +4171617,"work",1,1,1,1,"mandatory",1,1000,16000,1810015,62,8,16,8,"",,"WALK",1.516396122478756,"eat",,"0out_0in","work",171036336 +4171619,"othdiscr",1,1,1,1,"non_mandatory",1,9000,16000,1810015,80,9,19,10,"",14.427951929207534,"WALK_LRF",0.8762067488244525,"",,"0out_0in","othdiscr",171036404 +4171622,"othmaint",1,1,1,1,"non_mandatory",1,7000,16000,1810015,100,11,12,1,"",14.02155021495475,"TNC_SINGLE",-0.18852821228964295,"",,"0out_0in","othmaint",171036530 +4823797,"work",1,1,1,1,"mandatory",1,15000,14000,1952792,93,10,18,8,"",,"WALK",5.2053882150278685,"no_subtours",,"0out_0in","work",197775716 +5057160,"work",1,1,1,1,"mandatory",1,7000,5000,2048204,30,6,17,11,"",,"WALK_LOC",0.010326455398757789,"no_subtours",,"0out_0in","work",207343599 +5057338,"work",1,1,1,1,"mandatory",1,16000,7000,2048382,50,7,20,13,"",,"WALK_LOC",5.6695699337191785,"no_subtours",,"0out_0in","work",207350897 +5387762,"work",1,1,1,1,"mandatory",1,14000,9000,2223027,28,6,15,9,"",,"WALK_LRF",1.6596686780905205,"no_subtours",,"0out_0in","work",220898281 +5387763,"eatout",1,1,2,2,"non_mandatory",1,12000,9000,2223027,91,10,16,6,"",14.015959650256292,"WALK",1.4718106357159384,"",,"0out_0in","eatout",220898289 +5387763,"othdiscr",1,1,1,2,"non_mandatory",1,16000,9000,2223027,169,18,18,0,"",14.599617247497788,"WALK_LRF",1.473890118377823,"",,"0out_0in","othdiscr",220898308 +5389226,"work",1,1,1,1,"mandatory",1,4000,16000,2223759,63,8,17,9,"",,"WALK",1.778739909377105,"no_subtours",,"0out_0in","work",220958305 +5389227,"eat",1,1,1,1,"atwork",1,15000,16000,2223759,85,10,10,0,"",12.858228927386488,"WALK",0.13242345411680737,"",220958346,"0out_0in","atwork",220958311 +5389227,"escort",1,1,1,1,"non_mandatory",1,5000,16000,2223759,145,15,15,0,"",12.681412371417782,"WALK",-0.25618122034875435,"",,"0out_0in","escort",220958316 +5389227,"work",1,1,1,1,"mandatory",1,16000,16000,2223759,28,6,15,9,"",,"WALK",2.079861874799809,"eat",,"0out_0in","work",220958346 +7305540,"social",2,1,1,2,"non_mandatory",1,3000,20000,2727273,87,10,12,2,"",14.1996409773108,"WALK_LRF",1.6323701800235073,"",,"0out_0in","social",299527176 +7305540,"social",2,2,2,2,"non_mandatory",1,5000,20000,2727273,164,17,19,2,"",14.120642597671408,"WALK",1.6435805691427867,"",,"0out_1in","social",299527177 +7305540,"work",1,1,1,1,"mandatory",1,24000,20000,2727273,127,13,16,3,"",,"BIKE",0.9398542864235182,"no_subtours",,"0out_0in","work",299527179 +7305541,"shopping",1,1,1,2,"non_mandatory",1,16000,20000,2727273,154,16,16,0,"",13.28794064279901,"WALK_LOC",1.014745941162379,"",,"0out_0in","shopping",299527214 +7305541,"social",1,1,2,2,"non_mandatory",1,2000,20000,2727273,171,18,20,2,"",14.151071365958824,"WALK_HVY",1.4638753299235214,"",,"0out_0in","social",299527217 +7305541,"work",1,1,1,1,"mandatory",1,5000,20000,2727273,45,7,15,8,"",,"BIKE",1.6688374225826097,"no_subtours",,"0out_0in","work",299527220 +7453413,"othmaint",1,1,1,1,"non_mandatory",1,16000,20000,2762078,102,11,14,3,"",14.985169483093204,"WALK_LOC",1.6799244511999831,"",,"0out_0in","othmaint",305589961 +7511873,"work",1,1,1,1,"mandatory",1,16000,8000,2820538,45,7,15,8,"",,"WALK_LOC",-0.8856043115317688,"no_subtours",,"0out_0in","work",307986832 +7512109,"work",1,1,1,1,"mandatory",1,9000,8000,2820774,48,7,18,11,"",,"WALK_LOC",5.407974628092707,"no_subtours",,"0out_0in","work",307996508 +7512514,"work",1,1,1,1,"mandatory",1,5000,8000,2821179,172,18,21,3,"",,"WALK",5.281889150266914,"no_subtours",,"0out_0in","work",308013113 +7513432,"social",1,1,1,1,"non_mandatory",1,9000,8000,2822097,77,9,16,7,"",14.426345007668951,"WALK_LOC",1.9557736782037987,"",,"0out_1in","social",308050748 +7513554,"work",1,1,1,1,"mandatory",1,2000,8000,2822219,96,10,21,11,"",,"TNC_SINGLE",5.870896361254442,"no_subtours",,"1out_0in","work",308055753 +7523517,"shopping",1,1,1,1,"non_mandatory",1,20000,7000,2832182,145,15,15,0,"",13.532091345687146,"WALK_LOC",1.1383618201531152,"",,"0out_0in","shopping",308464230 diff --git a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv index 0b7aeef939..3a6421b5c2 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv +++ b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv @@ -1,238 +1,261 @@ -person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum,trip_id -25872,25872,eatout,1,True,1,25000,7000,1060758,eatout,,15,WALK,12.622881703075194,8486065 -25872,25872,eatout,1,False,1,7000,25000,1060758,home,,21,WALK,12.807021628425415,8486069 -26103,26103,shopping,1,True,1,8000,8000,1070256,shopping,,16,WALK,12.5029740499786,8562049 -26103,26103,shopping,1,False,1,8000,8000,1070256,home,,20,WALK,12.5029740499786,8562053 -26143,26143,shopping,1,True,1,19000,8000,1071896,shopping,,12,WALK,-0.028786720385919,8575169 -26143,26143,shopping,1,False,2,7000,19000,1071896,escort,36.069170215525695,13,WALK_LOC,0.0596550723757274,8575173 -26143,26143,shopping,2,False,2,8000,7000,1071896,home,,13,WALK,14.544360704356594,8575174 -27412,27412,univ,1,True,1,9000,9000,1123923,univ,,9,WALK,10.238432618676171,8991385 -27412,27412,univ,1,False,1,9000,9000,1123923,home,,10,WALK,10.238432618676171,8991389 -27412,27412,univ,1,True,4,7000,9000,1123924,escort,52.4294183195496,17,BIKE,13.493362410549922,8991393 -27412,27412,univ,2,True,4,7000,7000,1123924,shopping,53.01926176997787,17,BIKE,13.911132276096,8991394 -27412,27412,univ,3,True,4,7000,7000,1123924,escort,53.23086426572027,17,WALK,13.911132276096,8991395 -27412,27412,univ,4,True,4,9000,7000,1123924,univ,,17,WALK,9.965741143813498,8991396 -27412,27412,univ,1,False,1,9000,9000,1123924,home,,17,BIKE,10.482611643665615,8991397 -27415,27415,othmaint,1,True,1,16000,9000,1124043,othmaint,,7,BIKE,3.991435227151364,8992345 -27415,27415,othmaint,1,False,1,9000,16000,1124043,home,,14,BIKE,3.977759441323569,8992349 -27683,27683,othdiscr,1,True,1,9000,10000,1135028,othdiscr,,9,WALK_LOC,11.109665478014662,9080225 -27683,27683,othdiscr,1,False,4,7000,9000,1135028,escort,55.92888040973646,13,WALK,11.035231764382528,9080229 -27683,27683,othdiscr,2,False,4,9000,7000,1135028,othmaint,57.67662388100479,13,WALK_LOC,14.172826390440834,9080230 -27683,27683,othdiscr,3,False,4,7000,9000,1135028,shopping,55.11890671874478,13,WALK,11.035231856411922,9080231 -27683,27683,othdiscr,4,False,4,10000,7000,1135028,home,,13,WALK_LOC,14.097237153881805,9080232 -107628,107628,work,1,True,1,12000,6000,4412787,work,,10,WALK_LOC,4.23738210975023,35302297 -107628,107628,work,1,False,1,6000,12000,4412787,home,,17,WALK,4.177398023206957,35302301 -112666,112666,othmaint,1,True,1,1000,17000,4619334,othmaint,,12,WALK,-1.1983965087409965,36954673 -112666,112666,othmaint,1,False,1,17000,1000,4619334,home,,13,WALK,-1.312439942707106,36954677 -112666,112666,work,1,True,1,2000,17000,4619345,work,,17,WALK,0.151322967222737,36954761 -112666,112666,work,1,False,1,17000,2000,4619345,home,,21,WALK,0.4697850077158731,36954765 -112785,112785,eatout,1,True,1,11000,19000,4624191,eatout,,20,WALK,4.328113197335523,36993529 -112785,112785,eatout,1,False,1,19000,11000,4624191,home,,21,WALK,4.313101182265273,36993533 -112785,112785,work,1,True,1,21000,19000,4624224,work,,6,WALK,1.8420501444349504,36993793 -112785,112785,work,1,False,1,19000,21000,4624224,home,,18,WALK,1.8334411011838323,36993797 -112977,112977,work,1,True,3,8000,21000,4632096,escort,33.04133838114583,8,WALK,9.009776222045147,37056769 -112977,112977,work,2,True,3,7000,8000,4632096,escort,35.28678968135897,8,WALK,10.948561446864396,37056770 -112977,112977,work,3,True,3,12000,7000,4632096,work,,8,WALK,3.208903318908808,37056771 -112977,112977,work,1,False,4,9000,12000,4632096,othmaint,30.539889894402076,17,WALK,2.632506122325052,37056773 -112977,112977,work,2,False,4,7000,9000,4632096,escort,42.63129816637797,17,WALK,7.8276425435229235,37056774 -112977,112977,work,3,False,4,7000,7000,4632096,othmaint,49.379996481297965,17,WALK,11.036561446414703,37056775 -112977,112977,work,4,False,4,21000,7000,4632096,home,,17,WALK,10.394161433315231,37056776 -264055,226843,escort,1,True,1,20000,9000,10826264,escort,,6,WALK,1.609303603527435,86610113 -264055,226843,escort,1,False,1,9000,20000,10826264,home,,6,WALK,1.500843214540278,86610117 -264056,226843,work,1,True,1,10000,9000,10826335,work,,5,WALK_LOC,8.474132523587011,86610681 -264056,226843,work,1,False,1,9000,10000,10826335,home,,14,WALK_LOC,8.451196386482865,86610685 -323181,256406,shopping,1,True,1,11000,10000,13250440,shopping,,13,WALK,4.898455205211476,106003521 -323181,256406,shopping,1,False,2,9000,11000,13250440,escort,40.31798624577948,16,WALK,4.720179189781661,106003525 -323181,256406,shopping,2,False,2,10000,9000,13250440,home,,16,WALK,10.780621013348998,106003526 -323181,256406,work,1,True,1,9000,10000,13250460,work,,7,WALK_LOC,8.866606849192987,106003681 -323181,256406,work,1,False,1,10000,9000,13250460,home,,12,WALK,8.943956112104715,106003685 -323182,256406,work,1,True,1,2000,10000,13250501,work,,16,WALK_LRF,0.4866278431422626,106004009 -323182,256406,work,1,False,1,10000,2000,13250501,home,,20,WALK_LRF,0.4920280753234634,106004013 -325309,257470,work,1,True,1,19000,16000,13337708,work,,6,WALK,-1.2965720830153529,106701665 -325309,257470,work,1,False,1,16000,19000,13337708,home,,13,WALK,-1.3173517958919845,106701669 -325429,257530,work,1,True,2,7000,16000,13342628,escort,33.60698362494382,8,WALK,9.804561444298288,106741025 -325429,257530,work,2,True,2,5000,7000,13342628,work,,9,WALK,3.3081329142919915,106741026 -325429,257530,work,1,False,1,16000,5000,13342628,home,,18,WALK,2.832946137131945,106741029 -325430,257530,atwork,1,True,1,1000,23000,13342634,atwork,,11,TNC_SINGLE,1.285198550672105,106741073 -325430,257530,atwork,1,False,1,23000,1000,13342634,work,,13,TNC_SINGLE,1.2886153300442418,106741077 -325430,257530,othdiscr,1,True,1,16000,16000,13342655,othdiscr,,16,WALK,7.330879884940061,106741241 -325430,257530,othdiscr,1,False,1,16000,16000,13342655,home,,20,WALK,7.33087988512484,106741245 -325430,257530,work,1,True,1,23000,16000,13342669,work,,6,WALK_LOC,2.0406987569036743,106741353 -325430,257530,work,1,False,1,16000,23000,13342669,home,,16,WALK_LOC,2.1205858583347994,106741357 -644437,386748,work,1,True,1,24000,9000,26421956,work,,8,WALK,2.398575768520878,211375649 -644437,386748,work,1,False,1,9000,24000,26421956,home,,19,WALK_LRF,2.6720960087106285,211375653 -644438,386748,shopping,1,True,2,8000,9000,26421991,othdiscr,43.91352211511756,10,WALK,12.032732830156991,211375929 -644438,386748,shopping,2,True,2,16000,8000,26421991,shopping,,12,WALK_LOC,5.795035984789261,211375930 -644438,386748,shopping,1,False,1,9000,16000,26421991,home,,16,WALK_LRF,7.823496294062292,211375933 -644439,386748,work,1,True,1,22000,9000,26422038,work,,5,WALK_LRF,1.7428771545130517,211376305 -644439,386748,work,1,False,1,9000,22000,26422038,home,,15,WALK,1.86885070744339,211376309 -1264881,567768,othdiscr,1,True,1,18000,10000,51860146,othdiscr,,16,WALK_LOC,1.233984053082079,414881169 -1264881,567768,othdiscr,1,False,1,10000,18000,51860146,home,,21,WALK_LRF,1.5716192527699957,414881173 -1265935,568822,escort,1,True,1,8000,17000,51903344,escort,,18,WALK,9.818994025451875,415226753 -1265935,568822,escort,1,False,1,17000,8000,51903344,home,,19,WALK,9.958494232509716,415226757 -1265935,568822,othmaint,1,True,1,21000,17000,51903363,othmaint,,11,WALK,3.4811515445467536,415226905 -1265935,568822,othmaint,1,False,1,17000,21000,51903363,home,,14,WALK_LRF,3.799259767554127,415226909 -1356554,659441,shopping,1,True,1,13000,22000,55618747,shopping,,8,BIKE,-0.7588316695466023,444949977 -1356554,659441,shopping,1,False,1,22000,13000,55618747,home,,18,BIKE,-0.9265992357824824,444949981 -1632055,823275,atwork,1,True,1,12000,11000,66914259,atwork,,10,WALK,4.956248158173995,535314073 -1632055,823275,atwork,1,False,1,11000,12000,66914259,work,,10,WALK,4.956248078984152,535314077 -1632055,823275,work,1,True,1,11000,10000,66914294,work,,7,WALK,3.53078088614594,535314353 -1632055,823275,work,1,False,1,10000,11000,66914294,home,,15,WALK,3.53078055875979,535314357 -1632179,823399,work,1,True,2,6000,11000,66919378,shopping,30.226326066605,11,WALK,10.141295202967775,535355025 -1632179,823399,work,2,True,2,23000,6000,66919378,work,,11,WALK_LOC,1.211843196595506,535355026 -1632179,823399,work,1,False,2,7000,23000,66919378,shopping,33.39551329594212,21,WALK_LOC,1.6167681518696837,535355029 -1632179,823399,work,2,False,2,11000,7000,66919378,home,,21,WALK,11.52111911817073,535355030 -1774491,932260,work,1,True,1,7000,9000,72754170,work,,7,WALK_LOC,11.509109011754376,582033361 -1774491,932260,work,1,False,1,9000,7000,72754170,home,,15,WALK_LOC,11.434340926744566,582033365 -1774492,932260,shopping,1,True,2,7000,9000,72754205,othdiscr,50.515004160168914,12,WALK,14.438474985591771,582033641 -1774492,932260,shopping,2,True,2,16000,7000,72754205,shopping,,13,WALK_LOC,7.1497500612965466,582033642 -1774492,932260,shopping,1,False,1,9000,16000,72754205,home,,13,WALK_LRF,8.003866393547536,582033645 -1876395,983212,work,1,True,1,13000,21000,76932234,work,,6,WALK_LOC,0.0082845863634515,615457873 -1876395,983212,work,1,False,2,7000,13000,76932234,othdiscr,31.18255131388486,17,WALK_LOC,0.0374276967114544,615457877 -1876395,983212,work,2,False,2,21000,7000,76932234,home,,18,WALK,11.208251589738415,615457878 -2071280,1070367,work,1,True,1,16000,21000,84922519,work,,8,WALK,4.975425515919179,679380153 -2071280,1070367,work,1,False,1,21000,16000,84922519,home,,16,WALK,4.7554261455753934,679380157 -2071281,1070367,work,1,True,1,5000,21000,84922560,work,,9,WALK,3.035330184189096,679380481 -2071281,1070367,work,1,False,2,7000,5000,84922560,othdiscr,35.54470921265402,13,WALK,3.360924868679592,679380485 -2071281,1070367,work,2,False,2,21000,7000,84922560,home,,13,WALK,10.39416144383609,679380486 -2220332,1120051,work,1,True,2,7000,20000,91033651,work,37.708683174130016,9,WALK,10.219696407315745,728269209 -2220332,1120051,work,2,True,2,11000,7000,91033651,work,,11,WALK,3.629020464703427,728269210 -2220332,1120051,work,1,False,2,7000,11000,91033651,othmaint,35.51810478534547,16,WALK,3.6126450866505695,728269213 -2220332,1120051,work,2,False,2,20000,7000,91033651,home,,16,WALK,10.02169230676222,728269214 -2220333,1120051,othmaint,1,True,1,7000,20000,91033681,othmaint,,15,WALK_LOC,8.628357530180933,728269449 -2220333,1120051,othmaint,1,False,3,8000,7000,91033681,eatout,40.27006262947925,21,WALK_LOC,8.531134315535892,728269453 -2220333,1120051,othmaint,2,False,3,10000,8000,91033681,eatout,37.25832680880913,21,WALK_LOC,7.532098294705944,728269454 -2220333,1120051,othmaint,3,False,3,20000,10000,91033681,home,,21,WALK_LOC,6.240170553049018,728269455 -2220333,1120051,work,1,True,1,14000,20000,91033692,work,,6,WALK_LRF,1.5161043166984922,728269537 -2220333,1120051,work,1,False,1,20000,14000,91033692,home,,15,WALK_LOC,0.7434266921881713,728269541 -2220334,1120051,school,1,True,1,9000,20000,91033725,school,,8,WALK_LOC,10.058753507459729,728269801 -2220334,1120051,school,1,False,1,20000,9000,91033725,home,,20,WALK,9.814636763939884,728269805 -2746929,1234121,atwork,1,True,1,10000,21000,112624093,atwork,,17,WALK,9.58423331125647,900992745 -2746929,1234121,atwork,1,False,1,21000,10000,112624093,work,,17,WALK,9.59175323294754,900992749 -2746932,1234121,shopping,1,True,1,1000,11000,112624108,shopping,,17,SHARED2FREE,-0.0068224692201906,900992865 -2746932,1234121,shopping,1,False,1,11000,1000,112624108,home,,17,SHARED2FREE,-0.0077875668598362,900992869 -2746929,1234121,work,1,True,1,21000,11000,112624128,work,,6,WALK,2.9694319601524466,900993025 -2746929,1234121,work,1,False,1,11000,21000,112624128,home,,17,WALK,2.9694319554520967,900993029 -2746930,1234121,eatout,1,True,1,17000,11000,112624136,eatout,,10,WALK,4.435362336682579,900993089 -2746930,1234121,eatout,1,False,1,11000,17000,112624136,home,,15,WALK,4.463262318129899,900993093 -2746931,1234121,work,1,True,1,20000,11000,112624210,work,,7,WALK,1.1933443038963714,900993681 -2746931,1234121,work,1,False,1,11000,20000,112624210,home,,20,WALK,1.2637449668352003,900993685 -2746932,1234121,school,1,True,1,8000,11000,112624243,school,,7,WALK,11.3802464089204,900993945 -2746932,1234121,school,1,False,1,11000,8000,112624243,home,,17,WALK,11.282686396640877,900993949 -2746934,1234121,school,1,True,1,21000,11000,112624325,school,,10,WALK,3.657792220061346,900994601 -2746934,1234121,school,1,False,1,11000,21000,112624325,home,,17,WALK,3.657792219228462,900994605 -2936912,1286621,shopping,1,True,1,16000,17000,120413425,shopping,,14,WALK,7.855163989505554,963307401 -2936912,1286621,shopping,1,False,1,17000,16000,120413425,home,,22,WALK,7.853841131587542,963307405 -2957530,1307239,work,1,True,1,19000,21000,121258769,work,,11,WALK,0.3637138294765611,970070153 -2957530,1307239,work,1,False,1,21000,19000,121258769,home,,19,WALK,0.2307332766636538,970070157 -3112851,1384946,work,1,True,1,2000,25000,127626930,work,,11,WALK_LOC,0.6211997338372249,1021015441 -3112851,1384946,work,1,False,2,8000,2000,127626930,work,29.9712324638375,19,WALK_LOC,0.255211438812688,1021015445 -3112851,1384946,work,2,False,2,25000,8000,127626930,home,,20,WALK,9.061488007962073,1021015446 -3328643,1511271,shopping,1,True,1,19000,9000,136474396,shopping,,8,WALK,-0.8879590795653944,1091795169 -3328643,1511271,shopping,1,False,1,9000,19000,136474396,home,,16,WALK,-1.083191160238464,1091795173 -3495346,1594623,atwork,1,True,1,11000,5000,143309190,atwork,,14,WALK,5.543648655831851,1146473521 -3495346,1594623,atwork,1,False,1,5000,11000,143309190,work,,14,WALK_LOC,5.61918499405176,1146473525 -3495346,1594623,work,1,True,1,5000,10000,143309225,work,,7,WALK,2.524931061757108,1146473801 -3495346,1594623,work,1,False,1,10000,5000,143309225,home,,21,WALK,2.744927712292272,1146473805 -3495347,1594623,othmaint,1,True,1,12000,10000,143309255,othmaint,,10,WALK_LOC,3.183629274414097,1146474041 -3495347,1594623,othmaint,1,False,1,10000,12000,143309255,home,,11,WALK_LOC,3.183277552596278,1146474045 -3495347,1594623,shopping,1,True,1,1000,10000,143309260,shopping,,11,WALK_LRF,0.3379285592978597,1146474081 -3495347,1594623,shopping,1,False,1,10000,1000,143309260,home,,11,WALK_LRF,0.4541508537591313,1146474085 -3495347,1594623,work,1,True,1,9000,10000,143309266,work,,12,WALK,8.03004262714356,1146474129 -3495347,1594623,work,1,False,1,10000,9000,143309266,home,,19,WALK,8.170842629634851,1146474133 -3496420,1595160,atwork,1,True,1,12000,16000,143353224,atwork,,10,WALK,4.843447137861254,1146825793 -3496420,1595160,atwork,1,False,1,16000,12000,143353224,work,,12,WALK,4.843447111509163,1146825797 -3496420,1595160,work,1,True,1,16000,17000,143353259,work,,5,WALK,5.0458247003147685,1146826073 -3496420,1595160,work,1,False,1,17000,16000,143353259,home,,20,WALK,5.116224566837883,1146826077 -3496421,1595160,work,1,True,1,16000,17000,143353300,work,,7,WALK_LOC,5.977645296996196,1146826401 -3496421,1595160,work,1,False,1,17000,16000,143353300,home,,18,WALK,5.989179722709104,1146826405 -3608536,1651218,work,1,True,1,13000,9000,147950015,work,,7,WALK_LOC,-0.2844070906738741,1183600121 -3608536,1651218,work,1,False,1,9000,13000,147950015,home,,17,WALK,-0.2557713080959184,1183600125 -3608537,1651218,school,1,True,1,13000,9000,147950048,school,,7,WALK_LRF,1.0049003799424374,1183600385 -3608537,1651218,school,1,False,1,9000,13000,147950048,home,,17,WALK_LRF,1.000810478044286,1183600389 -4762866,1931922,school,1,True,1,17000,16000,195277537,school,,8,WALK_LRF,6.292251313972136,1562220297 -4762866,1931922,school,1,False,1,16000,17000,195277537,home,,13,WALK_LRF,6.2578013689770495,1562220301 -4762869,1931922,school,1,True,1,17000,16000,195277660,school,,8,WALK_LRF,6.292251313972136,1562221281 -4762869,1931922,school,1,False,1,16000,17000,195277660,home,,15,WALK_LRF,6.257797388209449,1562221285 -4762871,1931922,shopping,1,True,1,13000,16000,195277744,shopping,,12,WALK,-0.6022649376600622,1562221953 -4762871,1931922,shopping,1,False,1,16000,13000,195277744,home,,13,WALK,-0.6741441677126537,1562221957 -4762872,1931922,work,1,True,1,1000,16000,195277791,work,,9,WALK,-1.1893690966144852,1562222329 -4762872,1931922,work,1,False,1,16000,1000,195277791,home,,19,WALK,-1.3613354348131683,1562222333 -4762874,1931922,school,1,True,1,16000,16000,195277865,school,,7,WALK,6.714175485305698,1562222921 -4762874,1931922,school,1,False,1,16000,16000,195277865,home,,14,WALK,6.714175485873147,1562222925 -4762875,1931922,school,1,True,1,16000,16000,195277906,school,,8,WALK,6.714175485305698,1562223249 -4762875,1931922,school,1,False,1,16000,16000,195277906,home,,11,WALK,6.714175485305698,1562223253 -4817011,1946006,social,1,True,1,7000,8000,197497487,social,,12,WALK,8.97158312503046,1579979897 -4817011,1946006,social,1,False,1,8000,7000,197497487,home,,13,WALK,8.789583139086504,1579979901 -4817011,1946006,social,1,True,1,1000,8000,197497488,social,,14,WALK_LOC,-0.1838315538475003,1579979905 -4817011,1946006,social,1,False,1,8000,1000,197497488,home,,16,WALK_LOC,-0.2144249677066041,1579979909 -4950606,2010083,school,1,True,1,7000,8000,202974877,school,,8,WALK_LOC,13.226901524964711,1623799017 -4950606,2010083,school,1,False,1,8000,7000,202974877,home,,21,WALK,13.015690560204396,1623799021 -5057667,2048711,work,1,True,1,5000,11000,207364386,work,,8,WALK,3.132108367818242,1658915089 -5057667,2048711,work,1,False,1,11000,5000,207364386,home,,19,WALK,3.176108669344638,1658915093 -5386916,2222604,atwork,1,True,1,14000,10000,220863560,atwork,,13,WALK_LRF,2.6214243141937126,1766908481 -5386916,2222604,atwork,1,False,2,7000,14000,220863560,eatout,40.76162984079656,13,WALK_LOC,1.966082194929056,1766908485 -5386916,2222604,atwork,2,False,2,10000,7000,220863560,work,,13,WALK,14.875749907565677,1766908486 -5386916,2222604,shopping,1,True,2,9000,7000,220863589,social,35.52503266455083,19,WALK_LOC,10.635824434089114,1766908713 -5386916,2222604,shopping,2,True,2,20000,9000,220863589,shopping,,20,WALK_LOC,2.234018194162152,1766908714 -5386916,2222604,shopping,1,False,3,25000,20000,220863589,shopping,35.84551495448149,21,WALK_LRF,1.330786295384866,1766908717 -5386916,2222604,shopping,2,False,3,10000,25000,220863589,eatout,52.3743087314661,21,WALK_LOC,11.678738756115026,1766908718 -5386916,2222604,shopping,3,False,3,7000,10000,220863589,home,,21,WALK_LOC,9.976614394034591,1766908719 -5386916,2222604,work,1,True,1,10000,7000,220863595,work,,8,WALK_LOC,8.192659828887404,1766908761 -5386916,2222604,work,1,False,1,7000,10000,220863595,home,,18,WALK_LOC,8.188826447054582,1766908765 -5386917,2222604,work,1,True,1,19000,7000,220863636,work,,8,WALK_LOC,0.1911770675662809,1766909089 -5386917,2222604,work,1,False,2,7000,19000,220863636,othmaint,31.6003322795588,18,WALK,0.1437692426814362,1766909093 -5386917,2222604,work,2,False,2,7000,7000,220863636,home,,18,WALK,11.32236685212627,1766909094 -5387114,2222703,othdiscr,1,True,2,8000,7000,220871699,shopping,38.02242004489941,6,WALK,12.564286884276584,1766973593 -5387114,2222703,othdiscr,2,True,2,22000,8000,220871699,othdiscr,,7,WALK_LRF,2.805407913482188,1766973594 -5387114,2222703,othdiscr,1,False,1,7000,22000,220871699,home,,18,WALK_LOC,1.9273722416677583,1766973597 -5387114,2222703,othdiscr,1,True,1,6000,7000,220871700,othdiscr,,18,WALK,12.768489014109347,1766973601 -5387114,2222703,othdiscr,1,False,1,7000,6000,220871700,home,,18,WALK,12.712689042504248,1766973605 -5387114,2222703,work,1,True,1,4000,7000,220871713,work,,19,TNC_SINGLE,1.0831157444746895,1766973705 -5387114,2222703,work,1,False,1,7000,4000,220871713,home,,22,WALK,1.0793285014212155,1766973709 -5387115,2222703,eatout,1,True,1,14000,7000,220871721,eatout,,21,WALK_LOC,1.6853501652386769,1766973769 -5387115,2222703,eatout,1,False,1,7000,14000,220871721,home,,21,WALK_LOC,1.6973275752566237,1766973773 -5387115,2222703,work,1,True,2,7000,7000,220871754,shopping,31.69798103935113,9,WALK,11.322377732641469,1766974033 -5387115,2222703,work,2,True,2,13000,7000,220871754,work,,10,TNC_SINGLE,0.7778453750585694,1766974034 -5387115,2222703,work,1,False,2,9000,13000,220871754,othmaint,32.707495666775806,17,TNC_SINGLE,0.8285588262291184,1766974037 -5387115,2222703,work,2,False,2,7000,9000,220871754,home,,20,WALK,8.909402790957092,1766974038 -5388246,2223269,work,1,True,1,4000,10000,220918125,work,,9,WALK_LRF,0.7089583202667562,1767345001 -5388246,2223269,work,1,False,2,7000,4000,220918125,escort,31.994989861076217,15,WALK,0.5871608261082515,1767345005 -5388246,2223269,work,2,False,2,10000,7000,220918125,home,,19,WALK_LOC,11.34645373317459,1767345006 -5388247,2223269,atwork,1,True,1,2000,24000,220918131,atwork,,13,WALK,0.4219166577214059,1767345049 -5388247,2223269,atwork,1,False,1,24000,2000,220918131,work,,14,WALK,0.4444722615783372,1767345053 -5388247,2223269,work,1,True,1,24000,10000,220918166,work,,8,WALK,-0.0203809805190121,1767345329 -5388247,2223269,work,1,False,2,25000,24000,220918166,social,29.31818466200153,16,WALK,2.0913678350366944,1767345333 -5388247,2223269,work,2,False,2,10000,25000,220918166,home,,21,WALK,8.369527092415394,1767345334 -5388456,2223374,work,1,True,1,16000,11000,220926735,work,,9,WALK,4.434228526694411,1767413881 -5388456,2223374,work,1,False,1,11000,16000,220926735,home,,18,WALK,4.434228455705195,1767413885 -5388456,2223374,work,1,True,2,7000,11000,220926736,escort,36.15676938773793,18,WALK,10.587761425978275,1767413889 -5388456,2223374,work,2,True,2,16000,7000,220926736,work,,18,WALK,4.01623140579655,1767413890 -5388456,2223374,work,1,False,2,6000,16000,220926736,work,37.00996280650867,19,WALK,4.082231146080614,1767413893 -5388456,2223374,work,2,False,2,11000,6000,220926736,home,,19,WALK,9.254342916685156,1767413894 -5388457,2223374,work,1,True,1,23000,11000,220926776,work,,9,WALK_LOC,1.5921957770735595,1767414209 -5388457,2223374,work,1,False,1,11000,23000,220926776,home,,19,WALK_LOC,1.5919645118866783,1767414213 -5391136,2224714,atwork,1,True,1,13000,16000,221036580,atwork,,11,WALK,-0.4561262528247776,1768292641 -5391136,2224714,atwork,1,False,1,16000,13000,221036580,work,,13,WALK,-0.5045352445065882,1768292645 -5391136,2224714,eatout,1,True,1,22000,17000,221036582,eatout,,20,WALK_LRF,2.6683208551888327,1768292657 -5391136,2224714,eatout,1,False,1,17000,22000,221036582,home,,21,WALK_LRF,2.9626477168303618,1768292661 -5391136,2224714,work,1,True,4,6000,17000,221036615,work,42.3757416289184,8,WALK,9.44870723053955,1768292921 -5391136,2224714,work,2,True,4,8000,6000,221036615,escort,39.33637730721234,8,WALK,10.098789204079656,1768292922 -5391136,2224714,work,3,True,4,25000,8000,221036615,escort,38.8253097945831,9,WALK,10.453953179773286,1768292923 -5391136,2224714,work,4,True,4,16000,25000,221036615,work,,11,WALK_LOC,5.609497297787802,1768292924 -5391136,2224714,work,1,False,1,17000,16000,221036615,home,,17,WALK_LRF,6.375393830472105,1768292925 -5391137,2224714,work,1,True,1,2000,17000,221036656,work,,5,WALK_LRF,0.4679223024855918,1768293249 -5391137,2224714,work,1,False,4,6000,2000,221036656,othdiscr,29.24742791838357,16,WALK,0.3715206518006981,1768293253 -5391137,2224714,work,2,False,4,10000,6000,221036656,shopping,42.6597481033155,17,WALK_LOC,10.068026158792334,1768293254 -5391137,2224714,work,3,False,4,7000,10000,221036656,shopping,44.11765347097764,17,WALK,8.287106531455793,1768293255 -5391137,2224714,work,4,False,4,17000,7000,221036656,home,,18,WALK,10.823184515975516,1768293256 -7452651,2761316,othdiscr,1,True,1,14000,10000,305558716,othdiscr,,16,WALK_LRF,2.1434224937878565,2444469729 -7452651,2761316,othdiscr,1,False,1,10000,14000,305558716,home,,22,WALK_LRF,2.09912851886251,2444469733 -7452651,2761316,othmaint,1,True,1,2000,10000,305558719,othmaint,,5,WALK_LRF,0.8813635609701315,2444469753 -7452651,2761316,othmaint,1,False,1,10000,2000,305558719,home,,13,WALK_LRF,0.9656586006325034,2444469757 -7452726,2761391,shopping,1,True,2,8000,10000,305561799,shopping,41.14610613733738,12,WALK,11.654724087483617,2444494393 -7452726,2761391,shopping,2,True,2,16000,8000,305561799,shopping,,12,WALK,5.348165111115939,2444494394 -7452726,2761391,shopping,1,False,2,7000,16000,305561799,escort,44.24836557044917,15,WALK,5.680632173681755,2444494397 -7452726,2761391,shopping,2,False,2,10000,7000,305561799,home,,15,WALK,13.42152726365645,2444494398 -7453556,2762221,othdiscr,1,True,1,5000,21000,305595821,othdiscr,,14,WALK_LOC,4.690570517977736,2444766569 -7453556,2762221,othdiscr,1,False,1,21000,5000,305595821,home,,18,WALK,4.678949130107614,2444766573 +"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","depart","trip_mode","mode_choice_logsum","trip_id" +26686,26686,"shopping",1,true,1,11000,8000,1094159,"shopping",,12,"WALK_LOC",5.411343557320603,8753273 +26686,26686,"shopping",1,false,1,8000,11000,1094159,"home",,13,"WALK",5.463401368296604,8753277 +26844,26844,"othmaint",1,true,1,22000,8000,1100632,"othmaint",,8,"WALK_LRF",2.188520043314093,8805057 +26844,26844,"othmaint",1,false,1,8000,22000,1100632,"home",,9,"WALK_LRF",2.36878548584534,8805061 +26844,26844,"shopping",1,true,2,9000,8000,1100637,"shopping",37.654995499157636,9,"WALK_LOC",10.984274105698466,8805097 +26844,26844,"shopping",2,true,2,5000,9000,1100637,"shopping",,9,"WALK_LOC",4.54338910965502,8805098 +26844,26844,"shopping",1,false,1,8000,5000,1100637,"home",,14,"WALK_LOC",4.56041276868394,8805101 +27726,27726,"eatout",1,true,1,5000,10000,1136772,"eatout",,14,"WALK",3.620543614827592,9094177 +27726,27726,"eatout",1,false,1,10000,5000,1136772,"home",,19,"WALK",3.8995426979512775,9094181 +110675,110675,"work",1,true,1,15000,16000,4537714,"work",,5,"WALK",0.4290512337198249,36301713 +110675,110675,"work",1,false,1,16000,15000,4537714,"home",,18,"WALK",0.3209866213701127,36301717 +112064,112064,"work",1,true,1,13000,16000,4594663,"work",,13,"WALK",-0.8049686044441835,36757305 +112064,112064,"work",1,false,1,16000,13000,4594663,"home",,20,"WALK",-0.8616775847545357,36757309 +264108,226869,"eatout",1,true,1,8000,9000,10828434,"eatout",,14,"WALK",12.045414087419827,86627473 +264108,226869,"eatout",1,false,1,9000,8000,10828434,"home",,14,"WALK",12.045414087420168,86627477 +323689,256660,"work",1,true,2,7000,10000,13271288,"work",32.636430904267925,15,"WALK",10.992779238372787,106170305 +323689,256660,"work",2,true,2,2000,7000,13271288,"work",,16,"WALK",0.07082651725982507,106170306 +323689,256660,"work",1,false,1,10000,2000,13271288,"home",,21,"WALK_LRF",0.4297270030393883,106170309 +323690,256660,"work",1,true,1,2000,10000,13271329,"work",,12,"WALK_LRF",0.4596535626737871,106170633 +323690,256660,"work",1,false,2,9000,2000,13271329,"escort",28.817376874427456,16,"WALK_LRF",0.46372062273525616,106170637 +323690,256660,"work",2,false,2,10000,9000,13271329,"home",,17,"WALK",8.803450356162276,106170638 +325431,257531,"othdiscr",1,true,1,16000,16000,13342696,"othdiscr",,13,"WALK",7.3308796254372535,106741569 +325431,257531,"othdiscr",1,false,3,3000,16000,13342696,"social",41.42675360474179,15,"WALK",6.752233766482679,106741573 +325431,257531,"othdiscr",2,false,3,7000,3000,13342696,"escort",49.88012520113926,15,"WALK",9.19862804139483,106741574 +325431,257531,"othdiscr",3,false,3,16000,7000,13342696,"home",,15,"WALK",12.573466152605935,106741575 +325431,257531,"work",1,true,1,14000,16000,13342710,"work",,16,"WALK_LOC",1.566089725814676,106741681 +325431,257531,"work",1,false,1,16000,14000,13342710,"home",,19,"WALK",1.5222029212145018,106741685 +325432,257531,"work",1,true,1,15000,16000,13342751,"work",,7,"WALK_LOC",1.5021356653376314,106742009 +325432,257531,"work",1,false,1,16000,15000,13342751,"home",,15,"WALK",1.4521916317367,106742013 +595684,370497,"escort",1,true,1,8000,21000,24423053,"escort",,8,"WALK",9.791811195810004,195384425 +595684,370497,"escort",1,false,1,21000,8000,24423053,"home",,8,"WALK",9.702536666511893,195384429 +595684,370497,"work",1,true,4,8000,21000,24423083,"shopping",26.54293521509044,17,"WALK",9.135886955872488,195384665 +595684,370497,"work",2,true,4,9000,8000,24423083,"work",27.389617213100415,18,"WALK",8.121041430209859,195384666 +595684,370497,"work",3,true,4,7000,9000,24423083,"work",32.660107643803926,18,"WALK",10.660973323175552,195384667 +595684,370497,"work",4,true,4,2000,7000,24423083,"work",,19,"WALK",0.19579318269854307,195384668 +595684,370497,"work",1,false,1,21000,2000,24423083,"home",,22,"SHARED2FREE",-0.47818074479683437,195384669 +595685,370497,"school",1,true,1,13000,21000,24423116,"school",,8,"WALK",-0.822926858448749,195384929 +595685,370497,"school",1,false,1,21000,13000,24423116,"home",,15,"WALK_LOC",-1.1046192102021704,195384933 +595686,370497,"school",1,true,1,8000,21000,24423157,"school",,7,"WALK",11.098406414435402,195385257 +595686,370497,"school",1,false,1,21000,8000,24423157,"home",,11,"WALK",11.011686392048867,195385261 +644292,386699,"school",1,true,1,7000,7000,26416003,"school",,7,"WALK",13.595037388471335,211328025 +644292,386699,"school",1,false,1,7000,7000,26416003,"home",,13,"WALK",13.595037388471335,211328029 +644476,386761,"work",1,true,1,24000,16000,26423555,"work",,7,"WALK_LOC",2.646413663361418,211388441 +644476,386761,"work",1,false,1,16000,24000,26423555,"home",,17,"WALK",2.375093970511917,211388445 +644477,386761,"work",1,true,1,4000,16000,26423596,"work",,8,"WALK",0.5554280270852756,211388769 +644477,386761,"work",1,false,3,8000,4000,26423596,"othdiscr",29.4847474381881,8,"WALK_LOC",0.3997266318260631,211388773 +644477,386761,"work",2,false,3,7000,8000,26423596,"shopping",47.722440883077674,18,"WALK_LOC",10.251476771714287,211388774 +644477,386761,"work",3,false,3,16000,7000,26423596,"home",,18,"WALK",10.843966918146847,211388775 +644478,386761,"school",1,true,2,3000,16000,26423629,"othmaint",47.90324508769601,8,"WALK_LOC",8.49305350371487,211389033 +644478,386761,"school",2,true,2,10000,3000,26423629,"school",,8,"WALK_LRF",11.18164971936148,211389034 +644478,386761,"school",1,false,3,6000,10000,26423629,"shopping",46.85697357578727,13,"WALK_LOC",9.1553027122261,211389037 +644478,386761,"school",2,false,3,9000,6000,26423629,"social",56.412023953286514,22,"WALK_LRF",13.261264313673598,211389038 +644478,386761,"school",3,false,3,16000,9000,26423629,"home",,23,"WALK_LRF",11.58486335321286,211389039 +1267567,570454,"eatout",1,true,1,9000,21000,51970253,"eatout",,11,"WALK",9.80969354428707,415762025 +1267567,570454,"eatout",1,false,1,21000,9000,51970253,"home",,11,"WALK",9.893393806466781,415762029 +1427193,703381,"shopping",1,true,1,25000,25000,58514946,"shopping",,15,"BIKE",13.723708538343773,468119569 +1427193,703381,"shopping",1,false,1,25000,25000,58514946,"home",,21,"BIKE",13.723708538343773,468119573 +1427194,703381,"othmaint",1,true,1,15000,25000,58514982,"othmaint",,9,"BIKE",0.5314989172255216,468119857 +1427194,703381,"othmaint",1,false,1,25000,15000,58514982,"home",,13,"BIKE",0.45960042238994253,468119861 +1427194,703381,"othmaint",1,true,1,8000,25000,58514983,"othmaint",,14,"WALK",7.518240674377764,468119865 +1427194,703381,"othmaint",1,false,1,25000,8000,58514983,"home",,16,"BIKE",7.524697959190778,468119869 +1427194,703381,"othmaint",1,true,1,22000,25000,58514984,"othmaint",,16,"WALK_LOC",1.8191857365021504,468119873 +1427194,703381,"othmaint",1,false,1,25000,22000,58514984,"home",,20,"WALK",1.7451811938516613,468119877 +1572659,763879,"othdiscr",1,true,1,7000,6000,64479044,"othdiscr",,5,"WALK",14.202826252443442,515832353 +1572659,763879,"othdiscr",1,false,1,6000,7000,64479044,"home",,13,"WALK",14.258626224164276,515832357 +1572930,764150,"eatout",1,true,1,9000,9000,64490136,"eatout",,7,"WALK",10.959173634837487,515921089 +1572930,764150,"eatout",1,false,1,9000,9000,64490136,"home",,16,"WALK",10.95917363483388,515921093 +1632206,823426,"work",1,true,1,1000,11000,66920485,"work",,7,"WALK",-1.5956224393312588,535363881 +1632206,823426,"work",1,false,1,11000,1000,66920485,"home",,18,"WALK",-1.639499846307082,535363885 +1632281,823501,"work",1,true,1,9000,12000,66923560,"work",,8,"WALK_LRF",9.072303922465583,535388481 +1632281,823501,"work",1,false,1,12000,9000,66923560,"home",,18,"WALK_LOC",9.08314468928761,535388485 +1632987,824207,"atwork",1,true,1,17000,13000,66952471,"atwork",,10,"WALK_LOC",6.277349865930346,535619769 +1632987,824207,"atwork",1,false,1,13000,17000,66952471,"work",,10,"WALK_LRF",6.267597398587461,535619773 +1632987,824207,"work",1,true,1,13000,18000,66952506,"work",,7,"WALK_LOC",-0.3201579365284811,535620049 +1632987,824207,"work",1,false,1,18000,13000,66952506,"home",,20,"WALK_LOC",-0.3594648843996779,535620053 +1875721,982875,"work",1,true,1,13000,16000,76904600,"work",,7,"DRIVEALONEFREE",0.5784679823301695,615236801 +1875721,982875,"work",1,false,1,16000,13000,76904600,"home",,19,"SHARED3FREE",-0.47557679706281664,615236805 +1875722,982875,"work",1,true,1,10000,16000,76904641,"work",,7,"WALK",5.550997745640507,615237129 +1875722,982875,"work",1,false,1,16000,10000,76904641,"home",,18,"WALK",5.572997834556732,615237133 +2159057,1099626,"work",1,true,1,2000,20000,88521376,"work",,7,"BIKE",-0.5791792872936957,708171009 +2159057,1099626,"work",1,false,1,20000,2000,88521376,"home",,17,"BIKE",-0.6248114915418729,708171013 +2159058,1099626,"univ",1,true,1,9000,20000,88521409,"univ",,7,"WALK",10.269026275933195,708171273 +2159058,1099626,"univ",1,false,1,20000,9000,88521409,"home",,14,"WALK_LOC",10.017804352037523,708171277 +2159059,1099626,"school",1,true,1,17000,20000,88521450,"school",,8,"WALK",2.5208933659011423,708171601 +2159059,1099626,"school",1,false,1,20000,17000,88521450,"home",,15,"WALK",2.489416163895632,708171605 +2458500,1173905,"othdiscr",1,true,1,8000,8000,100798525,"othdiscr",,13,"WALK",9.142685289582726,806388201 +2458500,1173905,"othdiscr",1,false,1,8000,8000,100798525,"home",,15,"WALK",9.142685147217486,806388205 +2458502,1173905,"school",1,true,1,21000,8000,100798613,"school",,9,"WALK_LOC",3.40061687064914,806388905 +2458502,1173905,"school",1,false,1,8000,21000,100798613,"home",,15,"WALK_LOC",3.467230900559858,806388909 +2458503,1173905,"school",1,true,1,9000,8000,100798654,"school",,8,"WALK",9.848192623423305,806389233 +2458503,1173905,"school",1,false,1,8000,9000,100798654,"home",,17,"WALK",9.848192623421339,806389237 +2566698,1196298,"othmaint",1,true,1,17000,25000,105234646,"othmaint",,14,"WALK",2.4320969181708274,841877169 +2566698,1196298,"othmaint",1,false,1,25000,17000,105234646,"home",,15,"WALK",2.0996177397663334,841877173 +2566698,1196298,"work",1,true,2,7000,25000,105234657,"work",34.781247802458935,7,"WALK",9.572003738684229,841877257 +2566698,1196298,"work",2,true,2,11000,7000,105234657,"work",,8,"WALK",2.589280616830245,841877258 +2566698,1196298,"work",1,false,3,7000,11000,105234657,"work",34.38399802414528,11,"WALK",2.5702215900039493,841877261 +2566698,1196298,"work",2,false,3,7000,7000,105234657,"eatout",46.33672423485007,12,"WALK",10.196777679530099,841877262 +2566698,1196298,"work",3,false,3,25000,7000,105234657,"home",,12,"WALK",9.426787291131419,841877263 +2566699,1196298,"escort",1,true,1,5000,25000,105234668,"escort",,8,"WALK",3.499661827044405,841877345 +2566699,1196298,"escort",1,false,1,25000,5000,105234668,"home",,9,"WALK",3.1439310716764095,841877349 +2566699,1196298,"escort",1,true,1,2000,25000,105234669,"escort",,18,"WALK",0.2207065371384486,841877353 +2566699,1196298,"escort",1,false,1,25000,2000,105234669,"home",,21,"WALK",0.0645230613261685,841877357 +2566699,1196298,"othdiscr",1,true,1,2000,25000,105234684,"othdiscr",,10,"WALK",0.2207063663573701,841877473 +2566699,1196298,"othdiscr",1,false,1,25000,2000,105234684,"home",,12,"WALK",0.06452268380940691,841877477 +2566699,1196298,"shopping",1,true,1,12000,25000,105234692,"shopping",,12,"WALK",4.331730930907167,841877537 +2566699,1196298,"shopping",1,false,1,25000,12000,105234692,"home",,17,"WALK",4.253611343861028,841877541 +2566700,1196298,"school",1,true,1,17000,25000,105234731,"school",,8,"WALK",3.3596916050588748,841877849 +2566700,1196298,"school",1,false,1,25000,17000,105234731,"home",,15,"WALK",2.844795965429825,841877853 +2566701,1196298,"escort",1,true,1,15000,25000,105234750,"escort",,18,"SHARED3FREE",0.7510367359743052,841878001 +2566701,1196298,"escort",1,false,1,25000,15000,105234750,"home",,19,"SHARED3FREE",0.5170571296970309,841878005 +2566701,1196298,"school",1,true,1,8000,25000,105234772,"school",,7,"WALK",10.377546474783648,841878177 +2566701,1196298,"school",1,false,1,25000,8000,105234772,"home",,13,"WALK",10.404646423723685,841878181 +2566702,1196298,"othdiscr",1,true,1,17000,25000,105234807,"othdiscr",,18,"WALK",3.8787174748250832,841878457 +2566702,1196298,"othdiscr",1,false,3,8000,17000,105234807,"othdiscr",38.41301895614709,20,"WALK",3.731436789708465,841878461 +2566702,1196298,"othdiscr",2,false,3,7000,8000,105234807,"escort",57.021060151258915,20,"WALK",12.331097986979803,841878462 +2566702,1196298,"othdiscr",3,false,3,25000,7000,105234807,"home",,20,"WALK",13.411567550566136,841878463 +2936848,1286557,"eatout",1,true,1,21000,11000,120410774,"eatout",,18,"WALK",4.184272891826802,963286193 +2936848,1286557,"eatout",1,false,1,11000,21000,120410774,"home",,18,"WALK",4.184272891826802,963286197 +2936848,1286557,"othmaint",1,true,3,25000,11000,120410796,"othmaint",43.9224625738787,13,"WALK",8.499031597964903,963286369 +2936848,1286557,"othmaint",2,true,3,7000,25000,120410796,"othmaint",45.899044597290825,13,"WALK_LOC",9.601221637377746,963286370 +2936848,1286557,"othmaint",3,true,3,7000,7000,120410796,"othmaint",,15,"WALK",9.766384206735802,963286371 +2936848,1286557,"othmaint",1,false,2,8000,7000,120410796,"shopping",42.99831282925046,16,"WALK_LOC",9.791851252806124,963286373 +2936848,1286557,"othmaint",2,false,2,11000,8000,120410796,"home",,16,"WALK",8.624638911562878,963286374 +2936848,1286557,"shopping",1,true,1,8000,11000,120410801,"shopping",,18,"WALK",12.13469405323234,963286409 +2936848,1286557,"shopping",1,false,3,7000,8000,120410801,"othdiscr",58.263702718491615,18,"WALK",12.357894046600236,963286413 +2936848,1286557,"shopping",2,false,3,7000,7000,120410801,"escort",61.6886580104208,19,"WALK",14.414866203326692,963286414 +2936848,1286557,"shopping",3,false,3,11000,7000,120410801,"home",,19,"WALK",13.856866219908829,963286415 +3061894,1363467,"othmaint",1,true,1,4000,24000,125537682,"othmaint",,8,"WALK_LOC",1.2581477773605165,1004301457 +3061894,1363467,"othmaint",1,false,1,24000,4000,125537682,"home",,10,"WALK",1.257177917863963,1004301461 +3061894,1363467,"shopping",1,true,2,7000,24000,125537687,"othmaint",36.82889013969701,8,"WALK_LOC",13.495011906378089,1004301497 +3061894,1363467,"shopping",2,true,2,2000,7000,125537687,"shopping",,8,"WALK_LOC",0.565184198020481,1004301498 +3061894,1363467,"shopping",1,false,2,25000,2000,125537687,"eatout",34.99825728868734,8,"WALK_LOC",0.6339313982356126,1004301501 +3061894,1363467,"shopping",2,false,2,24000,25000,125537687,"home",,8,"WALK_LOC",13.011744626496117,1004301502 +3061895,1363467,"othmaint",1,true,1,11000,24000,125537723,"othmaint",,8,"WALK",3.6903296324714514,1004301785 +3061895,1363467,"othmaint",1,false,1,24000,11000,125537723,"home",,8,"WALK_LOC",3.732966741421779,1004301789 +3061895,1363467,"shopping",1,true,2,25000,24000,125537728,"shopping",41.84307766687776,8,"WALK_LOC",12.887343785241576,1004301825 +3061895,1363467,"shopping",2,true,2,5000,25000,125537728,"shopping",,11,"WALK_LOC",4.34963250656242,1004301826 +3061895,1363467,"shopping",1,false,1,24000,5000,125537728,"home",,20,"WALK_LOC",4.22919350865673,1004301829 +3188483,1402945,"othmaint",1,true,1,9000,25000,130727831,"othmaint",,10,"WALK_LOC",6.481484248057271,1045822649 +3188483,1402945,"othmaint",1,false,4,6000,9000,130727831,"eatout",36.66127051218098,10,"WALK_LOC",6.777434160719099,1045822653 +3188483,1402945,"othmaint",2,false,4,16000,6000,130727831,"shopping",32.924413542253355,11,"WALK_LOC",7.647906411548703,1045822654 +3188483,1402945,"othmaint",3,false,4,7000,16000,130727831,"eatout",33.5485700998544,11,"WALK_LOC",4.406350241888349,1045822655 +3188483,1402945,"othmaint",4,false,4,25000,7000,130727831,"home",,11,"WALK_LOC",8.242503883378836,1045822656 +3188483,1402945,"shopping",1,true,1,11000,25000,130727836,"shopping",,14,"BIKE",4.467420723409526,1045822689 +3188483,1402945,"shopping",1,false,1,25000,11000,130727836,"home",,15,"BIKE",4.420260383253186,1045822693 +3188484,1402945,"work",1,true,1,14000,25000,130727883,"work",,15,"DRIVEALONEFREE",0.8430101496948217,1045823065 +3188484,1402945,"work",1,false,1,25000,14000,130727883,"home",,17,"WALK",0.5323817807935262,1045823069 +3188485,1402945,"work",1,true,1,5000,25000,130727924,"work",,8,"WALK",3.176125996500932,1045823393 +3188485,1402945,"work",1,false,1,25000,5000,130727924,"home",,18,"WALK",2.885731258806822,1045823397 +3232955,1444715,"escort",1,true,1,16000,14000,132551164,"escort",,15,"WALK",6.99607949876782,1060409313 +3232955,1444715,"escort",1,false,1,14000,16000,132551164,"home",,16,"WALK",7.1523194714836,1060409317 +3232955,1444715,"work",1,true,1,13000,14000,132551194,"work",,6,"WALK",-0.6306362451985411,1060409553 +3232955,1444715,"work",1,false,1,14000,13000,132551194,"home",,11,"WALK",-0.6306362451985411,1060409557 +3232955,1444715,"work",1,true,1,13000,14000,132551195,"work",,11,"SHARED2FREE",0.452335034485567,1060409561 +3232955,1444715,"work",1,false,1,14000,13000,132551195,"home",,14,"SHARED2FREE",-0.2662941409407339,1060409565 +3233462,1445222,"atwork",1,true,1,5000,21000,132571946,"atwork",,10,"WALK",4.54810209752725,1060575569 +3233462,1445222,"atwork",1,false,2,3000,5000,132571946,"work",38.52996463706063,10,"WALK",4.689176924751054,1060575573 +3233462,1445222,"atwork",2,false,2,21000,3000,132571946,"work",,10,"WALK",9.145243974232187,1060575574 +3233462,1445222,"work",1,true,1,21000,17000,132571981,"work",,9,"DRIVEALONEFREE",1.5724421545628722,1060575849 +3233462,1445222,"work",1,false,4,7000,21000,132571981,"work",30.842278186739854,17,"WALK",1.8020475726295213,1060575853 +3233462,1445222,"work",2,false,4,6000,7000,132571981,"othmaint",42.469375553808376,17,"WALK",10.073578746267579,1060575854 +3233462,1445222,"work",3,false,4,7000,6000,132571981,"escort",40.40244821152917,17,"WALK",8.854601045166811,1060575855 +3233462,1445222,"work",4,false,4,17000,7000,132571981,"home",,20,"WALK",8.238896211056357,1060575856 +3328568,1511234,"work",1,true,1,13000,8000,136471327,"work",,8,"WALK_LOC",-0.2435902544788206,1091770617 +3328568,1511234,"work",1,false,2,7000,13000,136471327,"escort",30.784963133093992,16,"WALK",-0.20149926925026043,1091770621 +3328568,1511234,"work",2,false,2,8000,7000,136471327,"home",,22,"WALK",11.478676207869436,1091770622 +3328569,1511234,"univ",1,true,1,9000,8000,136471360,"univ",,8,"WALK_LOC",10.078747551647542,1091770881 +3328569,1511234,"univ",1,false,1,8000,9000,136471360,"home",,16,"WALK_LOC",10.077775953315784,1091770885 +3495342,1594621,"atwork",1,true,4,8000,8000,143309026,"escort",59.78358512236625,11,"WALK",12.546654055806371,1146472209 +3495342,1594621,"atwork",2,true,4,7000,8000,143309026,"eatout",62.69554661345672,11,"WALK",14.39426624162804,1146472210 +3495342,1594621,"atwork",3,true,4,8000,7000,143309026,"eatout",58.670251524714686,11,"WALK",12.253374125394837,1146472211 +3495342,1594621,"atwork",4,true,4,7000,8000,143309026,"atwork",,11,"WALK",14.39426624162804,1146472212 +3495342,1594621,"atwork",1,false,1,8000,7000,143309026,"work",,11,"WALK",14.198746283189761,1146472213 +3495342,1594621,"work",1,true,1,8000,10000,143309061,"work",,8,"WALK",10.017940348880348,1146472489 +3495342,1594621,"work",1,false,1,10000,8000,143309061,"home",,17,"WALK",10.028242430555887,1146472493 +3495343,1594621,"shopping",1,true,2,8000,10000,143309096,"eatout",34.050659137480324,15,"WALK",11.766414100586582,1146472769 +3495343,1594621,"shopping",2,true,2,2000,8000,143309096,"shopping",,15,"WALK",-0.381439934127238,1146472770 +3495343,1594621,"shopping",1,false,2,25000,2000,143309096,"shopping",29.547104219689846,16,"WALK",0.06466501966771307,1146472773 +3495343,1594621,"shopping",2,false,2,10000,25000,143309096,"home",,16,"WALK",11.03258173332068,1146472774 +3596364,1645132,"univ",1,true,1,9000,9000,147450955,"univ",,11,"WALK",10.238432625148134,1179607641 +3596364,1645132,"univ",1,false,1,9000,9000,147450955,"home",,11,"WALK",10.238432625148134,1179607645 +3596364,1645132,"shopping",1,true,2,7000,9000,147450957,"othmaint",33.13947297558887,10,"WALK",12.302151262950213,1179607657 +3596364,1645132,"shopping",2,true,2,13000,7000,147450957,"shopping",,10,"TNC_SINGLE",-0.03534115978056812,1179607658 +3596364,1645132,"shopping",1,false,2,8000,13000,147450957,"shopping",28.5289091077587,11,"DRIVEALONEFREE",-0.04627866111274048,1179607661 +3596364,1645132,"shopping",2,false,2,9000,8000,147450957,"home",,11,"WALK",10.568844642356845,1179607662 +3596365,1645132,"school",1,true,1,11000,9000,147450996,"school",,10,"WALK",4.197503986285156,1179607969 +3596365,1645132,"school",1,false,3,25000,11000,147450996,"shopping",34.1106297943011,17,"WALK",3.2273280548831877,1179607973 +3596365,1645132,"school",2,false,3,6000,25000,147450996,"othmaint",53.965718905093475,17,"WALK",12.120015882476332,1179607974 +3596365,1645132,"school",3,false,3,9000,6000,147450996,"home",,17,"WALK",11.33462804101704,1179607975 +3891102,1747467,"atwork",1,true,1,8000,10000,159535186,"atwork",,11,"WALK",12.05033424549614,1276281489 +3891102,1747467,"atwork",1,false,2,8000,8000,159535186,"eatout",55.06169482702508,11,"WALK",12.546654055641511,1276281493 +3891102,1747467,"atwork",2,false,2,10000,8000,159535186,"work",,11,"WALK",12.087934172984744,1276281494 +3891102,1747467,"work",1,true,2,7000,16000,159535221,"escort",45.82929445712504,8,"WALK",11.099112493958854,1276281769 +3891102,1747467,"work",2,true,2,10000,7000,159535221,"work",,10,"WALK",8.30033879779025,1276281770 +3891102,1747467,"work",1,false,2,6000,10000,159535221,"shopping",41.9136622315094,17,"WALK_LOC",8.184787697293078,1276281773 +3891102,1747467,"work",2,false,2,16000,6000,159535221,"home",,21,"WALK_LOC",9.754298157016313,1276281774 +3891104,1747467,"othdiscr",1,true,1,17000,16000,159535289,"othdiscr",,7,"WALK",5.9419631748312804,1276282313 +3891104,1747467,"othdiscr",1,false,1,16000,17000,159535289,"home",,22,"WALK",5.85268329864962,1276282317 +4171615,1810015,"univ",1,true,1,14000,16000,171036246,"univ",,18,"WALK_LOC",0.6138935880581958,1368289969 +4171615,1810015,"univ",1,false,1,16000,14000,171036246,"home",,18,"WALK_LOC",0.6598392789087326,1368289973 +4171616,1810015,"shopping",1,true,1,14000,16000,171036289,"shopping",,10,"WALK",1.0944891382847342,1368290313 +4171616,1810015,"shopping",1,false,1,16000,14000,171036289,"home",,14,"WALK",0.938251329159558,1368290317 +4171617,1810015,"atwork",1,true,1,2000,1000,171036301,"atwork",,13,"WALK",0.5601366034839007,1368290409 +4171617,1810015,"atwork",1,false,2,7000,2000,171036301,"escort",35.42201416387718,16,"WALK",0.060794856605562796,1368290413 +4171617,1810015,"atwork",2,false,2,1000,7000,171036301,"work",,16,"WALK",13.860346322897334,1368290414 +4171617,1810015,"work",1,true,1,1000,16000,171036336,"work",,8,"WALK",-1.189732598419018,1368290689 +4171617,1810015,"work",1,false,1,16000,1000,171036336,"home",,16,"WALK",-1.3617459542702337,1368290693 +4171619,1810015,"othdiscr",1,true,1,9000,16000,171036404,"othdiscr",,9,"WALK_LOC",10.16759626945202,1368291233 +4171619,1810015,"othdiscr",1,false,1,16000,9000,171036404,"home",,19,"WALK_LRF",11.465161395499365,1368291237 +4171622,1810015,"othmaint",1,true,1,7000,16000,171036530,"othmaint",,11,"WALK_LOC",8.321092416462646,1368292241 +4171622,1810015,"othmaint",1,false,1,16000,7000,171036530,"home",,12,"WALK_LOC",8.2980822985276,1368292245 +4823797,1952792,"work",1,true,1,15000,14000,197775716,"work",,10,"WALK",0.600939366364589,1582205729 +4823797,1952792,"work",1,false,1,14000,15000,197775716,"home",,18,"WALK",0.600939366364589,1582205733 +5057160,2048204,"work",1,true,1,7000,5000,207343599,"work",,6,"WALK",11.571833286192984,1658748793 +5057160,2048204,"work",1,false,1,5000,7000,207343599,"home",,17,"WALK",11.581607356007767,1658748797 +5057338,2048382,"work",1,true,1,16000,7000,207350897,"work",,7,"WALK",5.537688024074189,1658807177 +5057338,2048382,"work",1,false,1,7000,16000,207350897,"home",,20,"WALK_LOC",5.628108606494237,1658807181 +5387762,2223027,"work",1,true,1,14000,9000,220898281,"work",,6,"WALK_LRF",1.3971297604721287,1767186249 +5387762,2223027,"work",1,false,1,9000,14000,220898281,"home",,15,"WALK_LRF",1.5019383031152798,1767186253 +5387763,2223027,"eatout",1,true,1,12000,9000,220898289,"eatout",,10,"WALK",3.812789544550936,1767186313 +5387763,2223027,"eatout",1,false,1,9000,12000,220898289,"home",,16,"WALK",3.756989867930841,1767186317 +5387763,2223027,"othdiscr",1,true,1,16000,9000,220898308,"othdiscr",,18,"WALK_LRF",7.846116133448075,1767186465 +5387763,2223027,"othdiscr",1,false,1,9000,16000,220898308,"home",,18,"WALK_LRF",7.8452599749849705,1767186469 +5389226,2223759,"work",1,true,1,4000,16000,220958305,"work",,8,"WALK",-0.3774592859607151,1767666441 +5389226,2223759,"work",1,false,1,16000,4000,220958305,"home",,17,"WALK",-0.4791731466008505,1767666445 +5389227,2223759,"atwork",1,true,1,15000,16000,220958311,"atwork",,10,"WALK",1.1096490706326976,1767666489 +5389227,2223759,"atwork",1,false,1,16000,15000,220958311,"work",,10,"WALK",1.0173084578996947,1767666493 +5389227,2223759,"escort",1,true,1,5000,16000,220958316,"escort",,15,"WALK",4.089263631480166,1767666529 +5389227,2223759,"escort",1,false,1,16000,5000,220958316,"home",,15,"WALK",4.01114390934704,1767666533 +5389227,2223759,"work",1,true,1,16000,16000,220958346,"work",,6,"WALK",5.450624006214329,1767666769 +5389227,2223759,"work",1,false,1,16000,16000,220958346,"home",,15,"WALK",5.450624005397038,1767666773 +7305540,2727273,"social",1,true,1,3000,20000,299527176,"social",,10,"WALK_LOC",5.766458225385338,2396217409 +7305540,2727273,"social",1,false,1,20000,3000,299527176,"home",,12,"WALK_LRF",6.632760786728702,2396217413 +7305540,2727273,"social",1,true,1,5000,20000,299527177,"social",,17,"WALK",2.0260650444736386,2396217417 +7305540,2727273,"social",1,false,2,8000,5000,299527177,"eatout",28.931822498294338,19,"WALK",2.7960049174334722,2396217421 +7305540,2727273,"social",2,false,2,20000,8000,299527177,"home",,19,"WALK",7.191368217582532,2396217422 +7305540,2727273,"work",1,true,1,24000,20000,299527179,"work",,13,"BIKE",1.6028747415427587,2396217433 +7305540,2727273,"work",1,false,1,20000,24000,299527179,"home",,16,"BIKE",1.5355807172674794,2396217437 +7305541,2727273,"shopping",1,true,1,16000,20000,299527214,"shopping",,16,"WALK",7.064692988783104,2396217713 +7305541,2727273,"shopping",1,false,1,20000,16000,299527214,"home",,16,"WALK_LOC",7.09909709224648,2396217717 +7305541,2727273,"social",1,true,1,2000,20000,299527217,"social",,18,"SHARED2FREE",-0.09077398290598436,2396217737 +7305541,2727273,"social",1,false,1,20000,2000,299527217,"home",,20,"WALK_LRF",0.9428070575911951,2396217741 +7305541,2727273,"work",1,true,1,5000,20000,299527220,"work",,7,"BIKE",3.05751180409295,2396217761 +7305541,2727273,"work",1,false,1,20000,5000,299527220,"home",,15,"BIKE",3.0382746070009836,2396217765 +7453413,2762078,"othmaint",1,true,1,16000,20000,305589961,"othmaint",,11,"WALK_LOC",4.900744546758276,2444719689 +7453413,2762078,"othmaint",1,false,1,20000,16000,305589961,"home",,14,"WALK",4.758208352965957,2444719693 +7511873,2820538,"work",1,true,1,16000,8000,307986832,"work",,7,"WALK_LOC",5.168165806764919,2463894657 +7511873,2820538,"work",1,false,1,8000,16000,307986832,"home",,15,"WALK",5.101211355552056,2463894661 +7512109,2820774,"work",1,true,1,9000,8000,307996508,"work",,7,"WALK",8.711826881041683,2463972065 +7512109,2820774,"work",1,false,1,8000,9000,307996508,"home",,18,"WALK",8.7124050619515,2463972069 +7512514,2821179,"work",1,true,1,5000,8000,308013113,"work",,18,"WALK",3.1849050442076052,2464104905 +7512514,2821179,"work",1,false,1,8000,5000,308013113,"home",,21,"WALK",3.1849050440009243,2464104909 +7513432,2822097,"social",1,true,1,9000,8000,308050748,"social",,9,"WALK_LOC",7.114170124202285,2464405985 +7513432,2822097,"social",1,false,2,6000,9000,308050748,"eatout",38.34416286222316,16,"WALK",7.049697414854915,2464405989 +7513432,2822097,"social",2,false,2,8000,6000,308050748,"home",,16,"WALK_LOC",8.114764225878853,2464405990 +7513554,2822219,"work",1,true,2,9000,8000,308055753,"eatout",27.180553888573456,10,"WALK",8.722225942224798,2464446025 +7513554,2822219,"work",2,true,2,2000,9000,308055753,"work",,12,"WALK_LRF",0.23693298760248696,2464446026 +7513554,2822219,"work",1,false,1,8000,2000,308055753,"home",,21,"WALK",0.4631451167473608,2464446029 +7523517,2832182,"shopping",1,true,1,20000,7000,308464230,"shopping",,15,"WALK_LOC",2.069543208835438,2467713841 +7523517,2832182,"shopping",1,false,1,7000,20000,308464230,"home",,15,"WALK_LOC",2.402219895088615,2467713845 From c7869193a59657142c33f56842db7246dd33ad3c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 7 Apr 2026 22:17:33 +1000 Subject: [PATCH 196/298] progressive 2 zone test --- .../test/reference_pipeline_2_zone_eet.zip | Bin 0 -> 263089 bytes .../test/test_multiple_zone.py | 39 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 activitysim/examples/placeholder_multiple_zone/test/reference_pipeline_2_zone_eet.zip diff --git a/activitysim/examples/placeholder_multiple_zone/test/reference_pipeline_2_zone_eet.zip b/activitysim/examples/placeholder_multiple_zone/test/reference_pipeline_2_zone_eet.zip new file mode 100644 index 0000000000000000000000000000000000000000..3e3b2419b009d3d4fc6b9e3282a10c0b17b5fa8a GIT binary patch literal 263089 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 z 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℞T{Ec!f%S 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�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 {zone} zone {step_name}: ok") +@test.run_if_exists("reference_pipeline_2_zone_eet.zip") +def test_multizone_progressive_eet(): + + import activitysim.abm # register components + + def test_path(dirname): + return os.path.join(os.path.dirname(__file__), dirname) + + state = workflow.State.make_default( + configs_dir=( + test_path(f"configs_eet"), + test_path(f"configs_2_zone"), + example_path(f"configs_2_zone"), + mtc_example_path("configs"), + ), + data_dir=(example_path(f"data_2"),), + output_dir=test_path("output"), + settings_file_name="settings.yaml", + ) + + assert state.settings.models == EXPECTED_MODELS + assert state.settings.chunk_size == 0 + assert state.settings.sharrow == False + assert state.settings.use_explicit_error_terms == True + + for step_name in EXPECTED_MODELS: + state.run.by_name(step_name) + try: + state.checkpoint.check_against( + Path(__file__).parent.joinpath("reference_pipeline_2_zone_eet.zip"), + checkpoint_name=step_name, + ) + except Exception: + print(f"> 2 zone eet {step_name}: ERROR") + raise + else: + print(f"> 2 zone {step_name}: ok") + + if __name__ == "__main__": build_data() From 0d2356e3cc43e38b2116fb97f4eacd11c75d916f Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Thu, 9 Apr 2026 13:46:47 +1000 Subject: [PATCH 197/298] Copy changes from m-richards:matt/poisson_eet_2026_04 --- activitysim/abm/models/location_choice.py | 15 +++ .../abm/models/parking_location_choice.py | 9 +- activitysim/abm/models/trip_destination.py | 21 ++-- .../abm/models/trip_scheduling_choice.py | 4 + .../core/interaction_sample_simulate.py | 30 ++++- activitysim/core/logit.py | 112 ++++++++++++++++-- activitysim/core/test/test_logit.py | 79 +++++++++++- 7 files changed, 247 insertions(+), 23 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 7c8ef16db8..1d7c76851a 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -18,6 +18,7 @@ from activitysim.core.exceptions import DuplicateWorkflowTableError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate +from activitysim.core.logit import AltsContext from activitysim.core.util import reindex """ @@ -603,6 +604,7 @@ def run_location_simulate( chunk_tag, trace_label, skip_choice=False, + alts_context: AltsContext | None = None, ): """ run location model on location_sample annotated with mode_choice logsum @@ -712,6 +714,7 @@ def run_location_simulate( compute_settings=model_settings.compute_settings.subcomponent_settings( "simulate" ), + alts_context=alts_context, ) if not want_logsums: @@ -737,6 +740,7 @@ def run_location_choice( chunk_tag, trace_label, skip_choice=False, + alts_context: AltsContext | None = None, ): """ Run the three-part location choice algorithm to generate a location choice for each chooser @@ -756,6 +760,8 @@ def run_location_choice( model_settings : dict chunk_size : int trace_label : str + skip_choice : bool + alts_context : AltsContext or None Returns ------- @@ -788,6 +794,13 @@ def run_location_choice( if choosers.shape[0] == 0: logger.info(f"{trace_label} skipping segment {segment_name}: no choosers") continue + # using land use rather than size terms in case something goes 0 base -> nonzero project, double + # check if that would be in dest_size_terms as a zero + alts_context = AltsContext.from_series( + dest_size_terms.index + ) # index zone_id, not ALT_DEST_COL_NAME + # assumes that dest_size_terms will always contain zeros for non-attractive zones, i.e. it will have the + # same length as land_use # - location_sample location_sample_df = run_location_sample( @@ -803,6 +816,7 @@ def run_location_choice( trace_label=tracing.extend_trace_label( trace_label, "sample.%s" % segment_name ), + alts_context=alts_context, ) # - location_logsums @@ -841,6 +855,7 @@ def run_location_choice( trace_label, "simulate.%s" % segment_name ), skip_choice=skip_choice, + alts_context=alts_context, ) if estimator: diff --git a/activitysim/abm/models/parking_location_choice.py b/activitysim/abm/models/parking_location_choice.py index 32f3aabee2..d4e7cd246c 100644 --- a/activitysim/abm/models/parking_location_choice.py +++ b/activitysim/abm/models/parking_location_choice.py @@ -20,10 +20,11 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LogitComponentSettings +from activitysim.core.exceptions import DuplicateWorkflowTableError from activitysim.core.interaction_sample_simulate import interaction_sample_simulate +from activitysim.core.logit import AltsContext from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, drop_unused_columns -from activitysim.core.exceptions import DuplicateWorkflowTableError logger = logging.getLogger(__name__) @@ -112,6 +113,7 @@ def parking_destination_simulate( chunk_size, trace_hh_id, trace_label, + alts_context: AltsContext | None = None, ): """ Chose destination from destination_sample (with od_logsum and dp_logsum columns added) @@ -150,6 +152,7 @@ def parking_destination_simulate( trace_label=trace_label, trace_choice_name="parking_loc", explicit_chunk_size=model_settings.explicit_chunk, + alts_context=alts_context, ) # drop any failed zero_prob destinations @@ -211,6 +214,9 @@ def choose_parking_location( ) destination_sample.index = np.repeat(trips.index.values, len(alternatives)) destination_sample.index.name = trips.index.name + # using destination_sample would also be right because destination_sample isn't a sample here, + # but that could change + alts_context = AltsContext.from_series(alternatives[alt_dest_col_name]) destinations = parking_destination_simulate( state, @@ -223,6 +229,7 @@ def choose_parking_location( chunk_size=chunk_size, trace_hh_id=trace_hh_id, trace_label=trace_label, + alts_context=alts_context, ) if want_sample_table: diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 853cfc35e9..59b7f22392 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -30,12 +30,13 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LocationComponentSettings +from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate +from activitysim.core.logit import AltsContext from activitysim.core.skim_dictionary import DataFrameMatrix from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, reindex -from activitysim.core.exceptions import InvalidTravelError, DuplicateWorkflowTableError logger = logging.getLogger(__name__) @@ -950,6 +951,7 @@ def trip_destination_simulate( skim_hotel, estimator, trace_label, + alts_context: AltsContext | None = None, ): """ Chose destination from destination_sample (with od_logsum and dp_logsum columns added) @@ -1036,6 +1038,7 @@ def trip_destination_simulate( trace_choice_name="trip_dest", estimator=estimator, explicit_chunk_size=model_settings.explicit_chunk, + alts_context=alts_context, ) if not want_logsums: @@ -1126,7 +1129,10 @@ def choose_trip_destination( destination_sample["dp_logsum"] = 0.0 t0 = print_elapsed_time("%s.compute_logsums" % trace_label, t0, debug=True) - + alt_dest_col_name = model_settings.ALT_DEST_COL_NAME + alts = alternatives.index + assert alts.name == alt_dest_col_name + alts_context = AltsContext.from_series(alts) destinations = trip_destination_simulate( state, primary_purpose=primary_purpose, @@ -1138,6 +1144,7 @@ def choose_trip_destination( skim_hotel=skim_hotel, estimator=estimator, trace_label=trace_label, + alts_context=alts_context, ) dropped_trips = ~trips.index.isin(destinations.index) @@ -1520,13 +1527,13 @@ def run_trip_destination( """ When using the trip destination model with sharrow, it is necessary - to set a value for `purpose_index_num` in the trip destination - annotate trips preprocessor. This allows for an optimized compiled + to set a value for `purpose_index_num` in the trip destination + annotate trips preprocessor. This allows for an optimized compiled lookup of the size term from the array of size terms. The value of - `purpose_index_num` should be the integer column position in the size - matrix, with usual zero-based numpy indexing semantics (i.e. the first + `purpose_index_num` should be the integer column position in the size + matrix, with usual zero-based numpy indexing semantics (i.e. the first column is zero). The preprocessor expression most likely needs to be - "size_terms.get_cols(df.purpose)" unless some unusual transform of + "size_terms.get_cols(df.purpose)" unless some unusual transform of size terms has been employed. """ diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index 81d908ef1b..3eb695feb5 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -20,6 +20,7 @@ ) from activitysim.core.configuration.logit import LogitComponentSettings from activitysim.core.interaction_sample_simulate import _interaction_sample_simulate +from activitysim.core.logit import AltsContext from activitysim.core.skim_dataset import SkimDataset from activitysim.core.skim_dictionary import SkimDict @@ -314,6 +315,9 @@ def run_trip_scheduling_choice( estimator=None, chunk_sizer=chunk_sizer, compute_settings=model_settings.compute_settings, + alts_context=AltsContext( + schedules[SCHEDULE_ID].min(), schedules[SCHEDULE_ID].max() + ), ) assert len(choices.index) == len(choosers.index) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index e73f64f4fb..115f4f7084 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -9,8 +9,9 @@ from activitysim.core import chunk, interaction_simulate, logit, tracing, util, workflow from activitysim.core.configuration.base import ComputeSettings -from activitysim.core.simulate import set_skim_wrapper_targets from activitysim.core.exceptions import SegmentedSpecificationError +from activitysim.core.logit import AltsContext +from activitysim.core.simulate import set_skim_wrapper_targets logger = logging.getLogger(__name__) @@ -34,6 +35,7 @@ def _interaction_sample_simulate( *, chunk_sizer: chunk.ChunkSizer, compute_settings: ComputeSettings | None = None, + alts_context: AltsContext | None = None, ): """ Run a MNL simulation in the situation in which alternatives must @@ -220,7 +222,7 @@ def _interaction_sample_simulate( ) chunk_sizer.log_df(trace_label, "interaction_utilities", interaction_utilities) - del interaction_df + del interaction_df # TODO-TS: this was deleted in M.Richards commit, relevant to altscontext or other? chunk_sizer.log_df(trace_label, "interaction_df", None) if have_trace_targets: @@ -264,6 +266,7 @@ def _interaction_sample_simulate( # insert the zero-prob utilities to pad each alternative set to same size padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999) + padded_alt_nrs = np.insert(interaction_df[choice_column], inserts, -999) chunk_sizer.log_df(trace_label, "padded_utilities", padded_utilities) del inserts @@ -272,11 +275,19 @@ def _interaction_sample_simulate( # reshape to array with one row per chooser, one column per alternative padded_utilities = padded_utilities.reshape(-1, max_sample_count) + padded_alt_nrs = padded_alt_nrs.reshape(-1, max_sample_count) # convert to a dataframe with one row per chooser and one column per alternative utilities_df = pd.DataFrame(padded_utilities, index=choosers.index) chunk_sizer.log_df(trace_label, "utilities_df", utilities_df) + # alt_nrs_df has columns for each alt in the choice set, with values indicating which alt_id + # they correspond to (as opposed to the 0-n index implied by the column number). + if alts_context is not None: + alt_nrs_df = pd.DataFrame(padded_alt_nrs, index=choosers.index) + else: + alt_nrs_df = None # if we don't provide the number of dense alternatives, assume that we'll use the old approach + del padded_utilities chunk_sizer.log_df(trace_label, "padded_utilities", None) @@ -320,7 +331,12 @@ def _interaction_sample_simulate( # positions is series with the chosen alternative represented as a column index in utilities_df # which is an integer between zero and num alternatives in the alternative sample positions, rands = logit.make_choices_utility_based( - state, utilities_df, trace_label=trace_label, trace_choosers=choosers + state, + utilities_df, + trace_label=trace_label, + trace_choosers=choosers, + alts_context=alts_context, + alt_nrs_df=alt_nrs_df, ) del utilities_df @@ -451,6 +467,7 @@ def interaction_sample_simulate( skip_choice=False, explicit_chunk_size=0, *, + alts_context: AltsContext | None = None, compute_settings: ComputeSettings | None = None, ): """ @@ -496,6 +513,12 @@ def interaction_sample_simulate( explicit_chunk_size : float, optional If > 0, specifies the chunk size to use when chunking the interaction simulation. If < 1, specifies the fraction of the total number of choosers. + alts_context: int, optional + The number of alternatives available in the choice set in the absense of sampling. + This is used with EET simulation to ensure consistent random numbers across the whole alternative set + ( as the sampled set may change between base and project). When not provided, + the fallback approach is used which may result in frozen error terms being applied to the wrong alternatives + if the choice set changes. Returns ------- @@ -551,6 +574,7 @@ def interaction_sample_simulate( skip_choice, chunk_sizer=chunk_sizer, compute_settings=compute_settings, + alts_context=alts_context, ) result_list.append(choices) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 5cb7774f47..625830bafe 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -4,6 +4,7 @@ import logging import warnings +from dataclasses import dataclass import numpy as np import pandas as pd @@ -29,6 +30,40 @@ PROB_MIN = 0.0 PROB_MAX = 1.0 +FREEZE_RANDOM_NUMBERS_FOR_DENSE_ALTERNATIVE_SET = True + + +@dataclass +class AltsContext: + """Representation of the alternatives without carrying around that full array.""" + + min_alt_id: int + max_alt_id: int + + def __post_init__(self): + # e.g. for zero based zones max_alt_id = n_alts - 1 + # but for 1 based zones, we don't need to add extra padding + self.n_rands_to_sample = max(self.max_alt_id, self.n_alts_to_cover_max_id) + + @classmethod + def from_series(cls, ser: Union[pd.Series, pd.Index]) -> "AltsContext": + min_alt_id = ser.min() + max_alt_id = ser.max() + return cls(min_alt_id, max_alt_id) + + @classmethod + def from_num_alts(cls, num_alts: int, zero_based: bool = True) -> "AltsContext": + if zero_based: + offset = -1 + else: + offset = 0 + return cls(min_alt_id=1 + offset, max_alt_id=num_alts + offset) + + @property + def n_alts_to_cover_max_id(self) -> int: + """If zones were non-consecutive, this could be a big over-estimate.""" + return self.max_alt_id + 1 + def report_bad_choices( state: workflow.State, @@ -343,7 +378,12 @@ def utils_to_probs( return probs -def add_ev1_random(state: workflow.State, df: pd.DataFrame): +def add_ev1_random( + state: workflow.State, + df: pd.DataFrame, + alt_info: AltsContext | None = None, + alt_nrs_df: pd.DataFrame | None = None, +): """ Add iid EV1 (Gumbel) random error terms to utilities for EET choice. @@ -359,9 +399,41 @@ def add_ev1_random(state: workflow.State, df: pd.DataFrame): Utilities with EV1 errors added. """ nest_utils_for_choice = df.copy() - nest_utils_for_choice += state.get_rn_generator().gumbel_for_df( - nest_utils_for_choice, n=nest_utils_for_choice.shape[1] - ) + assert (alt_info is None) == ( + alt_nrs_df is None + ), "n_zones and alt_nrs_df must both be provided or omitted together" + + if alt_nrs_df is not None and FREEZE_RANDOM_NUMBERS_FOR_DENSE_ALTERNATIVE_SET: + assert alt_info is not None # narrowing for mypy + + idx_array = alt_nrs_df.values + mask = idx_array == -999 + safe_idx = np.where( + mask, 1, idx_array + ) # replace -999 with a temp value inbounds + # generate random number for all alts - this is wasteful, but ensures that the same zone + # gets the same random number if the sampled choice set changes between base and project + # (alternatively, one could seed a channel for (persons x zones) and use the zone seed to ensure consistency. + # Trade off is needing to seed (persons x zones) rows and multiindex channels to + # avoid extra random numbers generated here. Quick benchmark suggests seeding per row is likely slower + rands_dense = state.get_rn_generator().gumbel_for_df( + nest_utils_for_choice, n=alt_info.n_alts_to_cover_max_id + ) + # generate n=alt_info.max_alt_id+1 rather than n_alts so that indexing works + # (this is drawing a random number for a redundant zeroth zone in 1 based zoning systems) + # TODO deal with non 0->n-1 indexed land use more efficiently? ideally do where alt_nrs_df is constructed, + # not on the fly here. Potentially via state.get_injectable('network_los').get_skim_dict('taz').zone_ids + rands = np.take_along_axis(rands_dense, safe_idx, axis=1) + rands[ + mask + ] = 0 # zero out the masked zones so they don't have the util adjustment of alt 0 + else: + # old behaviour, to remove + rands = state.get_rn_generator().gumbel_for_df( + nest_utils_for_choice, n=nest_utils_for_choice.shape[1] + ) + + nest_utils_for_choice += rands return nest_utils_for_choice @@ -387,6 +459,8 @@ def make_choices_explicit_error_term_nl( trace_label, trace_choosers=None, allow_bad_utils=False, + alts_context: AltsContext | None = None, + alt_nrs_df: pd.DataFrame | None = None, ): """ Walk down the nesting tree and make a choice at each level using EET. @@ -412,7 +486,9 @@ def make_choices_explicit_error_term_nl( state.tracing.trace_df( nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils") ) - nest_utils_for_choice = add_ev1_random(state, nested_utilities) + nest_utils_for_choice = add_ev1_random( + state, nested_utilities, alts_context, alt_nrs_df + ) all_alternatives = set(nest.name for nest in each_nest(nest_spec, type="leaf")) logit_nest_groups = group_nest_names_by_level(nest_spec) @@ -450,7 +526,13 @@ def make_choices_explicit_error_term_nl( def make_choices_explicit_error_term_mnl( - state, utilities, trace_label, trace_choosers=None, allow_bad_utils=False + state, + utilities, + trace_label, + trace_choosers=None, + allow_bad_utils=False, + alts_context: AltsContext | None = None, + alt_nrs_df: pd.DataFrame | None = None, ) -> pd.Series: """ Make EET choices for a multinomial logit model by adding EV1 errors. @@ -472,7 +554,7 @@ def make_choices_explicit_error_term_mnl( state.tracing.trace_df( utilities, tracing.extend_trace_label(trace_label, "utilities") ) - utilities_incl_unobs = add_ev1_random(state, utilities) + utilities_incl_unobs = add_ev1_random(state, utilities, alts_context, alt_nrs_df) if trace_label: state.tracing.trace_df( utilities_incl_unobs, @@ -502,11 +584,19 @@ def make_choices_explicit_error_term( trace_label=None, trace_choosers=None, allow_bad_utils=False, + alts_context: AltsContext | None = None, + alt_nrs_df: pd.DataFrame | None = None, ) -> pd.Series: trace_label = tracing.extend_trace_label(trace_label, "make_choices_eet") if nest_spec is None: choices = make_choices_explicit_error_term_mnl( - state, utilities, trace_label, trace_choosers, allow_bad_utils + state, + utilities, + trace_label, + trace_choosers, + allow_bad_utils, + alts_context, + alt_nrs_df, ) else: choices = make_choices_explicit_error_term_nl( @@ -517,6 +607,8 @@ def make_choices_explicit_error_term( trace_label, trace_choosers, allow_bad_utils, + alts_context, + alt_nrs_df, ) return choices @@ -529,6 +621,8 @@ def make_choices_utility_based( trace_label: str = None, trace_choosers=None, allow_bad_utils=False, + alts_context: AltsContext | None = None, + alt_nrs_df: pd.DataFrame | None = None, ) -> tuple[pd.Series, pd.Series]: trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") @@ -542,6 +636,8 @@ def make_choices_utility_based( trace_label, trace_choosers=trace_choosers, allow_bad_utils=allow_bad_utils, + alts_context=alts_context, + alt_nrs_df=alt_nrs_df, ) # EET does not expose per-row random draws; return zeros for compatibility. rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index e381cd85ae..569e491539 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -3,14 +3,16 @@ from __future__ import annotations import os.path +import re import numpy as np import pandas as pd import pandas.testing as pdt import pytest -from activitysim.core import logit, simulate, workflow +from activitysim.core import logit, random, simulate, workflow from activitysim.core.exceptions import InvalidTravelError +from activitysim.core.logit import AltsContext, add_ev1_random from activitysim.core.simulate import eval_variables @@ -442,7 +444,7 @@ def test_choose_from_tree_raises_on_missing_leaf(): # EET Choice Behavior Tests # def test_make_choices_eet_mnl(monkeypatch): - def fake_add_ev1_random(_state, _df): + def fake_add_ev1_random(_state, _df, alt_info=None, alt_nrs_df=None): return pd.DataFrame( [[1.0, 3.0], [4.0, 2.0]], index=[100, 101], @@ -461,7 +463,7 @@ def fake_add_ev1_random(_state, _df): def test_make_choices_eet_nl(monkeypatch): - def fake_add_ev1_random(_state, _df): + def fake_add_ev1_random(_state, _df, alt_info=None, alt_nrs_df=None): return pd.DataFrame( [[5.0, 1.0, 4.0, 2.0], [3.0, 4.0, 1.0, 2.0]], index=[10, 11], @@ -496,7 +498,7 @@ def fake_add_ev1_random(_state, _df): def test_make_choices_utility_based_sets_zero_rands(monkeypatch): - def fake_add_ev1_random(_state, df): + def fake_add_ev1_random(_state, df, alt_info=None, alt_nrs_df=None): return pd.DataFrame( [[2.0, 1.0], [0.5, 2.5]], index=df.index, @@ -714,3 +716,72 @@ def test_interaction_dataset_sampled(interaction_choosers, interaction_alts): interacted, expected = interacted.align(expected, axis=1) pdt.assert_frame_equal(interacted, expected) + + +def reset_step(state, name="test_step"): + state.get_rn_generator().end_step(name) + state.get_rn_generator().begin_step(name) + + +def test_make_choices_utility_based_sampled_alts(): + """Test the situation of making choices from a sampled choice set""" + # TODO should these tests go in test_random? + state = workflow.State().default_settings() + # Make explicit that there's two indexing schemes - the raw alts, and the 0 based internals + utils_project_raw = pd.DataFrame( + {"a": 10.582999, "b": 10.680792, "c": 10.710443}, + index=pd.Index([0], name="person_id"), + ) + # zero based indexes + utils_project = utils_project_raw.rename(columns={"a": 0, "b": 1, "c": 2}) + utils_base = utils_project_raw[["a", "c"]].rename(columns={"a": 0, "c": 1}) + + assert utils_project.index.name == "person_id" + state.get_rn_generator().add_channel("persons", utils_project) + state.get_rn_generator().begin_step("test_step") + # mock base case, where alt 1 is omitted (it was improved in the project) + # this situation is quite common with poisson sampling with a variable choice set size, + # but it can also happen in with-replacement EET sampling e.g. if alt 2 had a pick_count of 2 in the base case. + # In principle, it can also be problematic for non-sampled choices where there is a base project difference in the + # availability of alternatives .e.g a new mode was introduced in the project case + + utils_project_with_rands = add_ev1_random(state, utils_project) + rands_project = utils_project_with_rands - utils_project + reset_step(state) + utils_base_with_rands = add_ev1_random(state, utils_base) + rands_base = utils_base_with_rands - utils_base + rands_base_labeled = rands_base.rename(columns={0: "a", 1: "c"}) + rands_project_labeled = rands_project.rename(columns={0: "a", 1: "b", 2: "c"}) + with pytest.raises( + AssertionError, match=re.escape('(column name="c") are different') + ): + # TODO this should pass + pdt.assert_frame_equal( + rands_base_labeled, rands_project_labeled.loc[:, rands_base_labeled.columns] + ) + # document incorrect invariant - first two columns have the same random numbers: + pdt.assert_frame_equal(rands_base, rands_project.iloc[:, :2]) + + # revised approach + reset_step(state) + alt_nrs_df = pd.DataFrame({0: 0, 1: 1, 2: 2}, index=utils_project_raw.index) + alt_info = AltsContext.from_num_alts(3, zero_based=True) + utils_project_with_rands = add_ev1_random( + state, utils_project, alt_info=alt_info, alt_nrs_df=alt_nrs_df + ) + rands_project = utils_project_with_rands - utils_project + reset_step(state) + + # alt "b" is missing from the sampled choice set, alt_nrs_df is set to reflect that + alt_nrs_df = pd.DataFrame({0: 0, 1: 2}, index=utils_project_raw.index) + utils_base_with_rands = add_ev1_random( + state, utils_base, alt_info=alt_info, alt_nrs_df=alt_nrs_df + ) + rands_base = utils_base_with_rands - utils_base + rands_base_labeled = rands_base.rename(columns={0: "a", 1: "c"}) + rands_project_labeled = rands_project.rename(columns={0: "a", 1: "b", 2: "c"}) + + # Corrected invariant holds true + pdt.assert_frame_equal( + rands_base_labeled, rands_project_labeled.loc[:, rands_base_labeled.columns] + ) From 89dbe5c1d83dc51e316f9974695d495b737be0bc Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Thu, 9 Apr 2026 17:47:30 +1000 Subject: [PATCH 198/298] Tidy, complete implementation of alts_context, add tests, rebuild regress for multi zone tests --- activitysim/abm/models/location_choice.py | 1 - .../abm/models/util/tour_destination.py | 5 +- .../models/util/vectorize_tour_scheduling.py | 3 + .../core/interaction_sample_simulate.py | 30 +- activitysim/core/logit.py | 57 ++- .../test/test_interaction_sample_simulate.py | 106 +++++ activitysim/core/test/test_logit.py | 51 ++ .../test/reference_pipeline_2_zone_eet.zip | Bin 263089 -> 285194 bytes .../test/regress/final_eet_tours_2_zone.csv | 186 ++++---- .../test/regress/final_eet_trips_2_zone.csv | 441 +++++++++--------- 10 files changed, 521 insertions(+), 359 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 1d7c76851a..1ef7af5f06 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -816,7 +816,6 @@ def run_location_choice( trace_label=tracing.extend_trace_label( trace_label, "sample.%s" % segment_name ), - alts_context=alts_context, ) # - location_logsums diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index d99803bd7d..00ce0d1b83 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -12,15 +12,16 @@ from activitysim.core import ( config, estimation, + expressions, los, simulate, tracing, workflow, - expressions, ) from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate +from activitysim.core.logit import AltsContext from activitysim.core.util import reindex logger = logging.getLogger(__name__) @@ -873,6 +874,7 @@ def run_destination_simulate( state.tracing.dump_df(DUMP, choosers, trace_label, "choosers") log_alt_losers = state.settings.log_alt_losers + alts_context = AltsContext.from_series(destination_size_terms.index) choices = interaction_sample_simulate( state, @@ -891,6 +893,7 @@ def run_destination_simulate( estimator=estimator, skip_choice=skip_choice, compute_settings=model_settings.compute_settings, + alts_context=alts_context, ) if not want_logsums: diff --git a/activitysim/abm/models/util/vectorize_tour_scheduling.py b/activitysim/abm/models/util/vectorize_tour_scheduling.py index c199ef40da..14772bde66 100644 --- a/activitysim/abm/models/util/vectorize_tour_scheduling.py +++ b/activitysim/abm/models/util/vectorize_tour_scheduling.py @@ -17,6 +17,7 @@ from activitysim.core.configuration.base import ComputeSettings, PreprocessorSettings from activitysim.core.configuration.logit import LogitComponentSettings from activitysim.core.interaction_sample_simulate import interaction_sample_simulate +from activitysim.core.logit import AltsContext from activitysim.core.util import reindex logger = logging.getLogger(__name__) @@ -849,6 +850,7 @@ def _schedule_tours( estimator.write_interaction_sample_alternatives(alt_tdd) log_alt_losers = state.settings.log_alt_losers + alts_context = AltsContext.from_series(alt_tdd[choice_column]) choices = interaction_sample_simulate( state, @@ -862,6 +864,7 @@ def _schedule_tours( trace_label=tour_trace_label, estimator=estimator, compute_settings=compute_settings, + alts_context=alts_context, ) chunk_sizer.log_df(tour_trace_label, "choices", choices) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 115f4f7084..81897899a0 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -222,9 +222,6 @@ def _interaction_sample_simulate( ) chunk_sizer.log_df(trace_label, "interaction_utilities", interaction_utilities) - del interaction_df # TODO-TS: this was deleted in M.Richards commit, relevant to altscontext or other? - chunk_sizer.log_df(trace_label, "interaction_df", None) - if have_trace_targets: state.tracing.trace_interaction_eval_results( trace_eval_results, @@ -268,10 +265,11 @@ def _interaction_sample_simulate( padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999) padded_alt_nrs = np.insert(interaction_df[choice_column], inserts, -999) chunk_sizer.log_df(trace_label, "padded_utilities", padded_utilities) - del inserts - del interaction_utilities - chunk_sizer.log_df(trace_label, "interaction_utilities", None) + del interaction_df # TODO-TS: this was deleted in M.Richards commit, relevant to altscontext or other? + chunk_sizer.log_df(trace_label, "interaction_df", None) + + del inserts # reshape to array with one row per chooser, one column per alternative padded_utilities = padded_utilities.reshape(-1, max_sample_count) @@ -513,12 +511,12 @@ def interaction_sample_simulate( explicit_chunk_size : float, optional If > 0, specifies the chunk size to use when chunking the interaction simulation. If < 1, specifies the fraction of the total number of choosers. - alts_context: int, optional - The number of alternatives available in the choice set in the absense of sampling. + alts_context: AltsContext, optional + Representation of the full alternatives domain (min and max alternative id) + in the absence of sampling. This is used with EET simulation to ensure consistent random numbers across the whole alternative set ( as the sampled set may change between base and project). When not provided, - the fallback approach is used which may result in frozen error terms being applied to the wrong alternatives - if the choice set changes. + EET with integer-coded choice ids will raise an error. Returns ------- @@ -540,6 +538,18 @@ def interaction_sample_simulate( trace_label = tracing.extend_trace_label(trace_label, "interaction_sample_simulate") chunk_tag = chunk_tag or trace_label + if state.settings.use_explicit_error_terms: + choice_ids_are_int = pd.api.types.is_integer_dtype(alternatives[choice_column]) + if alts_context is None and choice_ids_are_int: + raise ValueError( + "alts_context is required for interaction_sample_simulate when " + "use_explicit_error_terms is True and choice_column is integer-coded" + ) + if alts_context is not None and not choice_ids_are_int: + raise ValueError( + "alts_context can only be used with integer-coded choice_column values" + ) + result_list = [] for ( i, diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 625830bafe..2670d044fa 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -5,6 +5,7 @@ import logging import warnings from dataclasses import dataclass +from typing import Union import numpy as np import pandas as pd @@ -30,8 +31,6 @@ PROB_MIN = 0.0 PROB_MAX = 1.0 -FREEZE_RANDOM_NUMBERS_FOR_DENSE_ALTERNATIVE_SET = True - @dataclass class AltsContext: @@ -401,37 +400,35 @@ def add_ev1_random( nest_utils_for_choice = df.copy() assert (alt_info is None) == ( alt_nrs_df is None - ), "n_zones and alt_nrs_df must both be provided or omitted together" - - if alt_nrs_df is not None and FREEZE_RANDOM_NUMBERS_FOR_DENSE_ALTERNATIVE_SET: - assert alt_info is not None # narrowing for mypy - - idx_array = alt_nrs_df.values - mask = idx_array == -999 - safe_idx = np.where( - mask, 1, idx_array - ) # replace -999 with a temp value inbounds - # generate random number for all alts - this is wasteful, but ensures that the same zone - # gets the same random number if the sampled choice set changes between base and project - # (alternatively, one could seed a channel for (persons x zones) and use the zone seed to ensure consistency. - # Trade off is needing to seed (persons x zones) rows and multiindex channels to - # avoid extra random numbers generated here. Quick benchmark suggests seeding per row is likely slower - rands_dense = state.get_rn_generator().gumbel_for_df( - nest_utils_for_choice, n=alt_info.n_alts_to_cover_max_id - ) - # generate n=alt_info.max_alt_id+1 rather than n_alts so that indexing works - # (this is drawing a random number for a redundant zeroth zone in 1 based zoning systems) - # TODO deal with non 0->n-1 indexed land use more efficiently? ideally do where alt_nrs_df is constructed, - # not on the fly here. Potentially via state.get_injectable('network_los').get_skim_dict('taz').zone_ids - rands = np.take_along_axis(rands_dense, safe_idx, axis=1) - rands[ - mask - ] = 0 # zero out the masked zones so they don't have the util adjustment of alt 0 - else: - # old behaviour, to remove + ), "alt_info and alt_nrs_df must both be provided or omitted together" + + if alt_info is None: + # Fallback behaviour for models where alt_info/alt_nrs_df are not provided (e.g. non-integer alts) rands = state.get_rn_generator().gumbel_for_df( nest_utils_for_choice, n=nest_utils_for_choice.shape[1] ) + nest_utils_for_choice += rands + return nest_utils_for_choice + + idx_array = alt_nrs_df.values + mask = idx_array == -999 + safe_idx = np.where(mask, 1, idx_array) # replace -999 with a temp value inbounds + # generate random number for all alts - this is wasteful, but ensures that the same zone + # gets the same random number if the sampled choice set changes between base and project + # (alternatively, one could seed a channel for (persons x zones) and use the zone seed to ensure consistency. + # Trade off is needing to seed (persons x zones) rows and multiindex channels to + # avoid extra random numbers generated here. Quick benchmark suggests seeding per row is likely slower + rands_dense = state.get_rn_generator().gumbel_for_df( + nest_utils_for_choice, n=alt_info.n_alts_to_cover_max_id + ) + # generate n=alt_info.max_alt_id+1 rather than n_alts so that indexing works + # (this is drawing a random number for a redundant zeroth zone in 1 based zoning systems) + # TODO deal with non 0->n-1 indexed land use more efficiently? ideally do where alt_nrs_df is constructed, + # not on the fly here. Potentially via state.get_injectable('network_los').get_skim_dict('taz').zone_ids + rands = np.take_along_axis(rands_dense, safe_idx, axis=1) + rands[ + mask + ] = 0 # zero out the masked zones so they don't have the util adjustment of alt 0 nest_utils_for_choice += rands return nest_utils_for_choice diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 1be7954172..6ab79a532d 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -1,11 +1,14 @@ # ActivitySim # See full license in LICENSE.txt. +from __future__ import annotations + import numpy as np import pandas as pd import pytest from activitysim.core import interaction_sample_simulate, workflow +from activitysim.core.logit import AltsContext @pytest.fixture @@ -75,6 +78,7 @@ def test_interaction_sample_simulate_parity(state): alternatives, spec, choice_column="tdd", + alts_context=AltsContext.from_num_alts(num_alts_per_chooser, zero_based=True), ) assert len(choices_mnl) == num_choosers @@ -140,6 +144,7 @@ def test_interaction_sample_simulate_eet_unavailable_alternatives(state): alternatives, spec, choice_column="tdd", + alts_context=AltsContext.from_num_alts(num_alts_per_chooser, zero_based=True), ) assert len(choices_eet) == num_choosers @@ -149,3 +154,104 @@ def test_interaction_sample_simulate_eet_unavailable_alternatives(state): # Choices should only be 0 or 1 assert choices_eet.isin([0, 1]).all() assert not choices_eet.isin([2, 3, 4]).any() + + +def test_interaction_sample_simulate_passes_alts_context_and_alt_nrs_df( + state, monkeypatch +): + state.settings.use_explicit_error_terms = True + + choosers = pd.DataFrame( + {"chooser_attr": [1.0, 1.0]}, + index=pd.Index([100, 101], name="person_id"), + ) + alternatives = pd.DataFrame( + { + "alt_attr": [1.0, 0.5, 0.8, 1.2], + "tdd": [0, 2, 0, 2], + }, + index=pd.Index([100, 100, 101, 101], name="person_id"), + ) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + captured = {} + + def fake_make_choices_utility_based( + _state, + utilities, + name_mapping=None, + nest_spec=None, + trace_label=None, + trace_choosers=None, + allow_bad_utils=False, + alts_context=None, + alt_nrs_df=None, + ): + captured["alts_context"] = alts_context + captured["alt_nrs_df"] = alt_nrs_df.copy() if alt_nrs_df is not None else None + return pd.Series([0, 0], index=utilities.index), pd.Series( + np.zeros(len(utilities.index)), index=utilities.index + ) + + monkeypatch.setattr( + interaction_sample_simulate.logit, + "make_choices_utility_based", + fake_make_choices_utility_based, + ) + + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_alts_context_forwarding") + + ctx = AltsContext.from_num_alts(3, zero_based=True) + choices = interaction_sample_simulate.interaction_sample_simulate( + state, + choosers, + alternatives, + spec, + choice_column="tdd", + alts_context=ctx, + ) + + assert len(choices) == len(choosers) + assert captured["alts_context"] == ctx + assert captured["alt_nrs_df"] is not None + expected_alt_nrs = pd.DataFrame( + [[0, 2], [0, 2]], + index=choosers.index, + ) + pd.testing.assert_frame_equal(captured["alt_nrs_df"], expected_alt_nrs) + + +def test_interaction_sample_simulate_requires_alts_context_for_eet_integer_choices( + state, +): + state.settings.use_explicit_error_terms = True + + choosers = pd.DataFrame( + {"chooser_attr": [1.0, 1.0]}, + index=pd.Index([200, 201], name="person_id"), + ) + alternatives = pd.DataFrame( + { + "alt_attr": [1.0, 0.5, 0.8, 1.2], + "tdd": [0, 2, 0, 2], + }, + index=pd.Index([200, 200, 201, 201], name="person_id"), + ) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + with pytest.raises(ValueError, match="alts_context is required"): + interaction_sample_simulate.interaction_sample_simulate( + state, + choosers, + alternatives, + spec, + choice_column="tdd", + ) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 569e491539..b5111e352a 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -384,6 +384,30 @@ def get_rn_generator(): ) +def test_add_ev1_random_requires_paired_alt_context_args(): + class DummyRNG: + def gumbel_for_df(self, df, n): + return np.zeros((len(df), n)) + + class DummyState: + @staticmethod + def get_rn_generator(): + return DummyRNG() + + utilities = pd.DataFrame([[1.0, 2.0]], index=[1], columns=["a", "b"]) + + with pytest.raises( + AssertionError, + match="alt_info and alt_nrs_df must both be provided or omitted together", + ): + logit.add_ev1_random( + DummyState(), + utilities, + alt_info=AltsContext.from_num_alts(2), + alt_nrs_df=None, + ) + + # # Nested Logit Structure Tests # @@ -785,3 +809,30 @@ def test_make_choices_utility_based_sampled_alts(): pdt.assert_frame_equal( rands_base_labeled, rands_project_labeled.loc[:, rands_base_labeled.columns] ) + + +def test_alts_context_from_series_and_properties(): + ctx = AltsContext.from_series(pd.Index([3, 5, 9, 4])) + + assert ctx.min_alt_id == 3 + assert ctx.max_alt_id == 9 + assert ctx.n_alts_to_cover_max_id == 10 + assert ctx.n_rands_to_sample == 10 + + +@pytest.mark.parametrize( + "num_alts,zero_based,expected_min,expected_max,expected_n_cover", + [ + (5, True, 0, 4, 5), + (5, False, 1, 5, 6), + ], +) +def test_alts_context_from_num_alts( + num_alts, zero_based, expected_min, expected_max, expected_n_cover +): + ctx = AltsContext.from_num_alts(num_alts=num_alts, zero_based=zero_based) + + assert ctx.min_alt_id == expected_min + assert ctx.max_alt_id == expected_max + assert ctx.n_alts_to_cover_max_id == expected_n_cover + assert ctx.n_rands_to_sample == expected_n_cover diff --git a/activitysim/examples/placeholder_multiple_zone/test/reference_pipeline_2_zone_eet.zip b/activitysim/examples/placeholder_multiple_zone/test/reference_pipeline_2_zone_eet.zip index 3e3b2419b009d3d4fc6b9e3282a10c0b17b5fa8a..8e3abdfff6beb2d8c8e9fa0d20aad9280e7345ab 100644 GIT binary patch literal 285194 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

SgtnX+F#R*VryE*6 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 literal 263089 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 z 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℞T{Ec!f%S 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�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 Date: Fri, 10 Apr 2026 09:46:03 +1000 Subject: [PATCH 199/298] Commenting/fix alts context constructions, update semcog test --- activitysim/abm/models/location_choice.py | 1 + .../abm/models/parking_location_choice.py | 6 +- activitysim/abm/models/trip_destination.py | 1 + .../abm/models/util/tour_destination.py | 5 +- .../models/util/vectorize_tour_scheduling.py | 4 +- .../test/regress/final_eet_trips.csv | 192 +++++----- .../test/regress/final_trips.csv | 338 +++++++++--------- 7 files changed, 277 insertions(+), 270 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 1ef7af5f06..6fe242a6e7 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -796,6 +796,7 @@ def run_location_choice( continue # using land use rather than size terms in case something goes 0 base -> nonzero project, double # check if that would be in dest_size_terms as a zero + # use full index (including zero-size zones) to ensure stable random results alts_context = AltsContext.from_series( dest_size_terms.index ) # index zone_id, not ALT_DEST_COL_NAME diff --git a/activitysim/abm/models/parking_location_choice.py b/activitysim/abm/models/parking_location_choice.py index d4e7cd246c..07bc5bea0e 100644 --- a/activitysim/abm/models/parking_location_choice.py +++ b/activitysim/abm/models/parking_location_choice.py @@ -214,9 +214,9 @@ def choose_parking_location( ) destination_sample.index = np.repeat(trips.index.values, len(alternatives)) destination_sample.index.name = trips.index.name - # using destination_sample would also be right because destination_sample isn't a sample here, - # but that could change - alts_context = AltsContext.from_series(alternatives[alt_dest_col_name]) + # use full land_use index to ensure AltsContext spans full range of potential zones + land_use = state.get_dataframe("land_use") + alts_context = AltsContext.from_series(land_use.index) destinations = parking_destination_simulate( state, diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 59b7f22392..9888d0606a 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -1132,6 +1132,7 @@ def choose_trip_destination( alt_dest_col_name = model_settings.ALT_DEST_COL_NAME alts = alternatives.index assert alts.name == alt_dest_col_name + # use full index (including zero-size zones) to ensure stable random results alts_context = AltsContext.from_series(alts) destinations = trip_destination_simulate( state, diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 00ce0d1b83..84f51a0982 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -874,7 +874,10 @@ def run_destination_simulate( state.tracing.dump_df(DUMP, choosers, trace_label, "choosers") log_alt_losers = state.settings.log_alt_losers - alts_context = AltsContext.from_series(destination_size_terms.index) + # use full land_use index to ensure AltsContext spans full range of potential destinations + # (maintains stable random number generation even if zones flip zero/non-zero size) + land_use = state.get_dataframe("land_use") + alts_context = AltsContext.from_series(land_use.index) choices = interaction_sample_simulate( state, diff --git a/activitysim/abm/models/util/vectorize_tour_scheduling.py b/activitysim/abm/models/util/vectorize_tour_scheduling.py index 14772bde66..0666bf2c8c 100644 --- a/activitysim/abm/models/util/vectorize_tour_scheduling.py +++ b/activitysim/abm/models/util/vectorize_tour_scheduling.py @@ -850,7 +850,9 @@ def _schedule_tours( estimator.write_interaction_sample_alternatives(alt_tdd) log_alt_losers = state.settings.log_alt_losers - alts_context = AltsContext.from_series(alt_tdd[choice_column]) + # use full TDD alternatives index to ensure AltsContext spans full range of potential slots + tdd_alts = state.get_injectable("tdd_alts") + alts_context = AltsContext.from_series(tdd_alts.index) choices = interaction_sample_simulate( state, diff --git a/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv index cc98fe5d69..9826e1d681 100644 --- a/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv +++ b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv @@ -1,116 +1,116 @@ "person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","original_school_zone_id","parked_at_university","depart","tour_includes_parking","trip_id_pre_parking","trip_mode","mode_choice_logsum","trip_id" -2632461,1066212,"eatout",1,true,1,22688,22687,107930907,"eatout",,,false,24,0,863447257,"WALK",0.3324082937283966,1726894513 -2632461,1066212,"eatout",1,false,1,22687,22688,107930907,"home",,,false,32,0,863447261,"WALK",0.3324082937283966,1726894521 -2632461,1066212,"social",1,true,1,22676,22687,107930937,"social",,,false,38,0,863447497,"WALK",-0.372506247777352,1726894993 -2632461,1066212,"social",1,false,1,22687,22676,107930937,"home",,,false,38,0,863447501,"WALK",-0.372506247777352,1726895001 -2632461,1066212,"work",1,true,1,22770,22687,107930940,"work",,,false,11,0,863447521,"DRIVEALONE",-0.9006268476080008,1726895041 -2632461,1066212,"work",1,false,1,22687,22770,107930940,"home",,,false,23,0,863447525,"DRIVEALONE",-0.5528040173584109,1726895049 -2632746,1066390,"school",1,true,2,22684,22688,107942617,"shopping",10.301822957849977,,false,13,0,863540937,"SHARED3",0.08788155056513884,1727081873 -2632746,1066390,"school",2,true,2,22716,22684,107942617,"school",,,false,13,0,863540938,"SHARED3",0.21128282107010274,1727081874 -2632746,1066390,"school",1,false,1,22688,22716,107942617,"home",,,false,20,0,863540941,"SHARED3",-0.12094657865851986,1727081881 -2632746,1066390,"work",1,true,2,22798,22688,107942625,"parking",,,false,21,1,863541001,"DRIVEALONE",-1.0935617741756212,1727082001 -2632746,1066390,"work",2,true,2,22798,22798,107942625,"work",,,true,21,1,863541001,"WALK",2.688813549798029,1727082002 -2632746,1066390,"work",1,false,2,22798,22798,107942625,"parking",,,true,26,1,863541005,"WALK",2.6888134385754383,1727082009 -2632746,1066390,"work",2,false,2,22688,22798,107942625,"home",,,false,26,1,863541005,"DRIVEALONE",-1.285961232813202,1727082010 -2643231,1070862,"work",1,true,2,22767,22701,108372510,"parking",,,false,12,1,866980081,"DRIVEALONE",-2.254013060998411,1733960161 -2643231,1070862,"work",2,true,2,22767,22767,108372510,"work",,,true,12,1,866980081,"WALK",3.750337710238621,1733960162 -2643231,1070862,"work",1,false,2,22767,22767,108372510,"parking",,,true,27,1,866980085,"WALK",3.75033686292241,1733960169 -2643231,1070862,"work",2,false,2,22701,22767,108372510,"home",,,false,27,1,866980085,"DRIVEALONE",-1.0195938099395256,1733960170 -2851663,1151807,"work",1,true,2,22808,22768,116918222,"parking",,,false,8,1,935345777,"WALK",0.5794744566652396,1870691553 -2851663,1151807,"work",2,true,2,22808,22808,116918222,"work",,,true,8,1,935345777,"WALK",3.9202266680627016,1870691554 -2851663,1151807,"work",1,false,2,22808,22808,116918222,"parking",,,true,23,1,935345781,"WALK",3.9202264187221654,1870691561 -2851663,1151807,"work",2,false,2,22768,22808,116918222,"home",,,false,23,1,935345781,"WALK",0.5811901896672964,1870691562 -2851664,1151807,"atwork",1,true,1,22795,22795,116918247,"atwork",,,false,8,0,935345977,"WALK",0,1870691953 -2851664,1151807,"atwork",1,false,2,22807,22795,116918247,"eatout",11.697803529864785,,false,9,0,935345981,"WALK",-0.6403075075080801,1870691961 -2851664,1151807,"atwork",2,false,2,22795,22807,116918247,"work",,,false,9,0,935345982,"WALK",1.9742275881306344,1870691962 -2851664,1151807,"work",1,true,2,22795,22768,116918263,"parking",,,false,8,1,935346105,"DRIVEALONE",-0.1700734379058779,1870692209 -2851664,1151807,"work",2,true,2,22795,22795,116918263,"work",,,true,8,1,935346105,"WALK",2.014596847010505,1870692210 -2851664,1151807,"work",1,false,2,22795,22795,116918263,"parking",,,true,9,1,935346109,"WALK",2.014596847010505,1870692217 -2851664,1151807,"work",2,false,2,22768,22795,116918263,"home",,,false,9,1,935346109,"DRIVEALONE",-0.17669442402412502,1870692218 -2851664,1151807,"work",1,true,2,22795,22768,116918264,"parking",,,false,10,1,935346113,"SHARED2",0.18223026147932736,1870692225 -2851664,1151807,"work",2,true,2,22795,22795,116918264,"work",,,true,10,1,935346113,"WALK",3.0721786555313417,1870692226 -2851664,1151807,"work",1,false,3,22767,22795,116918264,"eatout",13.361606283751318,,true,12,1,935346117,"WALK",2.1699105206573512,1870692233 -2851664,1151807,"work",2,false,3,22795,22767,116918264,"parking",,,true,12,1,935346118,"WALK",3.660264542941122,1870692234 -2851664,1151807,"work",3,false,3,22768,22795,116918264,"home",,,false,12,1,935346118,"DRIVEALONE",0.19501777547255042,1870692235 +2632461,1066212,"eatout",1,true,1,22677,22687,107930907,"eatout",,,false,31,0,863447257,"WALK",1.2747067090732285,1726894513 +2632461,1066212,"eatout",1,false,1,22687,22677,107930907,"home",,,false,36,0,863447261,"WALK",1.2528040978215553,1726894521 +2632461,1066212,"social",1,true,1,22688,22687,107930937,"social",,,false,27,0,863447497,"WALK",0.22160552915226453,1726894993 +2632461,1066212,"social",1,false,1,22687,22688,107930937,"home",,,false,30,0,863447501,"WALK",0.22160552915226453,1726895001 +2632461,1066212,"work",1,true,1,22659,22687,107930940,"work",,,false,11,0,863447521,"DRIVEALONE",-0.2764049012484984,1726895041 +2632461,1066212,"work",1,false,1,22687,22659,107930940,"home",,,false,23,0,863447525,"DRIVEALONE",-0.26321709957638273,1726895049 +2632746,1066390,"school",1,true,2,22689,22688,107942617,"shopping",10.30120931810444,,false,10,0,863540937,"WALK",0.41173295672864374,1727081873 +2632746,1066390,"school",2,true,2,22716,22689,107942617,"school",,,false,10,0,863540938,"SHARED3",-0.12093048344357989,1727081874 +2632746,1066390,"school",1,false,1,22688,22716,107942617,"home",,,false,21,0,863540941,"SHARED3",-0.12094657865851986,1727081881 +2632746,1066390,"work",1,true,1,22688,22688,107942625,"work",,,false,21,0,863541001,"WALK",0.22160552915226453,1727082001 +2632746,1066390,"work",1,false,1,22688,22688,107942625,"home",,,false,26,0,863541005,"WALK",0.22160552915226453,1727082009 +2643231,1070862,"work",1,true,2,22795,22701,108372510,"parking",,,false,12,1,866980081,"DRIVEALONE",-0.8231318335063801,1733960161 +2643231,1070862,"work",2,true,2,22795,22795,108372510,"work",,,true,12,1,866980081,"WALK",2.0056875567057055,1733960162 +2643231,1070862,"work",1,false,2,22795,22795,108372510,"parking",,,true,27,1,866980085,"WALK",2.005696956562539,1733960169 +2643231,1070862,"work",2,false,2,22701,22795,108372510,"home",,,false,27,1,866980085,"DRIVEALONE",-0.856858292126302,1733960170 +2851663,1151807,"work",1,true,1,22743,22768,116918222,"work",,,false,8,0,935345777,"DRIVEALONE",0.32319821567472595,1870691553 +2851663,1151807,"work",1,false,1,22768,22743,116918222,"home",,,false,23,0,935345781,"SHARED2",0.31914343414862156,1870691561 +2851664,1151807,"atwork",1,true,1,22755,22783,116918247,"atwork",,,false,9,0,935345977,"WALK",-0.4285387553231248,1870691953 +2851664,1151807,"atwork",1,false,2,22767,22755,116918247,"eatout",13.620552291309844,,false,9,0,935345981,"WALK",-0.3360993920023427,1870691961 +2851664,1151807,"atwork",2,false,2,22783,22767,116918247,"work",,,false,9,0,935345982,"WALK",2.0196212107066467,1870691962 +2851664,1151807,"work",1,true,2,22783,22768,116918263,"parking",,,false,8,1,935346105,"DRIVEALONE",-0.101763675139766,1870692209 +2851664,1151807,"work",2,true,2,22783,22783,116918263,"work",,,true,8,1,935346105,"WALK",2.394893602362858,1870692210 +2851664,1151807,"work",1,false,2,22783,22783,116918263,"parking",,,true,9,1,935346109,"WALK",2.394893602362858,1870692217 +2851664,1151807,"work",2,false,2,22768,22783,116918263,"home",,,false,9,1,935346109,"DRIVEALONE",-0.14128432072913716,1870692218 +2851664,1151807,"work",1,true,2,22783,22768,116918264,"parking",,,false,20,1,935346113,"SHARED2",0.3036422340987692,1870692225 +2851664,1151807,"work",2,true,2,22783,22783,116918264,"work",,,true,20,1,935346113,"WALK",3.455230939742648,1870692226 +2851664,1151807,"work",1,false,3,22783,22783,116918264,"parking",,,true,22,1,935346117,"WALK",3.455230939742648,1870692233 +2851664,1151807,"work",2,false,3,22743,22783,116918264,"eatout",13.694190728203349,,false,22,1,935346117,"SHARED2",0.12796410019608423,1870692234 +2851664,1151807,"work",3,false,3,22768,22743,116918264,"home",,,false,22,1,935346118,"DRIVEALONE",0.28506746953977524,1870692235 2851665,1151807,"school",1,true,1,22738,22768,116918296,"school",,,false,9,0,935346369,"WALK",-0.3380929737459932,1870692737 2851665,1151807,"school",1,false,1,22768,22738,116918296,"home",,,false,25,0,935346373,"WALK",-0.3380929737459932,1870692745 -2851666,1151807,"school",1,true,1,22738,22768,116918337,"school",,,false,9,0,935346697,"WALK",-0.23394837977299351,1870693393 -2851666,1151807,"school",1,false,2,22768,22738,116918337,"eatout",12.976839556161908,,false,26,0,935346701,"WALK",-0.30724534671072457,1870693401 -2851666,1151807,"school",2,false,2,22768,22768,116918337,"home",,,false,26,0,935346702,"WALK",1.4569271228419698,1870693402 -2853258,1152693,"work",1,true,1,22808,22767,116983617,"work",,,false,20,0,935868937,"WALK",4.2361228435911125,1871737873 -2853258,1152693,"work",1,false,1,22767,22808,116983617,"home",,,false,42,0,935868941,"WALK",4.2355632459345705,1871737881 -2864033,1157863,"work",1,true,1,22766,22818,117425392,"work",,,false,22,0,939403137,"WALK",-0.5747999444276104,1878806273 -2864033,1157863,"work",1,false,3,22801,22766,117425392,"othmaint",11.425225674825322,,false,43,0,939403141,"WALK",-0.7024510798800492,1878806281 -2864033,1157863,"work",2,false,3,22802,22801,117425392,"othmaint",13.28624241505493,,false,43,0,939403142,"WALK",0.28664476657433274,1878806282 -2864033,1157863,"work",3,false,3,22818,22802,117425392,"home",,,false,44,0,939403143,"WALK",1.5286197350024198,1878806283 -2867650,1159450,"work",1,true,1,22740,22791,117573689,"work",,,false,5,0,940589513,"DRIVEALONE",-0.670801522478196,1881179025 -2867650,1159450,"work",1,false,1,22791,22740,117573689,"home",,,false,28,0,940589517,"SHARED2",0.03856943979091073,1881179033 -2867652,1159450,"school",1,true,1,22798,22791,117573763,"school",,,false,11,0,940590105,"WALK",-0.14197028764914804,1881180209 -2867652,1159450,"school",1,false,2,22807,22798,117573763,"escort",12.102989575726829,,false,26,0,940590109,"WALK",0.3099529390965043,1881180217 -2867652,1159450,"school",2,false,2,22791,22807,117573763,"home",,,false,27,0,940590110,"WALK",1.1921458680932129,1881180218 -2867653,1159450,"school",1,true,1,22716,22791,117573804,"school",,,false,9,0,940590433,"SHARED3",-0.7165798080815713,1881180865 -2867653,1159450,"school",1,false,1,22791,22716,117573804,"home",,,false,23,0,940590437,"SHARED3",-0.7056869394647015,1881180873 -2869308,1160345,"escort",1,true,4,22806,22788,117641637,"parking",,,false,37,1,941133097,"SHARED2",-0.35468797889700127,1882266193 -2869308,1160345,"escort",2,true,4,22761,22806,117641637,"escort",9.809199303175808,,true,37,1,941133097,"WALK",1.1693447862605972,1882266194 -2869308,1160345,"escort",3,true,4,22806,22761,117641637,"parking",,,true,38,1,941133098,"WALK",1.0527105195710942,1882266195 -2869308,1160345,"escort",4,true,4,22738,22806,117641637,"escort",,,false,38,1,941133098,"SHARED2",-0.7899590349500466,1882266196 -2869308,1160345,"escort",1,false,2,22762,22738,117641637,"escort",11.267844899645352,,false,39,1,941133101,"DRIVEALONE",-0.33121883758411125,1882266201 -2869308,1160345,"escort",2,false,2,22788,22762,117641637,"home",,,false,40,1,941133102,"SHARED2",-0.21686205931765942,1882266202 -2869308,1160345,"work",1,true,1,22769,22788,117641667,"work",,,false,11,1,941133337,"SHARED2",-0.24887791851324914,1882266673 -2869308,1160345,"work",1,false,6,22769,22769,117641667,"othmaint",11.968949912548455,,false,27,1,941133341,"SHARED3",-0.004404805067726633,1882266681 -2869308,1160345,"work",2,false,6,22761,22769,117641667,"parking",,,false,28,1,941133342,"WALK",-0.6678721152911544,1882266682 -2869308,1160345,"work",3,false,6,22767,22761,117641667,"shopping",10.633629340799134,,true,28,1,941133342,"WALK",3.0199993221581605,1882266683 -2869308,1160345,"work",4,false,6,22807,22767,117641667,"escort",13.512213256227986,,true,29,1,941133343,"WALK",4.2137726609909425,1882266684 -2869308,1160345,"work",5,false,6,22761,22807,117641667,"parking",,,true,30,1,941133344,"WALK",3.869947742844953,1882266685 -2869308,1160345,"work",6,false,6,22788,22761,117641667,"home",,,false,30,1,941133344,"SHARED3",-0.41885728895985064,1882266686 +2851666,1151807,"school",1,true,1,22738,22768,116918337,"school",,,false,9,1,935346697,"WALK",-0.23394837977299351,1870693393 +2851666,1151807,"school",1,false,4,22766,22738,116918337,"parking",,,false,26,1,935346701,"WALK",-0.12281263655431907,1870693401 +2851666,1151807,"school",2,false,4,22767,22766,116918337,"eatout",12.976839556161908,,true,26,1,935346701,"WALK",-0.452052425913061,1870693402 +2851666,1151807,"school",3,false,4,22766,22767,116918337,"parking",,,true,26,1,935346702,"WALK",2.053743433341529,1870693403 +2851666,1151807,"school",4,false,4,22768,22766,116918337,"home",,,false,26,1,935346702,"SHARED3",-0.16257826107609574,1870693404 +2853258,1152693,"work",1,true,1,22738,22767,116983617,"work",,,false,20,0,935868937,"WALK",-0.22675750604679695,1871737873 +2853258,1152693,"work",1,false,1,22767,22738,116983617,"home",,,false,42,0,935868941,"WALK",-0.22675750604679695,1871737881 +2864033,1157863,"work",1,true,1,22801,22818,117425392,"work",,,false,22,0,939403137,"WALK",3.73570922575177,1878806273 +2864033,1157863,"work",1,false,3,22771,22801,117425392,"othmaint",26.926672191384228,,false,43,0,939403141,"WALK",4.180094740047142,1878806281 +2864033,1157863,"work",2,false,3,22767,22771,117425392,"othmaint",27.815811398797507,,false,43,0,939403142,"WALK",4.6038065658867176,1878806282 +2864033,1157863,"work",3,false,3,22818,22767,117425392,"home",,,false,44,0,939403143,"WALK",4.817999024372856,1878806283 +2867650,1159450,"work",1,true,2,22800,22791,117573689,"parking",,,false,12,1,940589513,"DRIVEALONE",-0.06025969651174059,1881179025 +2867650,1159450,"work",2,true,2,22800,22800,117573689,"work",,,true,12,1,940589513,"WALK",3.0701275509879262,1881179026 +2867650,1159450,"work",1,false,2,22800,22800,117573689,"parking",,,true,37,1,940589517,"WALK",3.070138980004933,1881179033 +2867650,1159450,"work",2,false,2,22791,22800,117573689,"home",,,false,37,1,940589517,"WALK",0.2738110299731082,1881179034 +2867652,1159450,"school",1,true,1,22798,22791,117573763,"school",,,false,11,0,940590105,"WALK",-0.1419702876491479,1881180209 +2867652,1159450,"school",1,false,2,22807,22798,117573763,"escort",12.102989575726829,,false,26,0,940590109,"WALK",0.30995293909650445,1881180217 +2867652,1159450,"school",2,false,2,22791,22807,117573763,"home",,,false,27,0,940590110,"WALK",1.1921458680932127,1881180218 +2867653,1159450,"school",1,true,1,22738,22791,117573804,"school",,,false,9,0,940590433,"BIKE",-0.6921067330756006,1881180865 +2867653,1159450,"school",1,false,1,22791,22738,117573804,"home",,,false,23,0,940590437,"BIKE",-0.6921067330756006,1881180873 +2867653,1159450,"school",1,true,1,22738,22791,117573805,"school",,,false,8,0,940590441,"SCHOOLBUS",-1.3378817936541838,1881180881 +2867653,1159450,"school",1,false,1,22791,22738,117573805,"home",,,false,8,0,940590445,"SHARED2",-9.871239947524709,1881180889 +2869308,1160345,"escort",1,true,1,22814,22788,117641637,"escort",,,false,10,0,941133097,"WALK",-0.2948412350381067,1882266193 +2869308,1160345,"escort",1,false,1,22788,22814,117641637,"home",,,false,10,0,941133101,"WALK",-0.2948412350381067,1882266201 +2869308,1160345,"work",1,true,1,22640,22788,117641667,"work",,,false,11,1,941133337,"SHARED2",-0.6362720979256549,1882266673 +2869308,1160345,"work",1,false,6,22761,22640,117641667,"parking",,,false,27,1,941133341,"SHARED3",-0.5969499608294799,1882266681 +2869308,1160345,"work",2,false,6,22767,22761,117641667,"othmaint",11.470504256383801,,true,27,1,941133341,"WALK",3.0222067430139012,1882266682 +2869308,1160345,"work",3,false,6,22761,22767,117641667,"parking",,,true,28,1,941133342,"WALK",4.036268963328661,1882266683 +2869308,1160345,"work",4,false,6,22769,22761,117641667,"shopping",13.481404724590321,,false,28,1,941133342,"WALK",0.4697934390352235,1882266684 +2869308,1160345,"work",5,false,6,22769,22769,117641667,"escort",13.817298583185659,,false,29,1,941133343,"DRIVEALONE",0.5088371121647843,1882266685 +2869308,1160345,"work",6,false,6,22788,22769,117641667,"home",,,false,30,1,941133344,"DRIVEALONE",0.38196512409824096,1882266686 2869309,1160345,"univ",1,true,2,22795,22788,117641700,"parking",,,false,13,1,941133601,"DRIVEALONE",-0.15235107523409816,1882267201 2869309,1160345,"univ",2,true,2,22766,22795,117641700,"univ",,,true,13,1,941133601,"WALK_LOC",1.202786557349171,1882267202 -2869309,1160345,"univ",1,false,3,22766,22766,117641700,"othdiscr",12.456311079956105,,true,24,1,941133605,"WALK",2.0068506545834075,1882267209 -2869309,1160345,"univ",2,false,3,22795,22766,117641700,"parking",,,true,24,1,941133606,"WALK_LOC",1.142188272503556,1882267210 -2869309,1160345,"univ",3,false,3,22788,22795,117641700,"home",,,false,24,1,941133606,"DRIVEALONE",-0.15842120768012627,1882267211 -2869392,1160408,"shopping",1,true,1,22769,22784,117645105,"shopping",,,false,26,0,941160841,"DRIVEALONE",-0.6680935247002481,1882321681 -2869392,1160408,"shopping",1,false,2,22770,22769,117645105,"othmaint",11.503374294479649,,false,36,0,941160845,"WALK",-0.5869025084004701,1882321689 -2869392,1160408,"shopping",2,false,2,22784,22770,117645105,"home",,,false,37,0,941160846,"WALK",-0.14561343082958378,1882321690 -2871041,1161101,"work",1,true,1,22770,22747,117712720,"work",,,false,10,0,941701761,"WALK",4.37274480605373,1883403521 -2871041,1161101,"work",1,false,1,22747,22770,117712720,"home",,,false,30,0,941701765,"WALK",4.374474053696968,1883403529 +2869309,1160345,"univ",1,false,3,22795,22766,117641700,"parking",,,true,24,1,941133605,"WALK_LOC",1.142188272503556,1882267209 +2869309,1160345,"univ",2,false,3,22769,22795,117641700,"othdiscr",12.456311079956105,,false,24,1,941133605,"WALK",-1.6313849658981006,1882267210 +2869309,1160345,"univ",3,false,3,22788,22769,117641700,"home",,,false,24,1,941133606,"DRIVEALONE",-0.3087846946902839,1882267211 +2869392,1160408,"shopping",1,true,2,22797,22784,117645105,"parking",,,false,26,1,941160841,"DRIVEALONE",-0.0973453493011812,1882321681 +2869392,1160408,"shopping",2,true,2,22767,22797,117645105,"shopping",,,true,26,1,941160841,"WALK",3.635953706352258,1882321682 +2869392,1160408,"shopping",1,false,3,22797,22767,117645105,"parking",,,true,36,1,941160845,"WALK",3.2319057420708,1882321689 +2869392,1160408,"shopping",2,false,3,22778,22797,117645105,"othmaint",13.169263985923191,,false,36,1,941160845,"DRIVEALONE",-0.8388301157218331,1882321690 +2869392,1160408,"shopping",3,false,3,22784,22778,117645105,"home",,,false,37,1,941160846,"DRIVEALONE",-0.21796488394191485,1882321691 +2871041,1161101,"work",1,true,1,22801,22747,117712720,"work",,,false,10,0,941701761,"PNR_LOC",0.001366053793971812,1883403521 +2871041,1161101,"work",1,false,1,22747,22801,117712720,"home",,,false,30,0,941701765,"PNR_LOC",-0.0004743815570571668,1883403529 2871042,1161101,"work",1,true,2,22802,22747,117712761,"parking",,,false,6,1,941702089,"DRIVEALONE",0.31437493739186884,1883404177 2871042,1161101,"work",2,true,2,22802,22802,117712761,"work",,,true,6,1,941702089,"WALK",3.98103278438962,1883404178 2871042,1161101,"work",1,false,2,22802,22802,117712761,"parking",,,true,31,1,941702093,"WALK",3.9810287626204213,1883404185 2871042,1161101,"work",2,false,2,22747,22802,117712761,"home",,,false,31,1,941702093,"WALK",0.29964022247838484,1883404186 4717826,1936565,"univ",1,true,1,22809,22808,193430897,"univ",,,false,25,0,1547447177,"WALK",2.48948699138067,3094894353 4717826,1936565,"univ",1,false,4,22809,22809,193430897,"univ",10.85837416878764,22809,false,42,0,1547447181,"WALK",3.0000160707611045,3094894361 -4717826,1936565,"univ",2,false,4,22802,22809,193430897,"social",14.420134553925665,,false,43,0,1547447182,"WALK",2.8898362057163802,3094894362 -4717826,1936565,"univ",3,false,4,22807,22802,193430897,"eatout",18.598339591406937,,false,44,0,1547447183,"WALK_LOC",5.851209408094483,3094894363 -4717826,1936565,"univ",4,false,4,22808,22807,193430897,"home",,,false,44,0,1547447184,"WALK",5.537675529040812,3094894364 -4718747,1937486,"univ",1,true,3,22807,22765,193468658,"eatout",25.835053255003054,,false,14,0,1547749265,"WALK_LOC",5.394119748970986,3095498529 -4718747,1937486,"univ",2,true,3,22807,22807,193468658,"social",26.07487490221835,,false,16,0,1547749266,"WALK",5.765967272606238,3095498530 -4718747,1937486,"univ",3,true,3,22809,22807,193468658,"univ",,,false,19,0,1547749267,"WALK",3.0089831584168625,3095498531 +4717826,1936565,"univ",2,false,4,22767,22809,193430897,"social",14.420134553925665,,false,43,0,1547447182,"WALK",2.7860651296874943,3094894362 +4717826,1936565,"univ",3,false,4,22767,22767,193430897,"eatout",18.783329870271647,,false,44,0,1547447183,"WALK",5.6282528531399505,3094894363 +4717826,1936565,"univ",4,false,4,22808,22767,193430897,"home",,,false,44,0,1547447184,"WALK",5.305674253948064,3094894364 +4718747,1937486,"univ",1,true,3,22771,22765,193468658,"eatout",25.835053255003054,,false,14,0,1547749265,"WALK_LOC",4.433464410699681,3095498529 +4718747,1937486,"univ",2,true,3,22767,22771,193468658,"social",25.54589732725773,,false,16,0,1547749266,"WALK",5.362458425962315,3095498530 +4718747,1937486,"univ",3,true,3,22809,22767,193468658,"univ",,,false,19,0,1547749267,"WALK",2.7945694548961417,3095498531 4718747,1937486,"univ",1,false,1,22765,22809,193468658,"home",,,false,42,0,1547749269,"WALK",2.48457681340577,3095498537 -4718747,1937486,"shopping",1,true,2,22767,22765,193468660,"shopping",30.837861614853992,,false,12,0,1547749281,"WALK",6.438600267913209,3095498561 +4718747,1937486,"shopping",1,true,2,22767,22765,193468660,"shopping",30.83670381348187,,false,13,0,1547749281,"WALK",6.4385685368034355,3095498561 4718747,1937486,"shopping",2,true,2,22770,22767,193468660,"shopping",,,false,13,0,1547749282,"WALK",5.192455869479483,3095498562 -4718747,1937486,"shopping",1,false,1,22765,22770,193468660,"home",,,false,13,0,1547749285,"WALK",4.807792080345957,3095498569 -4720352,1939091,"univ",1,true,1,22809,22765,193534463,"univ",,,false,9,0,1548275705,"WALK",-0.9117642771058314,3096551409 -4720352,1939091,"univ",1,false,3,22767,22809,193534463,"shopping",11.843847663623558,,false,9,0,1548275709,"WALK",-0.50518420043921,3096551417 -4720352,1939091,"univ",2,false,3,22760,22767,193534463,"othdiscr",19.589050848806597,,false,9,0,1548275710,"WALK",2.07708617142782,3096551418 +4718747,1937486,"shopping",1,false,1,22765,22770,193468660,"home",,,false,14,0,1547749285,"WALK",4.807792080345957,3095498569 +4720352,1939091,"univ",1,true,1,22766,22765,193534463,"univ",,,false,9,0,1548275705,"WALK",-0.6239793637995562,3096551409 +4720352,1939091,"univ",1,false,3,22759,22766,193534463,"shopping",11.15021662330538,,false,9,0,1548275709,"WALK",-0.5391508363377113,3096551417 +4720352,1939091,"univ",2,false,3,22760,22759,193534463,"othdiscr",17.448616740575964,,false,9,0,1548275710,"WALK",1.2201965059296072,3096551418 4720352,1939091,"univ",3,false,3,22765,22760,193534463,"home",,,false,9,0,1548275711,"WALK",2.8041844809824235,3096551419 -4720352,1939091,"univ",1,true,1,22809,22765,193534464,"univ",,,false,23,0,1548275713,"WALK",2.507472441202307,3096551425 -4720352,1939091,"univ",1,false,2,22766,22809,193534464,"univ",10.595098453730076,22766,false,27,0,1548275717,"WALK",2.554225976269817,3096551433 -4720352,1939091,"univ",2,false,2,22765,22766,193534464,"home",,,false,28,0,1548275718,"WALK",2.711686716364389,3096551434 +4720352,1939091,"univ",1,true,1,22766,22765,193534464,"univ",,,false,13,0,1548275713,"WALK",2.715317834267571,3096551425 +4720352,1939091,"univ",1,false,2,22764,22766,193534464,"univ",11.320277288258279,,false,20,0,1548275717,"WALK",2.6563322165532184,3096551433 +4720352,1939091,"univ",2,false,2,22765,22764,193534464,"home",,,false,21,0,1548275718,"WALK",2.683225159417532,3096551434 4722297,1942003,"univ",1,true,1,22809,22810,193614208,"univ",,,false,11,0,1548913665,"WALK",2.4667125356379236,3097827329 4722297,1942003,"univ",1,false,1,22810,22809,193614208,"home",,,false,37,0,1548913669,"WALK",2.4563973988486754,3097827337 -4726458,1946164,"eatout",1,true,1,22770,22808,193784784,"eatout",,,false,27,0,1550278273,"WALK",0.3756438367025996,3100556545 -4726458,1946164,"eatout",1,false,1,22808,22770,193784784,"home",,,false,29,0,1550278277,"WALK",0.3756438367025996,3100556553 -4726458,1946164,"eatout",1,true,1,22771,22808,193784785,"eatout",,,false,29,0,1550278281,"WALK",0.6461148549373952,3100556561 -4726458,1946164,"eatout",1,false,1,22808,22771,193784785,"home",,,false,30,0,1550278285,"WALK",0.6461148549373952,3100556569 +4726458,1946164,"eatout",1,true,1,22762,22808,193784784,"eatout",,,false,21,0,1550278273,"WALK",-1.0299557646373856,3100556545 +4726458,1946164,"eatout",1,false,1,22808,22762,193784784,"home",,,false,22,0,1550278277,"WALK",-1.0299556501964702,3100556553 +4726458,1946164,"eatout",1,true,1,22773,22808,193784785,"eatout",,,false,28,0,1550278281,"WALK",-0.5777209461821046,3100556561 +4726458,1946164,"eatout",1,false,1,22808,22773,193784785,"home",,,false,29,0,1550278285,"WALK",-0.5777209461821046,3100556569 4726458,1946164,"shopping",1,true,1,22770,22808,193784811,"shopping",,,false,14,0,1550278489,"WALK",0.3756438367025996,3100556977 4726458,1946164,"shopping",1,false,1,22808,22770,193784811,"home",,,false,17,0,1550278493,"WALK",0.3756438367025996,3100556985 -4727363,1947069,"univ",1,true,1,22809,22765,193821914,"univ",,,false,14,0,1550575313,"WALK",-0.9117642771058314,3101150625 -4727363,1947069,"univ",1,false,3,22767,22809,193821914,"escort",13.861849979093286,,false,26,0,1550575317,"WALK",-0.50518420043921,3101150633 +4727363,1947069,"univ",1,true,1,22766,22765,193821914,"univ",,,false,14,0,1550575313,"WALK",-0.6239793637995562,3101150625 +4727363,1947069,"univ",1,false,3,22767,22766,193821914,"escort",13.043891235923125,,false,26,0,1550575317,"WALK",-0.9673991559282129,3101150633 4727363,1947069,"univ",2,false,3,22767,22767,193821914,"shopping",18.14486120913688,,false,26,0,1550575318,"WALK",2.62825193059268,3101150634 4727363,1947069,"univ",3,false,3,22765,22767,193821914,"home",,,false,27,0,1550575319,"WALK",2.1708672114306493,3101150635 -4729458,1949164,"univ",1,true,2,22767,22745,193907809,"eatout",13.431035125581994,,false,11,0,1551262473,"WALK",2.0891749086454086,3102524945 -4729458,1949164,"univ",2,true,2,22764,22767,193907809,"univ",,,false,11,0,1551262474,"WALK",-0.5148347167335139,3102524946 -4729458,1949164,"univ",1,false,2,22767,22764,193907809,"othdiscr",14.563044668763776,,false,27,0,1551262477,"WALK",-0.5148347167335139,3102524953 -4729458,1949164,"univ",2,false,2,22745,22767,193907809,"home",,,false,28,0,1551262478,"WALK",2.0891749086454086,3102524954 -4729679,1949385,"eatout",1,true,1,22745,22745,193916845,"eatout",,,false,26,0,1551334761,"WALK",0.7839251911505445,3102669521 -4729679,1949385,"eatout",1,false,1,22745,22745,193916845,"home",,,false,27,0,1551334765,"WALK",0.7839251911505445,3102669529 +4729458,1949164,"univ",1,true,2,22767,22745,193907809,"eatout",25.639413512184284,,false,11,0,1551262473,"WALK_LOC",5.44204000187347,3102524945 +4729458,1949164,"univ",2,true,2,22809,22767,193907809,"univ",,,false,11,0,1551262474,"WALK",2.7976141090172613,3102524946 +4729458,1949164,"univ",1,false,2,22802,22809,193907809,"othdiscr",26.8381285605357,,false,27,0,1551262477,"WALK",2.9113281848126373,3102524953 +4729458,1949164,"univ",2,false,2,22745,22802,193907809,"home",,,false,28,0,1551262478,"WALK",5.509333658704657,3102524954 +4729679,1949385,"eatout",1,true,1,22748,22745,193916845,"eatout",,,false,26,0,1551334761,"WALK",0.10355029016646346,3102669521 +4729679,1949385,"eatout",1,false,1,22745,22748,193916845,"home",,,false,27,0,1551334765,"WALK",0.10355029016646346,3102669529 diff --git a/activitysim/examples/production_semcog/test/regress/final_trips.csv b/activitysim/examples/production_semcog/test/regress/final_trips.csv index 3402fa28c2..47ebf986c7 100644 --- a/activitysim/examples/production_semcog/test/regress/final_trips.csv +++ b/activitysim/examples/production_semcog/test/regress/final_trips.csv @@ -1,169 +1,169 @@ -trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,original_school_zone_id,parked_at_university,depart,tour_includes_parking,trip_id_pre_parking,trip_mode,mode_choice_logsum -1727022433,2632656,1066353,eatout,1,True,2,22766,22688,107938902,parking,,,False,25.0,1,863511217,DRIVEALONE,-0.8746901452708546 -1727022434,2632656,1066353,eatout,2,True,2,22767,22766,107938902,eatout,,,True,25.0,1,863511217,WALK,6.210014031663612 -1727022441,2632656,1066353,eatout,1,False,2,22766,22767,107938902,parking,,,True,37.0,1,863511221,WALK,6.210037419792028 -1727022442,2632656,1066353,eatout,2,False,2,22688,22766,107938902,home,,,False,37.0,1,863511221,DRIVEALONE,-0.7301658119164749 -1727022961,2632656,1066353,work,1,True,1,22676,22688,107938935,work,,,False,6.0,0,863511481,DRIVEALONE,0.04287730952963362 -1727022969,2632656,1066353,work,1,False,1,22688,22676,107938935,home,,,False,20.0,0,863511485,DRIVEALONE,0.04281092148226895 -1727023489,2632657,1066353,school,1,True,1,22694,22688,107938968,school,,,False,9.0,0,863511745,SCHOOLBUS,-1.3476633093405816 -1727023497,2632657,1066353,school,1,False,2,22688,22694,107938968,eatout,-23.29946944115026,,False,26.0,0,863511749,SHARED3,-9.70321875140174 -1727023498,2632657,1066353,school,2,False,2,22688,22688,107938968,home,,,False,26.0,0,863511750,SHARED2,-8.514611350339074 -1727023793,2632658,1066353,escort,1,True,1,22689,22688,107938987,escort,,,False,32.0,0,863511897,WALK,0.0 -1727023801,2632658,1066353,escort,1,False,1,22688,22689,107938987,home,,,False,33.0,0,863511901,WALK,0.0 -1727023809,2632658,1066353,escort,1,True,1,22694,22688,107938988,escort,,,False,11.0,0,863511905,DRIVEALONE,0.14516010385695438 -1727023817,2632658,1066353,escort,1,False,1,22688,22694,107938988,home,,,False,12.0,0,863511909,SHARED3,0.13787110648003725 -1727024449,2632659,1066353,escort,1,True,1,22694,22688,107939028,escort,,,False,8.0,0,863512225,SHARED2,0.14516010385695438 -1727024457,2632659,1066353,escort,1,False,1,22688,22694,107939028,home,,,False,8.0,0,863512229,SHARED3,0.13787110648003725 -1727076625,2632738,1066386,school,1,True,2,22688,22688,107942289,eatout,10.532297646277984,,False,11.0,0,863538313,SHARED3,0.5546717586355614 -1727076626,2632738,1066386,school,2,True,2,22716,22688,107942289,school,,,False,11.0,0,863538314,SHARED2,0.04685598325940043 -1727076633,2632738,1066386,school,1,False,2,22685,22716,107942289,escort,12.187082550220447,,False,26.0,0,863538317,SHARED3,0.0461601223803947 -1727076634,2632738,1066386,school,2,False,2,22688,22685,107942289,home,,,False,27.0,0,863538318,SHARED3,0.49312008693289416 -1732416961,2640879,1069967,social,1,True,2,22688,22676,108276060,social,11.37473397796374,,False,31.0,0,866208481,SHARED2,0.006004185805916569 -1732416962,2640879,1069967,social,2,True,2,22688,22688,108276060,social,,,False,32.0,0,866208482,SHARED2,0.07253109075082566 -1732416969,2640879,1069967,social,1,False,1,22676,22688,108276060,home,,,False,38.0,0,866208485,SHARED2,0.006188670261483639 -1732417249,2640879,1069967,work,1,True,1,22688,22676,108276078,work,,,False,12.0,0,866208625,SHARED2,0.6840765480327687 -1732417257,2640879,1069967,work,1,False,1,22676,22688,108276078,home,,,False,19.0,0,866208629,DRIVEALONE,0.6845432142839015 -1732417265,2640879,1069967,work,1,True,1,22688,22676,108276079,work,,,False,11.0,0,866208633,DRIVEALONE,0.060574367289556945 -1732417273,2640879,1069967,work,1,False,1,22676,22688,108276079,home,,,False,12.0,0,866208637,DRIVEALONE,0.061074290505893525 -1735713169,2645904,1072088,escort,1,True,1,22694,22711,108482073,escort,,,False,28.0,0,867856585,DRIVEALONE,0.10721753220207553 -1735713177,2645904,1072088,escort,1,False,1,22711,22694,108482073,home,,,False,30.0,0,867856589,SHARED2,0.10722521021183494 -1735713265,2645905,1072088,othdiscr,1,True,2,22766,22711,108482079,parking,,,False,27.0,1,867856633,SHARED2,-1.1455545722211293 -1735713266,2645905,1072088,othdiscr,2,True,2,22766,22766,108482079,othdiscr,,,True,27.0,1,867856633,SHARED2,3.6111900800276358 -1735713273,2645905,1072088,othdiscr,1,False,2,22766,22766,108482079,parking,,,True,35.0,1,867856637,WALK,3.6112032457334435 -1735713274,2645905,1072088,othdiscr,2,False,2,22711,22766,108482079,home,,,False,35.0,1,867856637,SHARED2,-0.9957395363664312 -1735714209,2645905,1072088,shopping,1,True,3,22711,22711,108482138,othmaint,9.775720423403623,,False,22.0,0,867857105,WALK,0.1085924278937159 -1735714210,2645905,1072088,shopping,2,True,3,22711,22711,108482138,social,10.488858934392573,,False,23.0,0,867857106,WALK,0.1085924278937159 -1735714211,2645905,1072088,shopping,3,True,3,22711,22711,108482138,shopping,,,False,24.0,0,867857107,WALK,0.1085924278937159 -1735714217,2645905,1072088,shopping,1,False,1,22711,22711,108482138,home,,,False,24.0,0,867857109,WALK,0.1085924278937159 -1735715489,2645907,1072088,school,1,True,1,22716,22711,108482218,school,,,False,12.0,0,867857745,DRIVEALONE,-0.20641630035108205 -1735715497,2645907,1072088,school,1,False,1,22711,22716,108482218,home,,,False,26.0,0,867857749,DRIVEALONE,-0.20450702798248435 -1752394321,2671332,1083128,shopping,1,True,1,22650,22637,109524645,shopping,,,False,29.0,0,876197161,DRIVEALONE,-0.04893274687468395 -1752394329,2671332,1083128,shopping,1,False,2,22713,22650,109524645,eatout,8.732598620243117,,False,30.0,0,876197165,DRIVEALONE,-0.564278349048812 -1752394330,2671332,1083128,shopping,2,False,2,22637,22713,109524645,home,,,False,30.0,0,876197166,DRIVEALONE,-0.6753239045055804 -1871905057,2853513,1152948,shopping,1,True,1,22781,22770,116994066,shopping,,,False,13.0,0,935952529,WALK,-0.15286567920809457 -1871905065,2853513,1152948,shopping,1,False,1,22770,22781,116994066,home,,,False,16.0,0,935952533,WALK,-0.15286567920809457 -1871905073,2853513,1152948,shopping,1,True,1,22800,22770,116994067,shopping,,,False,22.0,0,935952537,WALK,-0.6260852483044879 -1871905081,2853513,1152948,shopping,1,False,2,22807,22800,116994067,othmaint,13.689405015818817,,False,23.0,0,935952541,WALK,-0.5662778537062289 -1871905082,2853513,1152948,shopping,2,False,2,22770,22807,116994067,home,,,False,23.0,0,935952542,WALK,2.1564636451535324 -1871905089,2853513,1152948,shopping,1,True,1,22798,22770,116994068,shopping,,,False,25.0,0,935952545,WALK,0.3629456060638815 -1871905097,2853513,1152948,shopping,1,False,1,22770,22798,116994068,home,,,False,32.0,0,935952549,WALK,0.3629456060638815 -1873669969,2856204,1154357,escort,1,True,1,22767,22815,117104373,escort,,,False,11.0,0,936834985,WALK,1.572441903227518 -1873669977,2856204,1154357,escort,1,False,1,22815,22767,117104373,home,,,False,11.0,0,936834989,WALK,1.572441903227518 -1873670225,2856204,1154357,othdiscr,1,True,1,22795,22815,117104389,othdiscr,,,False,11.0,0,936835113,WALK,-1.136157391902266 -1873670233,2856204,1154357,othdiscr,1,False,1,22815,22795,117104389,home,,,False,15.0,0,936835117,WALK,-1.136157391902266 -1873670321,2856204,1154357,univ,1,True,3,22767,22815,117104395,work,13.498034431649993,,False,21.0,0,936835161,WALK,5.028511196364142 -1873670322,2856204,1154357,univ,2,True,3,22809,22767,117104395,univ,10.775599452844196,22809,False,22.0,0,936835162,WALK_LOC,2.7954607582586246 -1873670323,2856204,1154357,univ,3,True,3,22809,22809,117104395,univ,,,False,24.0,0,936835163,WALK,3.0008457318923365 -1873670329,2856204,1154357,univ,1,False,4,22767,22809,117104395,othmaint,11.97899358246422,,False,42.0,0,936835165,WALK,2.7869485143816908 -1873670330,2856204,1154357,univ,2,False,4,22764,22767,117104395,univ,14.341759600913552,,False,42.0,0,936835166,WALK,5.487620370472843 -1873670331,2856204,1154357,univ,3,False,4,22770,22764,117104395,othdiscr,12.599191957343043,,False,44.0,0,936835167,WALK,2.6096929225906433 -1873670332,2856204,1154357,univ,4,False,4,22815,22770,117104395,home,,,False,44.0,0,936835168,WALK_LOC,3.5917238491796746 -1873969057,2856660,1154635,eatout,1,True,1,22810,22815,117123066,eatout,,,False,32.0,0,936984529,WALK,-0.29168228455903983 -1873969065,2856660,1154635,eatout,1,False,1,22815,22810,117123066,home,,,False,33.0,0,936984533,WALK,-0.29168228455903983 -1873969457,2856660,1154635,univ,1,True,1,22764,22815,117123091,univ,,,False,17.0,0,936984729,WALK,1.8818869666378932 -1873969465,2856660,1154635,univ,1,False,4,22766,22764,117123091,univ,9.92705808981923,22766,False,20.0,0,936984733,WALK,2.332439730730206 -1873969466,2856660,1154635,univ,2,False,4,22767,22766,117123091,eatout,11.698000750719451,,False,20.0,0,936984734,WALK,2.0354974925066145 -1873969467,2856660,1154635,univ,3,False,4,22764,22767,117123091,univ,14.467091292345321,,False,26.0,0,936984735,WALK,5.113556461480974 -1873969468,2856660,1154635,univ,4,False,4,22815,22764,117123091,home,,,False,30.0,0,936984736,WALK,1.8818676173433673 -1873970113,2856661,1154635,univ,1,True,2,22767,22815,117123132,eatout,24.13568322189892,,False,9.0,0,936985057,WALK,5.024093072052891 -1873970114,2856661,1154635,univ,2,True,2,22809,22767,117123132,univ,,,False,10.0,0,936985058,WALK,2.7951650320100074 -1873970121,2856661,1154635,univ,1,False,4,22809,22809,117123132,univ,22.10560655915902,22809,False,24.0,0,936985061,WALK,3.000453614136991 -1873970122,2856661,1154635,univ,2,False,4,22809,22809,117123132,univ,21.992495219718602,22809,False,24.0,0,936985062,WALK,3.000453614136991 -1873970123,2856661,1154635,univ,3,False,4,22807,22809,117123132,work,26.50952124631712,,False,25.0,0,936985063,WALK,3.007444572576448 -1873970124,2856661,1154635,univ,4,False,4,22815,22807,117123132,home,,,False,31.0,0,936985064,WALK,5.1535448290063615 -1877439505,2861950,1156849,shopping,1,True,1,22800,22801,117339969,shopping,,,False,21.0,0,938719753,WALK,-0.49881710844895727 -1877439513,2861950,1156849,shopping,1,False,1,22801,22800,117339969,home,,,False,30.0,0,938719757,WALK,-0.49881710844895727 -1877439697,2861950,1156849,univ,1,True,1,22809,22801,117339981,univ,,,False,13.0,0,938719849,WALK,2.7089529835300503 -1877439705,2861950,1156849,univ,1,False,2,22766,22809,117339981,univ,22.262579096450402,22766,False,20.0,0,938719853,WALK_LOC,2.560406996243793 -1877439706,2861950,1156849,univ,2,False,2,22801,22766,117339981,home,,,False,21.0,0,938719854,WALK_LOC,2.3733339088312397 -1877440353,2861951,1156849,univ,1,True,1,22809,22801,117340022,univ,,,False,11.0,0,938720177,WALK,-0.656569218265208 -1877440361,2861951,1156849,univ,1,False,3,22767,22809,117340022,othdiscr,14.308117668699108,,False,12.0,0,938720181,WALK,-0.50518420043921 -1877440362,2861951,1156849,univ,2,False,3,22767,22767,117340022,shopping,17.816995526914052,,False,12.0,0,938720182,WALK,2.62825193059268 -1877440363,2861951,1156849,univ,3,False,3,22801,22767,117340022,home,,,False,13.0,0,938720183,WALK,1.890862363486975 -1877441009,2861952,1156849,univ,1,True,1,22809,22801,117340063,univ,,,False,7.0,0,938720505,WALK,-0.656569218265208 -1877441017,2861952,1156849,univ,1,False,1,22801,22809,117340063,home,,,False,11.0,0,938720509,WALK,-0.656569218265208 -1877441025,2861952,1156849,univ,1,True,1,22809,22801,117340064,univ,,,False,30.0,0,938720513,WALK,-0.656569218265208 -1877441033,2861952,1156849,univ,1,False,1,22801,22809,117340064,home,,,False,32.0,0,938720517,WALK,-0.656569218265208 -1877508577,2862055,1156884,univ,1,True,3,22767,22804,117344286,eatout,12.979710329497422,,False,19.0,0,938754289,WALK,1.8938019500606744 -1877508578,2862055,1156884,univ,2,True,3,22767,22767,117344286,work,15.06828301301832,,False,20.0,0,938754290,WALK,2.62825193059268 -1877508579,2862055,1156884,univ,3,True,3,22809,22767,117344286,univ,,,False,22.0,0,938754291,WALK,-0.50518420043921 -1877508585,2862055,1156884,univ,1,False,1,22804,22809,117344286,home,,,False,22.0,0,938754293,WALK,-0.44242952311569 -1877509233,2862056,1156884,univ,1,True,1,22809,22804,117344327,univ,,,False,12.0,0,938754617,WALK,2.816810400707295 -1877509241,2862056,1156884,univ,1,False,1,22804,22809,117344327,home,,,False,27.0,0,938754621,WALK,2.818487568540833 -1877509889,2862057,1156884,univ,1,True,1,22809,22804,117344368,univ,,,False,9.0,0,938754945,WALK,2.8149418226097684 -1877509897,2862057,1156884,univ,1,False,4,22767,22809,117344368,othdiscr,14.092132672154193,,False,29.0,0,938754949,WALK,2.8115136852150613 -1877509898,2862057,1156884,univ,2,False,4,22809,22767,117344368,univ,15.082414498585248,22809,False,29.0,0,938754950,WALK_LOC,5.421713617528955 -1877509899,2862057,1156884,univ,3,False,4,22809,22809,117344368,univ,11.07119990210677,22809,False,29.0,0,938754951,WALK,3.0039396426418254 -1877509900,2862057,1156884,univ,4,False,4,22804,22809,117344368,home,,,False,40.0,0,938754952,WALK,2.7990874824379435 -1878731969,2863920,1157823,othmaint,1,True,2,22795,22812,117420748,parking,,,False,7.0,1,939365985,DRIVEALONE,-0.09902637407657627 -1878731970,2863920,1157823,othmaint,2,True,2,22806,22795,117420748,othmaint,,,True,7.0,1,939365985,WALK,3.92788746633635 -1878731977,2863920,1157823,othmaint,1,False,7,22767,22806,117420748,eatout,11.485301584384368,,True,8.0,1,939365989,WALK,4.150955713093153 -1878731978,2863920,1157823,othmaint,2,False,7,22795,22767,117420748,parking,,,True,8.0,1,939365990,WALK,4.44330039201803 -1878731979,2863920,1157823,othmaint,3,False,7,22738,22795,117420748,shopping,11.358833245442405,,False,8.0,1,939365990,DRIVEALONE,-0.6103921916626766 -1878731980,2863920,1157823,othmaint,4,False,7,22795,22738,117420748,parking,,,False,9.0,1,939365991,DRIVEALONE,-0.39605030270985575 -1878731981,2863920,1157823,othmaint,5,False,7,22767,22795,117420748,eatout,11.192175326364463,,True,9.0,1,939365991,WALK,2.952615589445755 -1878731982,2863920,1157823,othmaint,6,False,7,22795,22767,117420748,parking,,,True,9.0,1,939365992,WALK,4.44330039201803 -1878731983,2863920,1157823,othmaint,7,False,7,22812,22795,117420748,home,,,False,9.0,1,939365992,DRIVEALONE,-0.11948535915923522 -1878732017,2863920,1157823,univ,1,True,1,22766,22812,117420751,univ,,,False,13.0,0,939366009,WALK,-1.5030831477846986 -1878732025,2863920,1157823,univ,1,False,1,22812,22766,117420751,home,,,False,32.0,0,939366013,WALK,-1.5030834911074449 -1878732673,2863921,1157823,univ,1,True,1,22809,22812,117420792,univ,,,False,14.0,0,939366337,WALK,-0.92894689390927 -1878732681,2863921,1157823,univ,1,False,1,22812,22809,117420792,home,,,False,15.0,0,939366341,WALK,-0.92894689390927 -1878732689,2863921,1157823,univ,1,True,1,22809,22812,117420793,univ,,,False,16.0,0,939366345,WALK_LOC,2.481692099564644 -1878732697,2863921,1157823,univ,1,False,3,22764,22809,117420793,univ,9.981605111059457,,False,24.0,0,939366349,WALK,2.459602060048778 -1878732698,2863921,1157823,univ,2,False,3,22764,22764,117420793,univ,9.84928711859661,,False,24.0,0,939366350,WALK,3.003985864868469 -1878732699,2863921,1157823,univ,3,False,3,22812,22764,117420793,home,,,False,26.0,0,939366351,WALK,2.316202097516391 -1878733329,2863922,1157823,univ,1,True,1,22764,22812,117420833,univ,,,False,16.0,0,939366665,WALK,-1.1568382386079819 -1878733337,2863922,1157823,univ,1,False,1,22812,22764,117420833,home,,,False,24.0,0,939366669,WALK,-1.1568382386079819 -1880695889,2866914,1159236,shopping,1,True,2,22738,22797,117543493,shopping,9.753195503246463,,False,20.0,1,940347945,SHARED2,-0.44959528911187474 -1880695890,2866914,1159236,shopping,2,True,2,22738,22738,117543493,shopping,,,False,21.0,1,940347946,SHARED2,-0.41569822407021684 -1880695897,2866914,1159236,shopping,1,False,2,22796,22738,117543493,parking,,,False,21.0,1,940347949,SHARED2,-0.618932986161138 -1880695898,2866914,1159236,shopping,2,False,2,22796,22796,117543493,parking,,,True,21.0,1,940347949,WALK,3.612657585008612 -1880695985,2866914,1159236,othdiscr,1,True,1,22733,22797,117543499,othdiscr,,,False,7.0,0,940347993,TNC_SHARED,-1.2334449201087534 -1880695993,2866914,1159236,othdiscr,1,False,1,22797,22733,117543499,home,,,False,9.0,0,940347997,SHARED3,-1.5864778947158276 -1880696209,2866914,1159236,work,1,True,2,22766,22797,117543513,parking,,,False,12.0,1,940348105,DRIVEALONE,-0.23277800252868205 -1880696210,2866914,1159236,work,2,True,2,22766,22766,117543513,work,,,True,12.0,1,940348105,WALK,2.014924960389029 -1880696217,2866914,1159236,work,1,False,1,22797,22766,117543513,home,,,True,18.0,1,940348109,WALK,0.7326263231237407 -1880696225,2866914,1159236,work,1,True,2,22766,22797,117543514,parking,,,False,25.0,1,940348113,DRIVEALONE,-0.2126150341505889 -1880696226,2866914,1159236,work,2,True,2,22766,22766,117543514,work,,,True,25.0,1,940348113,WALK,2.0149069444380916 -1880696233,2866914,1159236,work,1,False,1,22797,22766,117543514,home,,,True,27.0,1,940348117,WALK_LOC,0.7203611640018683 -1880696385,2866915,1159236,escort,1,True,1,22738,22797,117543524,escort,,,False,10.0,1,940348193,DRIVEALONE,-0.1859277636541118 -1880696393,2866915,1159236,escort,1,False,2,22796,22738,117543524,parking,,,False,10.0,1,940348197,DRIVEALONE,-0.3618327302148594 -1880696394,2866915,1159236,escort,2,False,2,22796,22796,117543524,parking,,,True,10.0,1,940348197,WALK,1.067154699334263 -1880696865,2866915,1159236,work,1,True,1,22801,22797,117543554,work,,,False,14.0,0,940348433,WALK,0.19014379979819185 -1880696873,2866915,1159236,work,1,False,4,22770,22801,117543554,shopping,11.568986593078103,,False,24.0,0,940348437,WALK,0.4209941097322794 -1880696874,2866915,1159236,work,2,False,4,22771,22770,117543554,eatout,13.008322605925745,,False,25.0,0,940348438,WALK,0.6865956537757166 -1880696875,2866915,1159236,work,3,False,4,22767,22771,117543554,shopping,12.587784804165072,,False,25.0,0,940348439,WALK,0.7791172141286921 -1880696876,2866915,1159236,work,4,False,4,22797,22767,117543554,home,,,False,26.0,0,940348440,WALK,0.4989418541157284 -1883150833,2870656,1160939,univ,1,True,1,22764,22740,117696927,univ,,,False,9.0,0,941575417,WALK,-0.4952620689018146 -1883150841,2870656,1160939,univ,1,False,1,22740,22764,117696927,home,,,False,20.0,0,941575421,SHARED2,-0.7088657694023863 -1885520561,2874269,1162627,eatout,1,True,1,22771,22758,117845035,eatout,,,False,19.0,0,942760281,WALK,0.8892845196071101 -1885520569,2874269,1162627,eatout,1,False,1,22758,22771,117845035,home,,,False,21.0,0,942760285,WALK,0.8892845196071101 -1885520961,2874269,1162627,univ,1,True,1,22766,22758,117845060,univ,,,False,33.0,0,942760481,WALK,-0.9851903695198061 -1885520969,2874269,1162627,univ,1,False,1,22758,22766,117845060,home,,,False,48.0,0,942760485,WALK,-0.9851903695198061 -1885521617,2874270,1162627,univ,1,True,1,22809,22758,117845101,univ,,,False,13.0,0,942760809,WALK,2.448568395465578 -1885521625,2874270,1162627,univ,1,False,3,22766,22809,117845101,univ,10.064918042210902,22766,False,29.0,0,942760813,WALK,2.5591208432062635 -1885521626,2874270,1162627,univ,2,False,3,22760,22766,117845101,eatout,12.726627067763937,,False,29.0,0,942760814,WALK,2.6006338904068236 -1885521627,2874270,1162627,univ,3,False,3,22758,22760,117845101,home,,,False,31.0,0,942760815,WALK,5.854019928994619 -1885522273,2874271,1162627,univ,1,True,1,22766,22758,117845142,univ,,,False,13.0,0,942761137,WALK,-0.9851903695198061 -1885522281,2874271,1162627,univ,1,False,3,22807,22766,117845142,social,12.701701490919964,,False,25.0,0,942761141,WALK,-0.7025935206727638 -1885522282,2874271,1162627,univ,2,False,3,22767,22807,117845142,eatout,18.97072944477215,,False,25.0,0,942761142,WALK,2.329516762852266 -1885522283,2874271,1162627,univ,3,False,3,22758,22767,117845142,home,,,False,26.0,0,942761143,WALK,2.1084560857351926 -3099151793,4724316,1944022,univ,1,True,2,22763,22765,193696987,escort,12.812689062193739,,False,26.0,0,1549575897,WALK,2.1351670996963623 -3099151794,4724316,1944022,univ,2,True,2,22766,22763,193696987,univ,,,False,27.0,0,1549575898,WALK,-0.8559965323806581 -3099151801,4724316,1944022,univ,1,False,2,22767,22766,193696987,eatout,13.154682962658466,,False,29.0,0,1549575901,WALK,-0.9673991559282129 -3099151802,4724316,1944022,univ,2,False,2,22765,22767,193696987,home,,,False,29.0,0,1549575902,WALK,2.1708672114306493 -3099404353,4724701,1944407,univ,1,True,1,22809,22808,193712772,univ,,,False,12.0,0,1549702177,BIKE,-0.2552725353403566 -3099404361,4724701,1944407,univ,1,False,2,22766,22809,193712772,univ,11.3195538505166,22766,False,37.0,0,1549702181,BIKE,-0.7042893633777787 -3099404362,4724701,1944407,univ,2,False,2,22808,22766,193712772,home,,,False,37.0,0,1549702182,BIKE,-0.6429800462056683 -3099416945,4724720,1944426,work,1,True,1,22738,22806,193713559,work,,,False,10.0,0,1549708473,WALK,3.424476741640782 -3099416953,4724720,1944426,work,1,False,1,22806,22738,193713559,home,,,False,25.0,0,1549708477,WALK,3.421200065013991 -3100974161,4727094,1946800,univ,1,True,2,22796,22808,193810885,parking,,,False,20.0,1,1550487081,SHARED2,-0.325497193352596 -3100974162,4727094,1946800,univ,2,True,2,22809,22796,193810885,univ,,,True,20.0,1,1550487081,WALK_LOC,2.1755216717496055 -3100974169,4727094,1946800,univ,1,False,1,22808,22809,193810885,home,,,True,27.0,1,1550487085,WALK,2.117177438190699 -3101586209,4728027,1947733,univ,1,True,1,22764,22806,193849138,univ,,,False,10.0,0,1550793105,WALK,-1.1092692699446687 -3101586217,4728027,1947733,univ,1,False,2,22767,22764,193849138,escort,13.485546584318579,,False,27.0,0,1550793109,WALK,-0.5148347167335139 -3101586218,4728027,1947733,univ,2,False,2,22806,22767,193849138,home,,,False,28.0,0,1550793110,WALK,1.9818127360573472 -3109893137,4740690,1970879,univ,1,True,1,22764,22745,194368321,univ,,,False,11.0,0,1554946569,WALK,2.576602218700462 -3109893145,4740690,1970879,univ,1,False,3,22768,22764,194368321,social,26.124396751214636,,False,31.0,0,1554946573,WALK,2.8253929561140825 -3109893146,4740690,1970879,univ,2,False,3,22760,22768,194368321,othdiscr,30.225849882595227,,False,32.0,0,1554946574,WALK,4.979971583696819 -3109893147,4740690,1970879,univ,3,False,3,22745,22760,194368321,home,,,False,32.0,0,1554946575,WALK,5.801965338203763 +"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","original_school_zone_id","parked_at_university","depart","tour_includes_parking","trip_id_pre_parking","trip_mode","mode_choice_logsum","trip_id" +2632656,1066353,"eatout",1,true,2,22766,22688,107938902,"parking",,,false,25,1,863511217,"DRIVEALONE",-0.8746901452708546,1727022433 +2632656,1066353,"eatout",2,true,2,22767,22766,107938902,"eatout",,,true,25,1,863511217,"WALK",6.210014031663612,1727022434 +2632656,1066353,"eatout",1,false,2,22766,22767,107938902,"parking",,,true,37,1,863511221,"WALK",6.210037419792028,1727022441 +2632656,1066353,"eatout",2,false,2,22688,22766,107938902,"home",,,false,37,1,863511221,"DRIVEALONE",-0.7301658119164749,1727022442 +2632656,1066353,"work",1,true,1,22676,22688,107938935,"work",,,false,6,0,863511481,"DRIVEALONE",0.04287730952963362,1727022961 +2632656,1066353,"work",1,false,1,22688,22676,107938935,"home",,,false,20,0,863511485,"DRIVEALONE",0.04281092148226895,1727022969 +2632657,1066353,"school",1,true,1,22694,22688,107938968,"school",,,false,9,0,863511745,"SCHOOLBUS",-1.3476633093405816,1727023489 +2632657,1066353,"school",1,false,2,22688,22694,107938968,"eatout",-23.29946944115026,,false,26,0,863511749,"SHARED3",-9.70321875140174,1727023497 +2632657,1066353,"school",2,false,2,22688,22688,107938968,"home",,,false,26,0,863511750,"SHARED2",-8.514611350339074,1727023498 +2632658,1066353,"escort",1,true,1,22689,22688,107938987,"escort",,,false,32,0,863511897,"WALK",0,1727023793 +2632658,1066353,"escort",1,false,1,22688,22689,107938987,"home",,,false,33,0,863511901,"WALK",0,1727023801 +2632658,1066353,"escort",1,true,1,22694,22688,107938988,"escort",,,false,11,0,863511905,"DRIVEALONE",0.14516010385695438,1727023809 +2632658,1066353,"escort",1,false,1,22688,22694,107938988,"home",,,false,12,0,863511909,"SHARED3",0.13787110648003725,1727023817 +2632659,1066353,"escort",1,true,1,22694,22688,107939028,"escort",,,false,8,0,863512225,"SHARED2",0.14516010385695438,1727024449 +2632659,1066353,"escort",1,false,1,22688,22694,107939028,"home",,,false,8,0,863512229,"SHARED3",0.13787110648003725,1727024457 +2632738,1066386,"school",1,true,2,22688,22688,107942289,"eatout",10.532297646277984,,false,11,0,863538313,"SHARED3",0.5546717586355614,1727076625 +2632738,1066386,"school",2,true,2,22716,22688,107942289,"school",,,false,11,0,863538314,"SHARED2",0.04685598325940043,1727076626 +2632738,1066386,"school",1,false,2,22685,22716,107942289,"escort",12.187082550220447,,false,26,0,863538317,"SHARED3",0.0461601223803947,1727076633 +2632738,1066386,"school",2,false,2,22688,22685,107942289,"home",,,false,27,0,863538318,"SHARED3",0.49312008693289416,1727076634 +2640879,1069967,"social",1,true,2,22688,22676,108276060,"social",11.37473397796374,,false,31,0,866208481,"SHARED2",0.006004185805916569,1732416961 +2640879,1069967,"social",2,true,2,22688,22688,108276060,"social",,,false,32,0,866208482,"SHARED2",0.07253109075082566,1732416962 +2640879,1069967,"social",1,false,1,22676,22688,108276060,"home",,,false,38,0,866208485,"SHARED2",0.006188670261483639,1732416969 +2640879,1069967,"work",1,true,1,22688,22676,108276078,"work",,,false,12,0,866208625,"SHARED2",0.6840765480327687,1732417249 +2640879,1069967,"work",1,false,1,22676,22688,108276078,"home",,,false,19,0,866208629,"DRIVEALONE",0.6845432142839015,1732417257 +2640879,1069967,"work",1,true,1,22688,22676,108276079,"work",,,false,11,0,866208633,"DRIVEALONE",0.060574367289556945,1732417265 +2640879,1069967,"work",1,false,1,22676,22688,108276079,"home",,,false,12,0,866208637,"DRIVEALONE",0.061074290505893525,1732417273 +2645904,1072088,"escort",1,true,1,22694,22711,108482073,"escort",,,false,28,0,867856585,"DRIVEALONE",0.10721753220207553,1735713169 +2645904,1072088,"escort",1,false,1,22711,22694,108482073,"home",,,false,30,0,867856589,"SHARED2",0.10722521021183494,1735713177 +2645905,1072088,"othdiscr",1,true,2,22766,22711,108482079,"parking",,,false,27,1,867856633,"SHARED2",-1.1455545722211293,1735713265 +2645905,1072088,"othdiscr",2,true,2,22766,22766,108482079,"othdiscr",,,true,27,1,867856633,"SHARED2",3.6111900800276358,1735713266 +2645905,1072088,"othdiscr",1,false,2,22766,22766,108482079,"parking",,,true,35,1,867856637,"WALK",3.6112032457334435,1735713273 +2645905,1072088,"othdiscr",2,false,2,22711,22766,108482079,"home",,,false,35,1,867856637,"SHARED2",-0.9957395363664312,1735713274 +2645905,1072088,"shopping",1,true,3,22711,22711,108482138,"othmaint",9.775720423403623,,false,22,0,867857105,"WALK",0.1085924278937159,1735714209 +2645905,1072088,"shopping",2,true,3,22711,22711,108482138,"social",10.488858934392573,,false,23,0,867857106,"WALK",0.1085924278937159,1735714210 +2645905,1072088,"shopping",3,true,3,22711,22711,108482138,"shopping",,,false,24,0,867857107,"WALK",0.1085924278937159,1735714211 +2645905,1072088,"shopping",1,false,1,22711,22711,108482138,"home",,,false,24,0,867857109,"WALK",0.1085924278937159,1735714217 +2645907,1072088,"school",1,true,1,22716,22711,108482218,"school",,,false,12,0,867857745,"DRIVEALONE",-0.20641630035108205,1735715489 +2645907,1072088,"school",1,false,1,22711,22716,108482218,"home",,,false,26,0,867857749,"DRIVEALONE",-0.20450702798248435,1735715497 +2671332,1083128,"shopping",1,true,1,22650,22637,109524645,"shopping",,,false,29,0,876197161,"DRIVEALONE",-0.04893274687468395,1752394321 +2671332,1083128,"shopping",1,false,2,22713,22650,109524645,"eatout",8.732598620243117,,false,30,0,876197165,"DRIVEALONE",-0.564278349048812,1752394329 +2671332,1083128,"shopping",2,false,2,22637,22713,109524645,"home",,,false,30,0,876197166,"DRIVEALONE",-0.6753239045055804,1752394330 +2853513,1152948,"shopping",1,true,1,22781,22770,116994066,"shopping",,,false,13,0,935952529,"WALK",-0.15286567920809457,1871905057 +2853513,1152948,"shopping",1,false,1,22770,22781,116994066,"home",,,false,16,0,935952533,"WALK",-0.15286567920809457,1871905065 +2853513,1152948,"shopping",1,true,1,22800,22770,116994067,"shopping",,,false,22,0,935952537,"WALK",-0.6260852483044879,1871905073 +2853513,1152948,"shopping",1,false,2,22807,22800,116994067,"othmaint",13.689405015818817,,false,23,0,935952541,"WALK",-0.5662778537062289,1871905081 +2853513,1152948,"shopping",2,false,2,22770,22807,116994067,"home",,,false,23,0,935952542,"WALK",2.1564636451535324,1871905082 +2853513,1152948,"shopping",1,true,1,22798,22770,116994068,"shopping",,,false,25,0,935952545,"WALK",0.3629456060638815,1871905089 +2853513,1152948,"shopping",1,false,1,22770,22798,116994068,"home",,,false,32,0,935952549,"WALK",0.3629456060638815,1871905097 +2856204,1154357,"escort",1,true,1,22767,22815,117104373,"escort",,,false,11,0,936834985,"WALK",1.572441903227518,1873669969 +2856204,1154357,"escort",1,false,1,22815,22767,117104373,"home",,,false,11,0,936834989,"WALK",1.572441903227518,1873669977 +2856204,1154357,"othdiscr",1,true,1,22795,22815,117104389,"othdiscr",,,false,11,0,936835113,"WALK",-1.136157391902266,1873670225 +2856204,1154357,"othdiscr",1,false,1,22815,22795,117104389,"home",,,false,15,0,936835117,"WALK",-1.136157391902266,1873670233 +2856204,1154357,"univ",1,true,3,22767,22815,117104395,"work",13.498034431649993,,false,21,0,936835161,"WALK",5.028511196364142,1873670321 +2856204,1154357,"univ",2,true,3,22809,22767,117104395,"univ",10.775599452844196,22809,false,22,0,936835162,"WALK_LOC",2.7954607582586246,1873670322 +2856204,1154357,"univ",3,true,3,22809,22809,117104395,"univ",,,false,24,0,936835163,"WALK",3.0008457318923365,1873670323 +2856204,1154357,"univ",1,false,4,22767,22809,117104395,"othmaint",11.97899358246422,,false,42,0,936835165,"WALK",2.7869485143816908,1873670329 +2856204,1154357,"univ",2,false,4,22764,22767,117104395,"univ",14.341759600913552,,false,42,0,936835166,"WALK",5.487620370472843,1873670330 +2856204,1154357,"univ",3,false,4,22770,22764,117104395,"othdiscr",12.599191957343043,,false,44,0,936835167,"WALK",2.6096929225906433,1873670331 +2856204,1154357,"univ",4,false,4,22815,22770,117104395,"home",,,false,44,0,936835168,"WALK_LOC",3.5917238491796746,1873670332 +2856660,1154635,"eatout",1,true,1,22810,22815,117123066,"eatout",,,false,32,0,936984529,"WALK",-0.29168228455903983,1873969057 +2856660,1154635,"eatout",1,false,1,22815,22810,117123066,"home",,,false,33,0,936984533,"WALK",-0.29168228455903983,1873969065 +2856660,1154635,"univ",1,true,1,22764,22815,117123091,"univ",,,false,17,0,936984729,"WALK",1.8818869666378932,1873969457 +2856660,1154635,"univ",1,false,4,22766,22764,117123091,"univ",9.92705808981923,22766,false,20,0,936984733,"WALK",2.332439730730206,1873969465 +2856660,1154635,"univ",2,false,4,22767,22766,117123091,"eatout",11.698000750719451,,false,20,0,936984734,"WALK",2.0354974925066145,1873969466 +2856660,1154635,"univ",3,false,4,22764,22767,117123091,"univ",14.467091292345321,,false,26,0,936984735,"WALK",5.113556461480974,1873969467 +2856660,1154635,"univ",4,false,4,22815,22764,117123091,"home",,,false,30,0,936984736,"WALK",1.8818676173433673,1873969468 +2856661,1154635,"univ",1,true,2,22767,22815,117123132,"eatout",24.13568322189892,,false,9,0,936985057,"WALK",5.024093072052891,1873970113 +2856661,1154635,"univ",2,true,2,22809,22767,117123132,"univ",,,false,10,0,936985058,"WALK",2.7951650320100074,1873970114 +2856661,1154635,"univ",1,false,4,22809,22809,117123132,"univ",22.10560655915902,22809,false,24,0,936985061,"WALK",3.000453614136991,1873970121 +2856661,1154635,"univ",2,false,4,22809,22809,117123132,"univ",21.992495219718602,22809,false,24,0,936985062,"WALK",3.000453614136991,1873970122 +2856661,1154635,"univ",3,false,4,22807,22809,117123132,"work",26.50952124631712,,false,25,0,936985063,"WALK",3.007444572576448,1873970123 +2856661,1154635,"univ",4,false,4,22815,22807,117123132,"home",,,false,31,0,936985064,"WALK",5.1535448290063615,1873970124 +2861950,1156849,"shopping",1,true,1,22800,22801,117339969,"shopping",,,false,21,0,938719753,"WALK",-0.49881710844895727,1877439505 +2861950,1156849,"shopping",1,false,1,22801,22800,117339969,"home",,,false,30,0,938719757,"WALK",-0.49881710844895727,1877439513 +2861950,1156849,"univ",1,true,1,22809,22801,117339981,"univ",,,false,13,0,938719849,"WALK",2.7089529835300503,1877439697 +2861950,1156849,"univ",1,false,2,22766,22809,117339981,"univ",22.262579096450402,22766,false,20,0,938719853,"WALK_LOC",2.560406996243793,1877439705 +2861950,1156849,"univ",2,false,2,22801,22766,117339981,"home",,,false,21,0,938719854,"WALK_LOC",2.3733339088312397,1877439706 +2861951,1156849,"univ",1,true,1,22809,22801,117340022,"univ",,,false,11,0,938720177,"WALK",-0.656569218265208,1877440353 +2861951,1156849,"univ",1,false,3,22767,22809,117340022,"othdiscr",14.308117668699108,,false,12,0,938720181,"WALK",-0.50518420043921,1877440361 +2861951,1156849,"univ",2,false,3,22767,22767,117340022,"shopping",17.816995526914052,,false,12,0,938720182,"WALK",2.62825193059268,1877440362 +2861951,1156849,"univ",3,false,3,22801,22767,117340022,"home",,,false,13,0,938720183,"WALK",1.890862363486975,1877440363 +2861952,1156849,"univ",1,true,1,22809,22801,117340063,"univ",,,false,7,0,938720505,"WALK",-0.656569218265208,1877441009 +2861952,1156849,"univ",1,false,1,22801,22809,117340063,"home",,,false,11,0,938720509,"WALK",-0.656569218265208,1877441017 +2861952,1156849,"univ",1,true,1,22809,22801,117340064,"univ",,,false,30,0,938720513,"WALK",-0.656569218265208,1877441025 +2861952,1156849,"univ",1,false,1,22801,22809,117340064,"home",,,false,32,0,938720517,"WALK",-0.656569218265208,1877441033 +2862055,1156884,"univ",1,true,3,22767,22804,117344286,"eatout",12.979710329497422,,false,19,0,938754289,"WALK",1.8938019500606744,1877508577 +2862055,1156884,"univ",2,true,3,22767,22767,117344286,"work",15.06828301301832,,false,20,0,938754290,"WALK",2.62825193059268,1877508578 +2862055,1156884,"univ",3,true,3,22809,22767,117344286,"univ",,,false,22,0,938754291,"WALK",-0.50518420043921,1877508579 +2862055,1156884,"univ",1,false,1,22804,22809,117344286,"home",,,false,22,0,938754293,"WALK",-0.44242952311569,1877508585 +2862056,1156884,"univ",1,true,1,22809,22804,117344327,"univ",,,false,12,0,938754617,"WALK",2.816810400707295,1877509233 +2862056,1156884,"univ",1,false,1,22804,22809,117344327,"home",,,false,27,0,938754621,"WALK",2.818487568540833,1877509241 +2862057,1156884,"univ",1,true,1,22809,22804,117344368,"univ",,,false,9,0,938754945,"WALK",2.8149418226097684,1877509889 +2862057,1156884,"univ",1,false,4,22767,22809,117344368,"othdiscr",14.092132672154193,,false,29,0,938754949,"WALK",2.8115136852150613,1877509897 +2862057,1156884,"univ",2,false,4,22809,22767,117344368,"univ",15.082414498585248,22809,false,29,0,938754950,"WALK_LOC",5.421713617528955,1877509898 +2862057,1156884,"univ",3,false,4,22809,22809,117344368,"univ",11.07119990210677,22809,false,29,0,938754951,"WALK",3.0039396426418254,1877509899 +2862057,1156884,"univ",4,false,4,22804,22809,117344368,"home",,,false,40,0,938754952,"WALK",2.7990874824379435,1877509900 +2863920,1157823,"othmaint",1,true,2,22795,22812,117420748,"parking",,,false,7,1,939365985,"DRIVEALONE",-0.09902637407657627,1878731969 +2863920,1157823,"othmaint",2,true,2,22806,22795,117420748,"othmaint",,,true,7,1,939365985,"WALK",3.92788746633635,1878731970 +2863920,1157823,"othmaint",1,false,7,22767,22806,117420748,"eatout",11.485301584384368,,true,8,1,939365989,"WALK",4.150955713093153,1878731977 +2863920,1157823,"othmaint",2,false,7,22795,22767,117420748,"parking",,,true,8,1,939365990,"WALK",4.44330039201803,1878731978 +2863920,1157823,"othmaint",3,false,7,22738,22795,117420748,"shopping",11.358833245442405,,false,8,1,939365990,"DRIVEALONE",-0.6103921916626766,1878731979 +2863920,1157823,"othmaint",4,false,7,22795,22738,117420748,"parking",,,false,9,1,939365991,"DRIVEALONE",-0.39605030270985575,1878731980 +2863920,1157823,"othmaint",5,false,7,22767,22795,117420748,"eatout",11.192175326364463,,true,9,1,939365991,"WALK",2.952615589445755,1878731981 +2863920,1157823,"othmaint",6,false,7,22795,22767,117420748,"parking",,,true,9,1,939365992,"WALK",4.44330039201803,1878731982 +2863920,1157823,"othmaint",7,false,7,22812,22795,117420748,"home",,,false,9,1,939365992,"DRIVEALONE",-0.11948535915923522,1878731983 +2863920,1157823,"univ",1,true,1,22766,22812,117420751,"univ",,,false,13,0,939366009,"WALK",-1.5030831477846986,1878732017 +2863920,1157823,"univ",1,false,1,22812,22766,117420751,"home",,,false,32,0,939366013,"WALK",-1.5030834911074449,1878732025 +2863921,1157823,"univ",1,true,1,22809,22812,117420792,"univ",,,false,14,0,939366337,"WALK",-0.92894689390927,1878732673 +2863921,1157823,"univ",1,false,1,22812,22809,117420792,"home",,,false,15,0,939366341,"WALK",-0.92894689390927,1878732681 +2863921,1157823,"univ",1,true,1,22809,22812,117420793,"univ",,,false,16,0,939366345,"WALK_LOC",2.481692099564644,1878732689 +2863921,1157823,"univ",1,false,3,22764,22809,117420793,"univ",9.981605111059457,,false,24,0,939366349,"WALK",2.459602060048778,1878732697 +2863921,1157823,"univ",2,false,3,22764,22764,117420793,"univ",9.84928711859661,,false,24,0,939366350,"WALK",3.003985864868469,1878732698 +2863921,1157823,"univ",3,false,3,22812,22764,117420793,"home",,,false,26,0,939366351,"WALK",2.316202097516391,1878732699 +2863922,1157823,"univ",1,true,1,22764,22812,117420833,"univ",,,false,16,0,939366665,"WALK",-1.1568382386079819,1878733329 +2863922,1157823,"univ",1,false,1,22812,22764,117420833,"home",,,false,24,0,939366669,"WALK",-1.1568382386079819,1878733337 +2866914,1159236,"shopping",1,true,2,22738,22797,117543493,"shopping",9.753195503246463,,false,20,1,940347945,"SHARED2",-0.44959528911187474,1880695889 +2866914,1159236,"shopping",2,true,2,22738,22738,117543493,"shopping",,,false,21,1,940347946,"SHARED2",-0.41569822407021684,1880695890 +2866914,1159236,"shopping",1,false,2,22796,22738,117543493,"parking",,,false,21,1,940347949,"SHARED2",-0.618932986161138,1880695897 +2866914,1159236,"shopping",2,false,2,22796,22796,117543493,"parking",,,true,21,1,940347949,"WALK",3.612657585008612,1880695898 +2866914,1159236,"othdiscr",1,true,1,22733,22797,117543499,"othdiscr",,,false,7,0,940347993,"TNC_SHARED",-1.2334449201087534,1880695985 +2866914,1159236,"othdiscr",1,false,1,22797,22733,117543499,"home",,,false,9,0,940347997,"SHARED3",-1.5864778947158276,1880695993 +2866914,1159236,"work",1,true,2,22766,22797,117543513,"parking",,,false,12,1,940348105,"DRIVEALONE",-0.23277800252868205,1880696209 +2866914,1159236,"work",2,true,2,22766,22766,117543513,"work",,,true,12,1,940348105,"WALK",2.014924960389029,1880696210 +2866914,1159236,"work",1,false,1,22797,22766,117543513,"home",,,true,18,1,940348109,"WALK",0.7326263231237407,1880696217 +2866914,1159236,"work",1,true,2,22766,22797,117543514,"parking",,,false,25,1,940348113,"DRIVEALONE",-0.2126150341505889,1880696225 +2866914,1159236,"work",2,true,2,22766,22766,117543514,"work",,,true,25,1,940348113,"WALK",2.0149069444380916,1880696226 +2866914,1159236,"work",1,false,1,22797,22766,117543514,"home",,,true,27,1,940348117,"WALK_LOC",0.7203611640018683,1880696233 +2866915,1159236,"escort",1,true,1,22738,22797,117543524,"escort",,,false,10,1,940348193,"DRIVEALONE",-0.1859277636541118,1880696385 +2866915,1159236,"escort",1,false,2,22796,22738,117543524,"parking",,,false,10,1,940348197,"DRIVEALONE",-0.3618327302148594,1880696393 +2866915,1159236,"escort",2,false,2,22796,22796,117543524,"parking",,,true,10,1,940348197,"WALK",1.067154699334263,1880696394 +2866915,1159236,"work",1,true,1,22801,22797,117543554,"work",,,false,14,0,940348433,"WALK",0.19014379979819185,1880696865 +2866915,1159236,"work",1,false,4,22770,22801,117543554,"shopping",11.568986593078103,,false,24,0,940348437,"WALK",0.4209941097322794,1880696873 +2866915,1159236,"work",2,false,4,22771,22770,117543554,"eatout",13.008322605925745,,false,25,0,940348438,"WALK",0.6865956537757166,1880696874 +2866915,1159236,"work",3,false,4,22767,22771,117543554,"shopping",12.58778480416507,,false,25,0,940348439,"WALK",0.7791172141286921,1880696875 +2866915,1159236,"work",4,false,4,22797,22767,117543554,"home",,,false,26,0,940348440,"WALK",0.4989418541157284,1880696876 +2870656,1160939,"univ",1,true,1,22764,22740,117696927,"univ",,,false,9,0,941575417,"WALK",-0.4952620689018146,1883150833 +2870656,1160939,"univ",1,false,1,22740,22764,117696927,"home",,,false,20,0,941575421,"SHARED2",-0.7088657694023863,1883150841 +2874269,1162627,"eatout",1,true,1,22771,22758,117845035,"eatout",,,false,19,0,942760281,"WALK",0.8892845196071101,1885520561 +2874269,1162627,"eatout",1,false,1,22758,22771,117845035,"home",,,false,21,0,942760285,"WALK",0.8892845196071101,1885520569 +2874269,1162627,"univ",1,true,1,22766,22758,117845060,"univ",,,false,33,0,942760481,"WALK",-0.9851903695198061,1885520961 +2874269,1162627,"univ",1,false,1,22758,22766,117845060,"home",,,false,48,0,942760485,"WALK",-0.9851903695198061,1885520969 +2874270,1162627,"univ",1,true,1,22809,22758,117845101,"univ",,,false,13,0,942760809,"WALK",2.448568395465578,1885521617 +2874270,1162627,"univ",1,false,3,22766,22809,117845101,"univ",10.064918042210902,22766,false,29,0,942760813,"WALK",2.5591208432062635,1885521625 +2874270,1162627,"univ",2,false,3,22760,22766,117845101,"eatout",12.726627067763937,,false,29,0,942760814,"WALK",2.6006338904068236,1885521626 +2874270,1162627,"univ",3,false,3,22758,22760,117845101,"home",,,false,31,0,942760815,"WALK",5.854019928994619,1885521627 +2874271,1162627,"univ",1,true,1,22766,22758,117845142,"univ",,,false,13,0,942761137,"WALK",-0.9851903695198061,1885522273 +2874271,1162627,"univ",1,false,3,22807,22766,117845142,"social",12.701701490919964,,false,25,0,942761141,"WALK",-0.7025935206727638,1885522281 +2874271,1162627,"univ",2,false,3,22767,22807,117845142,"eatout",18.97072944477215,,false,25,0,942761142,"WALK",2.329516762852266,1885522282 +2874271,1162627,"univ",3,false,3,22758,22767,117845142,"home",,,false,26,0,942761143,"WALK",2.1084560857351926,1885522283 +4724316,1944022,"univ",1,true,2,22763,22765,193696987,"escort",12.812689062193739,,false,26,0,1549575897,"WALK",2.1351670996963623,3099151793 +4724316,1944022,"univ",2,true,2,22766,22763,193696987,"univ",,,false,27,0,1549575898,"WALK",-0.8559965323806581,3099151794 +4724316,1944022,"univ",1,false,2,22767,22766,193696987,"eatout",13.154682962658466,,false,29,0,1549575901,"WALK",-0.9673991559282129,3099151801 +4724316,1944022,"univ",2,false,2,22765,22767,193696987,"home",,,false,29,0,1549575902,"WALK",2.1708672114306493,3099151802 +4724701,1944407,"univ",1,true,1,22809,22808,193712772,"univ",,,false,12,0,1549702177,"BIKE",-0.2552725353403566,3099404353 +4724701,1944407,"univ",1,false,2,22766,22809,193712772,"univ",11.3195538505166,22766,false,37,0,1549702181,"BIKE",-0.7042893633777787,3099404361 +4724701,1944407,"univ",2,false,2,22808,22766,193712772,"home",,,false,37,0,1549702182,"BIKE",-0.6429800462056683,3099404362 +4724720,1944426,"work",1,true,1,22738,22806,193713559,"work",,,false,10,0,1549708473,"WALK",3.424476741640782,3099416945 +4724720,1944426,"work",1,false,1,22806,22738,193713559,"home",,,false,25,0,1549708477,"WALK",3.421200065013991,3099416953 +4727094,1946800,"univ",1,true,2,22796,22808,193810885,"parking",,,false,20,1,1550487081,"SHARED2",-0.325497193352596,3100974161 +4727094,1946800,"univ",2,true,2,22809,22796,193810885,"univ",,,true,20,1,1550487081,"WALK_LOC",2.1755216717496055,3100974162 +4727094,1946800,"univ",1,false,1,22808,22809,193810885,"home",,,true,27,1,1550487085,"WALK",2.117177438190699,3100974169 +4728027,1947733,"univ",1,true,1,22764,22806,193849138,"univ",,,false,10,0,1550793105,"WALK",-1.1092692699446687,3101586209 +4728027,1947733,"univ",1,false,2,22767,22764,193849138,"escort",13.485546584318579,,false,27,0,1550793109,"WALK",-0.5148347167335139,3101586217 +4728027,1947733,"univ",2,false,2,22806,22767,193849138,"home",,,false,28,0,1550793110,"WALK",1.9818127360573472,3101586218 +4740690,1970879,"univ",1,true,1,22764,22745,194368321,"univ",,,false,11,0,1554946569,"WALK",2.576602218700462,3109893137 +4740690,1970879,"univ",1,false,3,22768,22764,194368321,"social",26.124396751214636,,false,31,0,1554946573,"WALK",2.8253929561140825,3109893145 +4740690,1970879,"univ",2,false,3,22760,22768,194368321,"othdiscr",30.225849882595227,,false,32,0,1554946574,"WALK",4.979971583696819,3109893146 +4740690,1970879,"univ",3,false,3,22745,22760,194368321,"home",,,false,32,0,1554946575,"WALK",5.801965338203763,3109893147 From 2d181b2a9ada7d22d7ed8e8f7765360e9150d2ba Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Fri, 10 Apr 2026 11:30:08 +1000 Subject: [PATCH 200/298] Deterministic randoms when adding/removing tours for trip scheduling choice --- .../test/test_trip_scheduling_consistency.py | 98 +++++++++++++++++++ .../abm/models/trip_scheduling_choice.py | 51 +++++++--- .../test_misc/test_trip_scheduling_choice.py | 12 ++- 3 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 activitysim/abm/models/test/test_trip_scheduling_consistency.py diff --git a/activitysim/abm/models/test/test_trip_scheduling_consistency.py b/activitysim/abm/models/test/test_trip_scheduling_consistency.py new file mode 100644 index 0000000000..b773c6a06a --- /dev/null +++ b/activitysim/abm/models/test/test_trip_scheduling_consistency.py @@ -0,0 +1,98 @@ +""" +Tests confirming the SCHEDULE_ID in run_trip_scheduling_choice is not chunk-sensitive. +""" +from __future__ import annotations + +import numpy as np +import pandas as pd + +from activitysim.abm.models import trip_scheduling_choice as tsc + + +def _make_two_way_stop_tours(tour_ids, duration=4): + """Return a minimal tours DataFrame where every tour has stops on both legs.""" + n = len(tour_ids) + return pd.DataFrame( + { + tsc.TOUR_DURATION_COLUMN: [duration] * n, + tsc.NUM_OB_STOPS: [1] * n, + tsc.NUM_IB_STOPS: [1] * n, + tsc.HAS_OB_STOPS: [True] * n, + tsc.HAS_IB_STOPS: [True] * n, + }, + index=pd.Index(tour_ids, name="tour_id"), + ) + + +def test_schedule_ids_shift_with_different_co_chunked_tours(): + """ + Confirm that SCHEDULE_IDs assigned to a given tour's alternatives do not change + depending on which other tours are present in the same chunk. + + generate_schedule_alternatives numbers alternatives sequentially starting at 1 + across the full set of input tours. A tour processed alongside tours with lower + IDs will therefore have its alternatives numbered beginning at a higher offset + than if it were processed alone. + """ + tours_both = _make_two_way_stop_tours([0, 1], duration=4) + tours_solo = _make_two_way_stop_tours([1], duration=4) + + alts_both = tsc.generate_schedule_alternatives(tours_both) + alts_solo = tsc.generate_schedule_alternatives(tours_solo) + + ids_with_tour0 = alts_both.loc[alts_both.index == 1, tsc.SCHEDULE_ID].values + ids_without_tour0 = alts_solo.loc[alts_solo.index == 1, tsc.SCHEDULE_ID].values + + # Same number of schedule alternatives for tour 1 regardless of co-tours + assert len(ids_with_tour0) == len(ids_without_tour0), ( + "Tour 1 should have the same number of alternatives whether processed " + "alone or together with tour 0." + ) + + # and the IDs themselves don't differ + assert np.array_equal( + ids_with_tour0, ids_without_tour0 + ), "SCHEDULE_IDs for tour 1 changed when tour 0 was added to the chunk." + + +def test_shifted_schedule_ids_produce_same_gumbel_draws(): + """ + Confirm that the SCHEDULE_ID shift documented in + test_schedule_ids_shift_with_different_co_chunked_tours translates directly + into different Gumbel error terms under the AltsContext indexing scheme. + + add_ev1_random generates a dense array of random numbers with length + alt_info.n_alts_to_cover_max_id, then selects per-alternative values via + np.take_along_axis indexed by the SCHEDULE_IDs. When those IDs change, the + selected values change too — meaning a tour can receive different error terms + (and make a different choice) solely because of who else is in its chunk. + """ + tours_both = _make_two_way_stop_tours([0, 1], duration=4) + tours_solo = _make_two_way_stop_tours([1], duration=4) + + alts_both = tsc.generate_schedule_alternatives(tours_both) + alts_solo = tsc.generate_schedule_alternatives(tours_solo) + + ids_with_tour0 = alts_both.loc[alts_both.index == 1, tsc.SCHEDULE_ID].values + ids_without_tour0 = alts_solo.loc[alts_solo.index == 1, tsc.SCHEDULE_ID].values + + # Reproduce the dense random draw that add_ev1_random would make for tour 1. + # Use a fixed seed to make the test deterministic. + max_alt_id_both = int(alts_both[tsc.SCHEDULE_ID].max()) + rng = np.random.RandomState(42) + # n_alts_to_cover_max_id = max_alt_id + 1 (see AltsContext.__post_init__) + rands_dense = rng.gumbel(size=max_alt_id_both + 1) + + gumbel_with_tour0 = rands_dense[ids_with_tour0] + + # For the solo run, the dense array is shorter; regenerate from the same seed + max_alt_id_solo = int(alts_solo[tsc.SCHEDULE_ID].max()) + rng2 = np.random.RandomState(42) + rands_dense_solo = rng2.gumbel(size=max_alt_id_solo + 1) + + gumbel_without_tour0 = rands_dense_solo[ids_without_tour0] + + assert np.array_equal(gumbel_with_tour0, gumbel_without_tour0), ( + "Gumbel draws for tour 1's alternatives should not differ when SCHEDULE_IDs " + "are shifted by the presence of tour 0." + ) diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index 3eb695feb5..ed316f2279 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -84,7 +84,12 @@ def generate_schedule_alternatives(tours): schedules = pd.concat([no_stops, one_way, two_way], sort=True) schedules[SCHEDULE_ID] = np.arange(1, schedules.shape[0] + 1) # this sort is necessary to keep single process and multiprocess results the same! + # sort_values works here because the index is named "tour_id". schedules.sort_values(by=["tour_id", SCHEDULE_ID], inplace=True) + # Promote the named tour_id index to a plain column, then re-index by SCHEDULE_ID + # (drop=False keeps SCHEDULE_ID accessible as a column too). Callers can then + # identify each alternative's tour via the "tour_id" column rather than the index. + schedules = schedules.reset_index().set_index(SCHEDULE_ID, drop=False) return schedules @@ -270,6 +275,19 @@ def run_trip_scheduling_choice( indirect_tours = tours.loc[tours[HAS_OB_STOPS] | tours[HAS_IB_STOPS]] if len(indirect_tours) > 0: + # Generate all schedule alternatives upfront over the full indirect-tour set so + # that SCHEDULE_IDs are globally stable. If we generated per-chunk instead, a + # tour's alternatives would receive different IDs depending on the other tours in + # its chunk, which would cause add_ev1_random to index into a different position + # in the dense Gumbel draw array and produce different (chunk-sensitive) error + # terms for the same tour. + all_schedules = generate_schedule_alternatives(indirect_tours) + # Build the AltsContext once from the global ID range so every chunk uses the + # same dense-random-draw width, giving each alternative a stable Gumbel draw. + global_alts_context = AltsContext( + all_schedules[SCHEDULE_ID].min(), all_schedules[SCHEDULE_ID].max() + ) + # Iterate through the chunks result_list = [] for ( @@ -278,14 +296,22 @@ def run_trip_scheduling_choice( chunk_trace_label, chunk_sizer, ) in chunk.adaptive_chunked_choosers(state, indirect_tours, trace_label): - # Sort the choosers and get the schedule alternatives + # Sort the choosers and filter the pre-computed alternatives to this chunk. choosers = choosers.sort_index() - schedules = generate_schedule_alternatives(choosers).sort_index() + schedules = all_schedules[ + all_schedules["tour_id"].isin(choosers.index) + ].sort_index() + + # _interaction_sample_simulate requires alternatives indexed by chooser + # (tour_id), so create a view with tour_id as the index. + schedules_for_sim = ( + schedules.reset_index(drop=True).set_index("tour_id").sort_index() + ) # preprocessing alternatives expressions.annotate_preprocessors( state, - df=schedules, + df=schedules_for_sim, locals_dict=locals_dict, skims=None, model_settings=model_settings, @@ -295,13 +321,13 @@ def run_trip_scheduling_choice( # Assuming we did the max_alt_size calculation correctly, # we should get the same sizes here. - assert choosers[NUM_ALTERNATIVES].sum() == schedules.shape[0] + assert choosers[NUM_ALTERNATIVES].sum() == schedules_for_sim.shape[0] # Run the simulation choices = _interaction_sample_simulate( state, choosers=choosers, - alternatives=schedules, + alternatives=schedules_for_sim, spec=spec, choice_column=SCHEDULE_ID, allow_zero_probs=False, @@ -315,14 +341,14 @@ def run_trip_scheduling_choice( estimator=None, chunk_sizer=chunk_sizer, compute_settings=model_settings.compute_settings, - alts_context=AltsContext( - schedules[SCHEDULE_ID].min(), schedules[SCHEDULE_ID].max() - ), + alts_context=global_alts_context, ) assert len(choices.index) == len(choosers.index) - choices = schedules[schedules[SCHEDULE_ID].isin(choices)] + # choices is a Series of chosen SCHEDULE_IDs; look them up against the + # SCHEDULE_ID-indexed schedules to retrieve the duration columns. + choices = schedules.loc[schedules[SCHEDULE_ID].isin(choices)] result_list.append(choices) @@ -337,8 +363,11 @@ def run_trip_scheduling_choice( assert len(choices.index) == len(indirect_tours.index) # The choices here are only the indirect tours, so the durations - # need to be updated on the main tour dataframe. - tours.update(choices[[MAIN_LEG_DURATION, OB_DURATION, IB_DURATION]]) + # need to be updated on the main tour dataframe. Re-index by tour_id + # (stored as a column by generate_schedule_alternatives) for alignment. + tours.update( + choices.set_index("tour_id")[[MAIN_LEG_DURATION, OB_DURATION, IB_DURATION]] + ) # Cleanup data types and drop temporary columns tours[[MAIN_LEG_DURATION, OB_DURATION, IB_DURATION]] = tours[ diff --git a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py index 6823a5b123..6acf57cc67 100644 --- a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py @@ -1,9 +1,11 @@ -import numpy as np -import pandas as pd -import pytest +from __future__ import annotations + import os from pathlib import Path +import numpy as np +import pandas as pd +import pytest from activitysim.abm.models import trip_scheduling_choice as tsc from activitysim.abm.tables.skims import skim_dict @@ -150,7 +152,9 @@ def initialize_network_los() -> bool: def test_generate_schedule_alternatives(tours): windows = tsc.generate_schedule_alternatives(tours) assert windows.shape[0] == 296 - assert windows.shape[1] == 4 + assert ( + windows.shape[1] == 5 + ) # tour_id, schedule_id, main_leg_duration, ob_duration, ib_duration output_columns = [ tsc.SCHEDULE_ID, From cfe616657b6ea4bd3319c628fee865969397e4cc Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Fri, 10 Apr 2026 11:47:52 +1000 Subject: [PATCH 201/298] PR noise --- activitysim/abm/models/parking_location_choice.py | 2 +- activitysim/abm/models/trip_destination.py | 12 ++++++------ activitysim/abm/models/util/tour_destination.py | 2 +- .../test/test_misc/test_trip_scheduling_choice.py | 8 +++----- .../core/test/test_interaction_sample_simulate.py | 2 -- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/activitysim/abm/models/parking_location_choice.py b/activitysim/abm/models/parking_location_choice.py index 07bc5bea0e..b07ec5b873 100644 --- a/activitysim/abm/models/parking_location_choice.py +++ b/activitysim/abm/models/parking_location_choice.py @@ -20,11 +20,11 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LogitComponentSettings -from activitysim.core.exceptions import DuplicateWorkflowTableError from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, drop_unused_columns +from activitysim.core.exceptions import DuplicateWorkflowTableError logger = logging.getLogger(__name__) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 9888d0606a..55b1ffc7b8 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -30,13 +30,13 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LocationComponentSettings -from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.skim_dictionary import DataFrameMatrix from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, reindex +from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError logger = logging.getLogger(__name__) @@ -1528,13 +1528,13 @@ def run_trip_destination( """ When using the trip destination model with sharrow, it is necessary - to set a value for `purpose_index_num` in the trip destination - annotate trips preprocessor. This allows for an optimized compiled + to set a value for `purpose_index_num` in the trip destination + annotate trips preprocessor. This allows for an optimized compiled lookup of the size term from the array of size terms. The value of - `purpose_index_num` should be the integer column position in the size - matrix, with usual zero-based numpy indexing semantics (i.e. the first + `purpose_index_num` should be the integer column position in the size + matrix, with usual zero-based numpy indexing semantics (i.e. the first column is zero). The preprocessor expression most likely needs to be - "size_terms.get_cols(df.purpose)" unless some unusual transform of + "size_terms.get_cols(df.purpose)" unless some unusual transform of size terms has been employed. """ diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 84f51a0982..0531a2caeb 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -12,11 +12,11 @@ from activitysim.core import ( config, estimation, - expressions, los, simulate, tracing, workflow, + expressions, ) from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.interaction_sample import interaction_sample diff --git a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py index 6acf57cc67..3f1bd6985a 100644 --- a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py @@ -1,11 +1,9 @@ -from __future__ import annotations - -import os -from pathlib import Path - import numpy as np import pandas as pd import pytest +import os +from pathlib import Path + from activitysim.abm.models import trip_scheduling_choice as tsc from activitysim.abm.tables.skims import skim_dict diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 6ab79a532d..62a40825f8 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -1,8 +1,6 @@ # ActivitySim # See full license in LICENSE.txt. -from __future__ import annotations - import numpy as np import pandas as pd import pytest From 7a22612e0c3b7c0bbd77b099cd4ddc84cbbb46df Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Fri, 10 Apr 2026 15:00:25 +1000 Subject: [PATCH 202/298] Roll back final_trips.csv for production_semcog --- activitysim/abm/models/location_choice.py | 4 +- activitysim/abm/models/trip_destination.py | 12 +- .../test/regress/final_trips.csv | 338 +++++++++--------- 3 files changed, 176 insertions(+), 178 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 6fe242a6e7..e52d9e9bfb 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -797,9 +797,7 @@ def run_location_choice( # using land use rather than size terms in case something goes 0 base -> nonzero project, double # check if that would be in dest_size_terms as a zero # use full index (including zero-size zones) to ensure stable random results - alts_context = AltsContext.from_series( - dest_size_terms.index - ) # index zone_id, not ALT_DEST_COL_NAME + alts_context = AltsContext.from_series(dest_size_terms.index) # assumes that dest_size_terms will always contain zeros for non-attractive zones, i.e. it will have the # same length as land_use diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 55b1ffc7b8..9888d0606a 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -30,13 +30,13 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LocationComponentSettings +from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.skim_dictionary import DataFrameMatrix from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, reindex -from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError logger = logging.getLogger(__name__) @@ -1528,13 +1528,13 @@ def run_trip_destination( """ When using the trip destination model with sharrow, it is necessary - to set a value for `purpose_index_num` in the trip destination - annotate trips preprocessor. This allows for an optimized compiled + to set a value for `purpose_index_num` in the trip destination + annotate trips preprocessor. This allows for an optimized compiled lookup of the size term from the array of size terms. The value of - `purpose_index_num` should be the integer column position in the size - matrix, with usual zero-based numpy indexing semantics (i.e. the first + `purpose_index_num` should be the integer column position in the size + matrix, with usual zero-based numpy indexing semantics (i.e. the first column is zero). The preprocessor expression most likely needs to be - "size_terms.get_cols(df.purpose)" unless some unusual transform of + "size_terms.get_cols(df.purpose)" unless some unusual transform of size terms has been employed. """ diff --git a/activitysim/examples/production_semcog/test/regress/final_trips.csv b/activitysim/examples/production_semcog/test/regress/final_trips.csv index 47ebf986c7..3402fa28c2 100644 --- a/activitysim/examples/production_semcog/test/regress/final_trips.csv +++ b/activitysim/examples/production_semcog/test/regress/final_trips.csv @@ -1,169 +1,169 @@ -"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","original_school_zone_id","parked_at_university","depart","tour_includes_parking","trip_id_pre_parking","trip_mode","mode_choice_logsum","trip_id" -2632656,1066353,"eatout",1,true,2,22766,22688,107938902,"parking",,,false,25,1,863511217,"DRIVEALONE",-0.8746901452708546,1727022433 -2632656,1066353,"eatout",2,true,2,22767,22766,107938902,"eatout",,,true,25,1,863511217,"WALK",6.210014031663612,1727022434 -2632656,1066353,"eatout",1,false,2,22766,22767,107938902,"parking",,,true,37,1,863511221,"WALK",6.210037419792028,1727022441 -2632656,1066353,"eatout",2,false,2,22688,22766,107938902,"home",,,false,37,1,863511221,"DRIVEALONE",-0.7301658119164749,1727022442 -2632656,1066353,"work",1,true,1,22676,22688,107938935,"work",,,false,6,0,863511481,"DRIVEALONE",0.04287730952963362,1727022961 -2632656,1066353,"work",1,false,1,22688,22676,107938935,"home",,,false,20,0,863511485,"DRIVEALONE",0.04281092148226895,1727022969 -2632657,1066353,"school",1,true,1,22694,22688,107938968,"school",,,false,9,0,863511745,"SCHOOLBUS",-1.3476633093405816,1727023489 -2632657,1066353,"school",1,false,2,22688,22694,107938968,"eatout",-23.29946944115026,,false,26,0,863511749,"SHARED3",-9.70321875140174,1727023497 -2632657,1066353,"school",2,false,2,22688,22688,107938968,"home",,,false,26,0,863511750,"SHARED2",-8.514611350339074,1727023498 -2632658,1066353,"escort",1,true,1,22689,22688,107938987,"escort",,,false,32,0,863511897,"WALK",0,1727023793 -2632658,1066353,"escort",1,false,1,22688,22689,107938987,"home",,,false,33,0,863511901,"WALK",0,1727023801 -2632658,1066353,"escort",1,true,1,22694,22688,107938988,"escort",,,false,11,0,863511905,"DRIVEALONE",0.14516010385695438,1727023809 -2632658,1066353,"escort",1,false,1,22688,22694,107938988,"home",,,false,12,0,863511909,"SHARED3",0.13787110648003725,1727023817 -2632659,1066353,"escort",1,true,1,22694,22688,107939028,"escort",,,false,8,0,863512225,"SHARED2",0.14516010385695438,1727024449 -2632659,1066353,"escort",1,false,1,22688,22694,107939028,"home",,,false,8,0,863512229,"SHARED3",0.13787110648003725,1727024457 -2632738,1066386,"school",1,true,2,22688,22688,107942289,"eatout",10.532297646277984,,false,11,0,863538313,"SHARED3",0.5546717586355614,1727076625 -2632738,1066386,"school",2,true,2,22716,22688,107942289,"school",,,false,11,0,863538314,"SHARED2",0.04685598325940043,1727076626 -2632738,1066386,"school",1,false,2,22685,22716,107942289,"escort",12.187082550220447,,false,26,0,863538317,"SHARED3",0.0461601223803947,1727076633 -2632738,1066386,"school",2,false,2,22688,22685,107942289,"home",,,false,27,0,863538318,"SHARED3",0.49312008693289416,1727076634 -2640879,1069967,"social",1,true,2,22688,22676,108276060,"social",11.37473397796374,,false,31,0,866208481,"SHARED2",0.006004185805916569,1732416961 -2640879,1069967,"social",2,true,2,22688,22688,108276060,"social",,,false,32,0,866208482,"SHARED2",0.07253109075082566,1732416962 -2640879,1069967,"social",1,false,1,22676,22688,108276060,"home",,,false,38,0,866208485,"SHARED2",0.006188670261483639,1732416969 -2640879,1069967,"work",1,true,1,22688,22676,108276078,"work",,,false,12,0,866208625,"SHARED2",0.6840765480327687,1732417249 -2640879,1069967,"work",1,false,1,22676,22688,108276078,"home",,,false,19,0,866208629,"DRIVEALONE",0.6845432142839015,1732417257 -2640879,1069967,"work",1,true,1,22688,22676,108276079,"work",,,false,11,0,866208633,"DRIVEALONE",0.060574367289556945,1732417265 -2640879,1069967,"work",1,false,1,22676,22688,108276079,"home",,,false,12,0,866208637,"DRIVEALONE",0.061074290505893525,1732417273 -2645904,1072088,"escort",1,true,1,22694,22711,108482073,"escort",,,false,28,0,867856585,"DRIVEALONE",0.10721753220207553,1735713169 -2645904,1072088,"escort",1,false,1,22711,22694,108482073,"home",,,false,30,0,867856589,"SHARED2",0.10722521021183494,1735713177 -2645905,1072088,"othdiscr",1,true,2,22766,22711,108482079,"parking",,,false,27,1,867856633,"SHARED2",-1.1455545722211293,1735713265 -2645905,1072088,"othdiscr",2,true,2,22766,22766,108482079,"othdiscr",,,true,27,1,867856633,"SHARED2",3.6111900800276358,1735713266 -2645905,1072088,"othdiscr",1,false,2,22766,22766,108482079,"parking",,,true,35,1,867856637,"WALK",3.6112032457334435,1735713273 -2645905,1072088,"othdiscr",2,false,2,22711,22766,108482079,"home",,,false,35,1,867856637,"SHARED2",-0.9957395363664312,1735713274 -2645905,1072088,"shopping",1,true,3,22711,22711,108482138,"othmaint",9.775720423403623,,false,22,0,867857105,"WALK",0.1085924278937159,1735714209 -2645905,1072088,"shopping",2,true,3,22711,22711,108482138,"social",10.488858934392573,,false,23,0,867857106,"WALK",0.1085924278937159,1735714210 -2645905,1072088,"shopping",3,true,3,22711,22711,108482138,"shopping",,,false,24,0,867857107,"WALK",0.1085924278937159,1735714211 -2645905,1072088,"shopping",1,false,1,22711,22711,108482138,"home",,,false,24,0,867857109,"WALK",0.1085924278937159,1735714217 -2645907,1072088,"school",1,true,1,22716,22711,108482218,"school",,,false,12,0,867857745,"DRIVEALONE",-0.20641630035108205,1735715489 -2645907,1072088,"school",1,false,1,22711,22716,108482218,"home",,,false,26,0,867857749,"DRIVEALONE",-0.20450702798248435,1735715497 -2671332,1083128,"shopping",1,true,1,22650,22637,109524645,"shopping",,,false,29,0,876197161,"DRIVEALONE",-0.04893274687468395,1752394321 -2671332,1083128,"shopping",1,false,2,22713,22650,109524645,"eatout",8.732598620243117,,false,30,0,876197165,"DRIVEALONE",-0.564278349048812,1752394329 -2671332,1083128,"shopping",2,false,2,22637,22713,109524645,"home",,,false,30,0,876197166,"DRIVEALONE",-0.6753239045055804,1752394330 -2853513,1152948,"shopping",1,true,1,22781,22770,116994066,"shopping",,,false,13,0,935952529,"WALK",-0.15286567920809457,1871905057 -2853513,1152948,"shopping",1,false,1,22770,22781,116994066,"home",,,false,16,0,935952533,"WALK",-0.15286567920809457,1871905065 -2853513,1152948,"shopping",1,true,1,22800,22770,116994067,"shopping",,,false,22,0,935952537,"WALK",-0.6260852483044879,1871905073 -2853513,1152948,"shopping",1,false,2,22807,22800,116994067,"othmaint",13.689405015818817,,false,23,0,935952541,"WALK",-0.5662778537062289,1871905081 -2853513,1152948,"shopping",2,false,2,22770,22807,116994067,"home",,,false,23,0,935952542,"WALK",2.1564636451535324,1871905082 -2853513,1152948,"shopping",1,true,1,22798,22770,116994068,"shopping",,,false,25,0,935952545,"WALK",0.3629456060638815,1871905089 -2853513,1152948,"shopping",1,false,1,22770,22798,116994068,"home",,,false,32,0,935952549,"WALK",0.3629456060638815,1871905097 -2856204,1154357,"escort",1,true,1,22767,22815,117104373,"escort",,,false,11,0,936834985,"WALK",1.572441903227518,1873669969 -2856204,1154357,"escort",1,false,1,22815,22767,117104373,"home",,,false,11,0,936834989,"WALK",1.572441903227518,1873669977 -2856204,1154357,"othdiscr",1,true,1,22795,22815,117104389,"othdiscr",,,false,11,0,936835113,"WALK",-1.136157391902266,1873670225 -2856204,1154357,"othdiscr",1,false,1,22815,22795,117104389,"home",,,false,15,0,936835117,"WALK",-1.136157391902266,1873670233 -2856204,1154357,"univ",1,true,3,22767,22815,117104395,"work",13.498034431649993,,false,21,0,936835161,"WALK",5.028511196364142,1873670321 -2856204,1154357,"univ",2,true,3,22809,22767,117104395,"univ",10.775599452844196,22809,false,22,0,936835162,"WALK_LOC",2.7954607582586246,1873670322 -2856204,1154357,"univ",3,true,3,22809,22809,117104395,"univ",,,false,24,0,936835163,"WALK",3.0008457318923365,1873670323 -2856204,1154357,"univ",1,false,4,22767,22809,117104395,"othmaint",11.97899358246422,,false,42,0,936835165,"WALK",2.7869485143816908,1873670329 -2856204,1154357,"univ",2,false,4,22764,22767,117104395,"univ",14.341759600913552,,false,42,0,936835166,"WALK",5.487620370472843,1873670330 -2856204,1154357,"univ",3,false,4,22770,22764,117104395,"othdiscr",12.599191957343043,,false,44,0,936835167,"WALK",2.6096929225906433,1873670331 -2856204,1154357,"univ",4,false,4,22815,22770,117104395,"home",,,false,44,0,936835168,"WALK_LOC",3.5917238491796746,1873670332 -2856660,1154635,"eatout",1,true,1,22810,22815,117123066,"eatout",,,false,32,0,936984529,"WALK",-0.29168228455903983,1873969057 -2856660,1154635,"eatout",1,false,1,22815,22810,117123066,"home",,,false,33,0,936984533,"WALK",-0.29168228455903983,1873969065 -2856660,1154635,"univ",1,true,1,22764,22815,117123091,"univ",,,false,17,0,936984729,"WALK",1.8818869666378932,1873969457 -2856660,1154635,"univ",1,false,4,22766,22764,117123091,"univ",9.92705808981923,22766,false,20,0,936984733,"WALK",2.332439730730206,1873969465 -2856660,1154635,"univ",2,false,4,22767,22766,117123091,"eatout",11.698000750719451,,false,20,0,936984734,"WALK",2.0354974925066145,1873969466 -2856660,1154635,"univ",3,false,4,22764,22767,117123091,"univ",14.467091292345321,,false,26,0,936984735,"WALK",5.113556461480974,1873969467 -2856660,1154635,"univ",4,false,4,22815,22764,117123091,"home",,,false,30,0,936984736,"WALK",1.8818676173433673,1873969468 -2856661,1154635,"univ",1,true,2,22767,22815,117123132,"eatout",24.13568322189892,,false,9,0,936985057,"WALK",5.024093072052891,1873970113 -2856661,1154635,"univ",2,true,2,22809,22767,117123132,"univ",,,false,10,0,936985058,"WALK",2.7951650320100074,1873970114 -2856661,1154635,"univ",1,false,4,22809,22809,117123132,"univ",22.10560655915902,22809,false,24,0,936985061,"WALK",3.000453614136991,1873970121 -2856661,1154635,"univ",2,false,4,22809,22809,117123132,"univ",21.992495219718602,22809,false,24,0,936985062,"WALK",3.000453614136991,1873970122 -2856661,1154635,"univ",3,false,4,22807,22809,117123132,"work",26.50952124631712,,false,25,0,936985063,"WALK",3.007444572576448,1873970123 -2856661,1154635,"univ",4,false,4,22815,22807,117123132,"home",,,false,31,0,936985064,"WALK",5.1535448290063615,1873970124 -2861950,1156849,"shopping",1,true,1,22800,22801,117339969,"shopping",,,false,21,0,938719753,"WALK",-0.49881710844895727,1877439505 -2861950,1156849,"shopping",1,false,1,22801,22800,117339969,"home",,,false,30,0,938719757,"WALK",-0.49881710844895727,1877439513 -2861950,1156849,"univ",1,true,1,22809,22801,117339981,"univ",,,false,13,0,938719849,"WALK",2.7089529835300503,1877439697 -2861950,1156849,"univ",1,false,2,22766,22809,117339981,"univ",22.262579096450402,22766,false,20,0,938719853,"WALK_LOC",2.560406996243793,1877439705 -2861950,1156849,"univ",2,false,2,22801,22766,117339981,"home",,,false,21,0,938719854,"WALK_LOC",2.3733339088312397,1877439706 -2861951,1156849,"univ",1,true,1,22809,22801,117340022,"univ",,,false,11,0,938720177,"WALK",-0.656569218265208,1877440353 -2861951,1156849,"univ",1,false,3,22767,22809,117340022,"othdiscr",14.308117668699108,,false,12,0,938720181,"WALK",-0.50518420043921,1877440361 -2861951,1156849,"univ",2,false,3,22767,22767,117340022,"shopping",17.816995526914052,,false,12,0,938720182,"WALK",2.62825193059268,1877440362 -2861951,1156849,"univ",3,false,3,22801,22767,117340022,"home",,,false,13,0,938720183,"WALK",1.890862363486975,1877440363 -2861952,1156849,"univ",1,true,1,22809,22801,117340063,"univ",,,false,7,0,938720505,"WALK",-0.656569218265208,1877441009 -2861952,1156849,"univ",1,false,1,22801,22809,117340063,"home",,,false,11,0,938720509,"WALK",-0.656569218265208,1877441017 -2861952,1156849,"univ",1,true,1,22809,22801,117340064,"univ",,,false,30,0,938720513,"WALK",-0.656569218265208,1877441025 -2861952,1156849,"univ",1,false,1,22801,22809,117340064,"home",,,false,32,0,938720517,"WALK",-0.656569218265208,1877441033 -2862055,1156884,"univ",1,true,3,22767,22804,117344286,"eatout",12.979710329497422,,false,19,0,938754289,"WALK",1.8938019500606744,1877508577 -2862055,1156884,"univ",2,true,3,22767,22767,117344286,"work",15.06828301301832,,false,20,0,938754290,"WALK",2.62825193059268,1877508578 -2862055,1156884,"univ",3,true,3,22809,22767,117344286,"univ",,,false,22,0,938754291,"WALK",-0.50518420043921,1877508579 -2862055,1156884,"univ",1,false,1,22804,22809,117344286,"home",,,false,22,0,938754293,"WALK",-0.44242952311569,1877508585 -2862056,1156884,"univ",1,true,1,22809,22804,117344327,"univ",,,false,12,0,938754617,"WALK",2.816810400707295,1877509233 -2862056,1156884,"univ",1,false,1,22804,22809,117344327,"home",,,false,27,0,938754621,"WALK",2.818487568540833,1877509241 -2862057,1156884,"univ",1,true,1,22809,22804,117344368,"univ",,,false,9,0,938754945,"WALK",2.8149418226097684,1877509889 -2862057,1156884,"univ",1,false,4,22767,22809,117344368,"othdiscr",14.092132672154193,,false,29,0,938754949,"WALK",2.8115136852150613,1877509897 -2862057,1156884,"univ",2,false,4,22809,22767,117344368,"univ",15.082414498585248,22809,false,29,0,938754950,"WALK_LOC",5.421713617528955,1877509898 -2862057,1156884,"univ",3,false,4,22809,22809,117344368,"univ",11.07119990210677,22809,false,29,0,938754951,"WALK",3.0039396426418254,1877509899 -2862057,1156884,"univ",4,false,4,22804,22809,117344368,"home",,,false,40,0,938754952,"WALK",2.7990874824379435,1877509900 -2863920,1157823,"othmaint",1,true,2,22795,22812,117420748,"parking",,,false,7,1,939365985,"DRIVEALONE",-0.09902637407657627,1878731969 -2863920,1157823,"othmaint",2,true,2,22806,22795,117420748,"othmaint",,,true,7,1,939365985,"WALK",3.92788746633635,1878731970 -2863920,1157823,"othmaint",1,false,7,22767,22806,117420748,"eatout",11.485301584384368,,true,8,1,939365989,"WALK",4.150955713093153,1878731977 -2863920,1157823,"othmaint",2,false,7,22795,22767,117420748,"parking",,,true,8,1,939365990,"WALK",4.44330039201803,1878731978 -2863920,1157823,"othmaint",3,false,7,22738,22795,117420748,"shopping",11.358833245442405,,false,8,1,939365990,"DRIVEALONE",-0.6103921916626766,1878731979 -2863920,1157823,"othmaint",4,false,7,22795,22738,117420748,"parking",,,false,9,1,939365991,"DRIVEALONE",-0.39605030270985575,1878731980 -2863920,1157823,"othmaint",5,false,7,22767,22795,117420748,"eatout",11.192175326364463,,true,9,1,939365991,"WALK",2.952615589445755,1878731981 -2863920,1157823,"othmaint",6,false,7,22795,22767,117420748,"parking",,,true,9,1,939365992,"WALK",4.44330039201803,1878731982 -2863920,1157823,"othmaint",7,false,7,22812,22795,117420748,"home",,,false,9,1,939365992,"DRIVEALONE",-0.11948535915923522,1878731983 -2863920,1157823,"univ",1,true,1,22766,22812,117420751,"univ",,,false,13,0,939366009,"WALK",-1.5030831477846986,1878732017 -2863920,1157823,"univ",1,false,1,22812,22766,117420751,"home",,,false,32,0,939366013,"WALK",-1.5030834911074449,1878732025 -2863921,1157823,"univ",1,true,1,22809,22812,117420792,"univ",,,false,14,0,939366337,"WALK",-0.92894689390927,1878732673 -2863921,1157823,"univ",1,false,1,22812,22809,117420792,"home",,,false,15,0,939366341,"WALK",-0.92894689390927,1878732681 -2863921,1157823,"univ",1,true,1,22809,22812,117420793,"univ",,,false,16,0,939366345,"WALK_LOC",2.481692099564644,1878732689 -2863921,1157823,"univ",1,false,3,22764,22809,117420793,"univ",9.981605111059457,,false,24,0,939366349,"WALK",2.459602060048778,1878732697 -2863921,1157823,"univ",2,false,3,22764,22764,117420793,"univ",9.84928711859661,,false,24,0,939366350,"WALK",3.003985864868469,1878732698 -2863921,1157823,"univ",3,false,3,22812,22764,117420793,"home",,,false,26,0,939366351,"WALK",2.316202097516391,1878732699 -2863922,1157823,"univ",1,true,1,22764,22812,117420833,"univ",,,false,16,0,939366665,"WALK",-1.1568382386079819,1878733329 -2863922,1157823,"univ",1,false,1,22812,22764,117420833,"home",,,false,24,0,939366669,"WALK",-1.1568382386079819,1878733337 -2866914,1159236,"shopping",1,true,2,22738,22797,117543493,"shopping",9.753195503246463,,false,20,1,940347945,"SHARED2",-0.44959528911187474,1880695889 -2866914,1159236,"shopping",2,true,2,22738,22738,117543493,"shopping",,,false,21,1,940347946,"SHARED2",-0.41569822407021684,1880695890 -2866914,1159236,"shopping",1,false,2,22796,22738,117543493,"parking",,,false,21,1,940347949,"SHARED2",-0.618932986161138,1880695897 -2866914,1159236,"shopping",2,false,2,22796,22796,117543493,"parking",,,true,21,1,940347949,"WALK",3.612657585008612,1880695898 -2866914,1159236,"othdiscr",1,true,1,22733,22797,117543499,"othdiscr",,,false,7,0,940347993,"TNC_SHARED",-1.2334449201087534,1880695985 -2866914,1159236,"othdiscr",1,false,1,22797,22733,117543499,"home",,,false,9,0,940347997,"SHARED3",-1.5864778947158276,1880695993 -2866914,1159236,"work",1,true,2,22766,22797,117543513,"parking",,,false,12,1,940348105,"DRIVEALONE",-0.23277800252868205,1880696209 -2866914,1159236,"work",2,true,2,22766,22766,117543513,"work",,,true,12,1,940348105,"WALK",2.014924960389029,1880696210 -2866914,1159236,"work",1,false,1,22797,22766,117543513,"home",,,true,18,1,940348109,"WALK",0.7326263231237407,1880696217 -2866914,1159236,"work",1,true,2,22766,22797,117543514,"parking",,,false,25,1,940348113,"DRIVEALONE",-0.2126150341505889,1880696225 -2866914,1159236,"work",2,true,2,22766,22766,117543514,"work",,,true,25,1,940348113,"WALK",2.0149069444380916,1880696226 -2866914,1159236,"work",1,false,1,22797,22766,117543514,"home",,,true,27,1,940348117,"WALK_LOC",0.7203611640018683,1880696233 -2866915,1159236,"escort",1,true,1,22738,22797,117543524,"escort",,,false,10,1,940348193,"DRIVEALONE",-0.1859277636541118,1880696385 -2866915,1159236,"escort",1,false,2,22796,22738,117543524,"parking",,,false,10,1,940348197,"DRIVEALONE",-0.3618327302148594,1880696393 -2866915,1159236,"escort",2,false,2,22796,22796,117543524,"parking",,,true,10,1,940348197,"WALK",1.067154699334263,1880696394 -2866915,1159236,"work",1,true,1,22801,22797,117543554,"work",,,false,14,0,940348433,"WALK",0.19014379979819185,1880696865 -2866915,1159236,"work",1,false,4,22770,22801,117543554,"shopping",11.568986593078103,,false,24,0,940348437,"WALK",0.4209941097322794,1880696873 -2866915,1159236,"work",2,false,4,22771,22770,117543554,"eatout",13.008322605925745,,false,25,0,940348438,"WALK",0.6865956537757166,1880696874 -2866915,1159236,"work",3,false,4,22767,22771,117543554,"shopping",12.58778480416507,,false,25,0,940348439,"WALK",0.7791172141286921,1880696875 -2866915,1159236,"work",4,false,4,22797,22767,117543554,"home",,,false,26,0,940348440,"WALK",0.4989418541157284,1880696876 -2870656,1160939,"univ",1,true,1,22764,22740,117696927,"univ",,,false,9,0,941575417,"WALK",-0.4952620689018146,1883150833 -2870656,1160939,"univ",1,false,1,22740,22764,117696927,"home",,,false,20,0,941575421,"SHARED2",-0.7088657694023863,1883150841 -2874269,1162627,"eatout",1,true,1,22771,22758,117845035,"eatout",,,false,19,0,942760281,"WALK",0.8892845196071101,1885520561 -2874269,1162627,"eatout",1,false,1,22758,22771,117845035,"home",,,false,21,0,942760285,"WALK",0.8892845196071101,1885520569 -2874269,1162627,"univ",1,true,1,22766,22758,117845060,"univ",,,false,33,0,942760481,"WALK",-0.9851903695198061,1885520961 -2874269,1162627,"univ",1,false,1,22758,22766,117845060,"home",,,false,48,0,942760485,"WALK",-0.9851903695198061,1885520969 -2874270,1162627,"univ",1,true,1,22809,22758,117845101,"univ",,,false,13,0,942760809,"WALK",2.448568395465578,1885521617 -2874270,1162627,"univ",1,false,3,22766,22809,117845101,"univ",10.064918042210902,22766,false,29,0,942760813,"WALK",2.5591208432062635,1885521625 -2874270,1162627,"univ",2,false,3,22760,22766,117845101,"eatout",12.726627067763937,,false,29,0,942760814,"WALK",2.6006338904068236,1885521626 -2874270,1162627,"univ",3,false,3,22758,22760,117845101,"home",,,false,31,0,942760815,"WALK",5.854019928994619,1885521627 -2874271,1162627,"univ",1,true,1,22766,22758,117845142,"univ",,,false,13,0,942761137,"WALK",-0.9851903695198061,1885522273 -2874271,1162627,"univ",1,false,3,22807,22766,117845142,"social",12.701701490919964,,false,25,0,942761141,"WALK",-0.7025935206727638,1885522281 -2874271,1162627,"univ",2,false,3,22767,22807,117845142,"eatout",18.97072944477215,,false,25,0,942761142,"WALK",2.329516762852266,1885522282 -2874271,1162627,"univ",3,false,3,22758,22767,117845142,"home",,,false,26,0,942761143,"WALK",2.1084560857351926,1885522283 -4724316,1944022,"univ",1,true,2,22763,22765,193696987,"escort",12.812689062193739,,false,26,0,1549575897,"WALK",2.1351670996963623,3099151793 -4724316,1944022,"univ",2,true,2,22766,22763,193696987,"univ",,,false,27,0,1549575898,"WALK",-0.8559965323806581,3099151794 -4724316,1944022,"univ",1,false,2,22767,22766,193696987,"eatout",13.154682962658466,,false,29,0,1549575901,"WALK",-0.9673991559282129,3099151801 -4724316,1944022,"univ",2,false,2,22765,22767,193696987,"home",,,false,29,0,1549575902,"WALK",2.1708672114306493,3099151802 -4724701,1944407,"univ",1,true,1,22809,22808,193712772,"univ",,,false,12,0,1549702177,"BIKE",-0.2552725353403566,3099404353 -4724701,1944407,"univ",1,false,2,22766,22809,193712772,"univ",11.3195538505166,22766,false,37,0,1549702181,"BIKE",-0.7042893633777787,3099404361 -4724701,1944407,"univ",2,false,2,22808,22766,193712772,"home",,,false,37,0,1549702182,"BIKE",-0.6429800462056683,3099404362 -4724720,1944426,"work",1,true,1,22738,22806,193713559,"work",,,false,10,0,1549708473,"WALK",3.424476741640782,3099416945 -4724720,1944426,"work",1,false,1,22806,22738,193713559,"home",,,false,25,0,1549708477,"WALK",3.421200065013991,3099416953 -4727094,1946800,"univ",1,true,2,22796,22808,193810885,"parking",,,false,20,1,1550487081,"SHARED2",-0.325497193352596,3100974161 -4727094,1946800,"univ",2,true,2,22809,22796,193810885,"univ",,,true,20,1,1550487081,"WALK_LOC",2.1755216717496055,3100974162 -4727094,1946800,"univ",1,false,1,22808,22809,193810885,"home",,,true,27,1,1550487085,"WALK",2.117177438190699,3100974169 -4728027,1947733,"univ",1,true,1,22764,22806,193849138,"univ",,,false,10,0,1550793105,"WALK",-1.1092692699446687,3101586209 -4728027,1947733,"univ",1,false,2,22767,22764,193849138,"escort",13.485546584318579,,false,27,0,1550793109,"WALK",-0.5148347167335139,3101586217 -4728027,1947733,"univ",2,false,2,22806,22767,193849138,"home",,,false,28,0,1550793110,"WALK",1.9818127360573472,3101586218 -4740690,1970879,"univ",1,true,1,22764,22745,194368321,"univ",,,false,11,0,1554946569,"WALK",2.576602218700462,3109893137 -4740690,1970879,"univ",1,false,3,22768,22764,194368321,"social",26.124396751214636,,false,31,0,1554946573,"WALK",2.8253929561140825,3109893145 -4740690,1970879,"univ",2,false,3,22760,22768,194368321,"othdiscr",30.225849882595227,,false,32,0,1554946574,"WALK",4.979971583696819,3109893146 -4740690,1970879,"univ",3,false,3,22745,22760,194368321,"home",,,false,32,0,1554946575,"WALK",5.801965338203763,3109893147 +trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,original_school_zone_id,parked_at_university,depart,tour_includes_parking,trip_id_pre_parking,trip_mode,mode_choice_logsum +1727022433,2632656,1066353,eatout,1,True,2,22766,22688,107938902,parking,,,False,25.0,1,863511217,DRIVEALONE,-0.8746901452708546 +1727022434,2632656,1066353,eatout,2,True,2,22767,22766,107938902,eatout,,,True,25.0,1,863511217,WALK,6.210014031663612 +1727022441,2632656,1066353,eatout,1,False,2,22766,22767,107938902,parking,,,True,37.0,1,863511221,WALK,6.210037419792028 +1727022442,2632656,1066353,eatout,2,False,2,22688,22766,107938902,home,,,False,37.0,1,863511221,DRIVEALONE,-0.7301658119164749 +1727022961,2632656,1066353,work,1,True,1,22676,22688,107938935,work,,,False,6.0,0,863511481,DRIVEALONE,0.04287730952963362 +1727022969,2632656,1066353,work,1,False,1,22688,22676,107938935,home,,,False,20.0,0,863511485,DRIVEALONE,0.04281092148226895 +1727023489,2632657,1066353,school,1,True,1,22694,22688,107938968,school,,,False,9.0,0,863511745,SCHOOLBUS,-1.3476633093405816 +1727023497,2632657,1066353,school,1,False,2,22688,22694,107938968,eatout,-23.29946944115026,,False,26.0,0,863511749,SHARED3,-9.70321875140174 +1727023498,2632657,1066353,school,2,False,2,22688,22688,107938968,home,,,False,26.0,0,863511750,SHARED2,-8.514611350339074 +1727023793,2632658,1066353,escort,1,True,1,22689,22688,107938987,escort,,,False,32.0,0,863511897,WALK,0.0 +1727023801,2632658,1066353,escort,1,False,1,22688,22689,107938987,home,,,False,33.0,0,863511901,WALK,0.0 +1727023809,2632658,1066353,escort,1,True,1,22694,22688,107938988,escort,,,False,11.0,0,863511905,DRIVEALONE,0.14516010385695438 +1727023817,2632658,1066353,escort,1,False,1,22688,22694,107938988,home,,,False,12.0,0,863511909,SHARED3,0.13787110648003725 +1727024449,2632659,1066353,escort,1,True,1,22694,22688,107939028,escort,,,False,8.0,0,863512225,SHARED2,0.14516010385695438 +1727024457,2632659,1066353,escort,1,False,1,22688,22694,107939028,home,,,False,8.0,0,863512229,SHARED3,0.13787110648003725 +1727076625,2632738,1066386,school,1,True,2,22688,22688,107942289,eatout,10.532297646277984,,False,11.0,0,863538313,SHARED3,0.5546717586355614 +1727076626,2632738,1066386,school,2,True,2,22716,22688,107942289,school,,,False,11.0,0,863538314,SHARED2,0.04685598325940043 +1727076633,2632738,1066386,school,1,False,2,22685,22716,107942289,escort,12.187082550220447,,False,26.0,0,863538317,SHARED3,0.0461601223803947 +1727076634,2632738,1066386,school,2,False,2,22688,22685,107942289,home,,,False,27.0,0,863538318,SHARED3,0.49312008693289416 +1732416961,2640879,1069967,social,1,True,2,22688,22676,108276060,social,11.37473397796374,,False,31.0,0,866208481,SHARED2,0.006004185805916569 +1732416962,2640879,1069967,social,2,True,2,22688,22688,108276060,social,,,False,32.0,0,866208482,SHARED2,0.07253109075082566 +1732416969,2640879,1069967,social,1,False,1,22676,22688,108276060,home,,,False,38.0,0,866208485,SHARED2,0.006188670261483639 +1732417249,2640879,1069967,work,1,True,1,22688,22676,108276078,work,,,False,12.0,0,866208625,SHARED2,0.6840765480327687 +1732417257,2640879,1069967,work,1,False,1,22676,22688,108276078,home,,,False,19.0,0,866208629,DRIVEALONE,0.6845432142839015 +1732417265,2640879,1069967,work,1,True,1,22688,22676,108276079,work,,,False,11.0,0,866208633,DRIVEALONE,0.060574367289556945 +1732417273,2640879,1069967,work,1,False,1,22676,22688,108276079,home,,,False,12.0,0,866208637,DRIVEALONE,0.061074290505893525 +1735713169,2645904,1072088,escort,1,True,1,22694,22711,108482073,escort,,,False,28.0,0,867856585,DRIVEALONE,0.10721753220207553 +1735713177,2645904,1072088,escort,1,False,1,22711,22694,108482073,home,,,False,30.0,0,867856589,SHARED2,0.10722521021183494 +1735713265,2645905,1072088,othdiscr,1,True,2,22766,22711,108482079,parking,,,False,27.0,1,867856633,SHARED2,-1.1455545722211293 +1735713266,2645905,1072088,othdiscr,2,True,2,22766,22766,108482079,othdiscr,,,True,27.0,1,867856633,SHARED2,3.6111900800276358 +1735713273,2645905,1072088,othdiscr,1,False,2,22766,22766,108482079,parking,,,True,35.0,1,867856637,WALK,3.6112032457334435 +1735713274,2645905,1072088,othdiscr,2,False,2,22711,22766,108482079,home,,,False,35.0,1,867856637,SHARED2,-0.9957395363664312 +1735714209,2645905,1072088,shopping,1,True,3,22711,22711,108482138,othmaint,9.775720423403623,,False,22.0,0,867857105,WALK,0.1085924278937159 +1735714210,2645905,1072088,shopping,2,True,3,22711,22711,108482138,social,10.488858934392573,,False,23.0,0,867857106,WALK,0.1085924278937159 +1735714211,2645905,1072088,shopping,3,True,3,22711,22711,108482138,shopping,,,False,24.0,0,867857107,WALK,0.1085924278937159 +1735714217,2645905,1072088,shopping,1,False,1,22711,22711,108482138,home,,,False,24.0,0,867857109,WALK,0.1085924278937159 +1735715489,2645907,1072088,school,1,True,1,22716,22711,108482218,school,,,False,12.0,0,867857745,DRIVEALONE,-0.20641630035108205 +1735715497,2645907,1072088,school,1,False,1,22711,22716,108482218,home,,,False,26.0,0,867857749,DRIVEALONE,-0.20450702798248435 +1752394321,2671332,1083128,shopping,1,True,1,22650,22637,109524645,shopping,,,False,29.0,0,876197161,DRIVEALONE,-0.04893274687468395 +1752394329,2671332,1083128,shopping,1,False,2,22713,22650,109524645,eatout,8.732598620243117,,False,30.0,0,876197165,DRIVEALONE,-0.564278349048812 +1752394330,2671332,1083128,shopping,2,False,2,22637,22713,109524645,home,,,False,30.0,0,876197166,DRIVEALONE,-0.6753239045055804 +1871905057,2853513,1152948,shopping,1,True,1,22781,22770,116994066,shopping,,,False,13.0,0,935952529,WALK,-0.15286567920809457 +1871905065,2853513,1152948,shopping,1,False,1,22770,22781,116994066,home,,,False,16.0,0,935952533,WALK,-0.15286567920809457 +1871905073,2853513,1152948,shopping,1,True,1,22800,22770,116994067,shopping,,,False,22.0,0,935952537,WALK,-0.6260852483044879 +1871905081,2853513,1152948,shopping,1,False,2,22807,22800,116994067,othmaint,13.689405015818817,,False,23.0,0,935952541,WALK,-0.5662778537062289 +1871905082,2853513,1152948,shopping,2,False,2,22770,22807,116994067,home,,,False,23.0,0,935952542,WALK,2.1564636451535324 +1871905089,2853513,1152948,shopping,1,True,1,22798,22770,116994068,shopping,,,False,25.0,0,935952545,WALK,0.3629456060638815 +1871905097,2853513,1152948,shopping,1,False,1,22770,22798,116994068,home,,,False,32.0,0,935952549,WALK,0.3629456060638815 +1873669969,2856204,1154357,escort,1,True,1,22767,22815,117104373,escort,,,False,11.0,0,936834985,WALK,1.572441903227518 +1873669977,2856204,1154357,escort,1,False,1,22815,22767,117104373,home,,,False,11.0,0,936834989,WALK,1.572441903227518 +1873670225,2856204,1154357,othdiscr,1,True,1,22795,22815,117104389,othdiscr,,,False,11.0,0,936835113,WALK,-1.136157391902266 +1873670233,2856204,1154357,othdiscr,1,False,1,22815,22795,117104389,home,,,False,15.0,0,936835117,WALK,-1.136157391902266 +1873670321,2856204,1154357,univ,1,True,3,22767,22815,117104395,work,13.498034431649993,,False,21.0,0,936835161,WALK,5.028511196364142 +1873670322,2856204,1154357,univ,2,True,3,22809,22767,117104395,univ,10.775599452844196,22809,False,22.0,0,936835162,WALK_LOC,2.7954607582586246 +1873670323,2856204,1154357,univ,3,True,3,22809,22809,117104395,univ,,,False,24.0,0,936835163,WALK,3.0008457318923365 +1873670329,2856204,1154357,univ,1,False,4,22767,22809,117104395,othmaint,11.97899358246422,,False,42.0,0,936835165,WALK,2.7869485143816908 +1873670330,2856204,1154357,univ,2,False,4,22764,22767,117104395,univ,14.341759600913552,,False,42.0,0,936835166,WALK,5.487620370472843 +1873670331,2856204,1154357,univ,3,False,4,22770,22764,117104395,othdiscr,12.599191957343043,,False,44.0,0,936835167,WALK,2.6096929225906433 +1873670332,2856204,1154357,univ,4,False,4,22815,22770,117104395,home,,,False,44.0,0,936835168,WALK_LOC,3.5917238491796746 +1873969057,2856660,1154635,eatout,1,True,1,22810,22815,117123066,eatout,,,False,32.0,0,936984529,WALK,-0.29168228455903983 +1873969065,2856660,1154635,eatout,1,False,1,22815,22810,117123066,home,,,False,33.0,0,936984533,WALK,-0.29168228455903983 +1873969457,2856660,1154635,univ,1,True,1,22764,22815,117123091,univ,,,False,17.0,0,936984729,WALK,1.8818869666378932 +1873969465,2856660,1154635,univ,1,False,4,22766,22764,117123091,univ,9.92705808981923,22766,False,20.0,0,936984733,WALK,2.332439730730206 +1873969466,2856660,1154635,univ,2,False,4,22767,22766,117123091,eatout,11.698000750719451,,False,20.0,0,936984734,WALK,2.0354974925066145 +1873969467,2856660,1154635,univ,3,False,4,22764,22767,117123091,univ,14.467091292345321,,False,26.0,0,936984735,WALK,5.113556461480974 +1873969468,2856660,1154635,univ,4,False,4,22815,22764,117123091,home,,,False,30.0,0,936984736,WALK,1.8818676173433673 +1873970113,2856661,1154635,univ,1,True,2,22767,22815,117123132,eatout,24.13568322189892,,False,9.0,0,936985057,WALK,5.024093072052891 +1873970114,2856661,1154635,univ,2,True,2,22809,22767,117123132,univ,,,False,10.0,0,936985058,WALK,2.7951650320100074 +1873970121,2856661,1154635,univ,1,False,4,22809,22809,117123132,univ,22.10560655915902,22809,False,24.0,0,936985061,WALK,3.000453614136991 +1873970122,2856661,1154635,univ,2,False,4,22809,22809,117123132,univ,21.992495219718602,22809,False,24.0,0,936985062,WALK,3.000453614136991 +1873970123,2856661,1154635,univ,3,False,4,22807,22809,117123132,work,26.50952124631712,,False,25.0,0,936985063,WALK,3.007444572576448 +1873970124,2856661,1154635,univ,4,False,4,22815,22807,117123132,home,,,False,31.0,0,936985064,WALK,5.1535448290063615 +1877439505,2861950,1156849,shopping,1,True,1,22800,22801,117339969,shopping,,,False,21.0,0,938719753,WALK,-0.49881710844895727 +1877439513,2861950,1156849,shopping,1,False,1,22801,22800,117339969,home,,,False,30.0,0,938719757,WALK,-0.49881710844895727 +1877439697,2861950,1156849,univ,1,True,1,22809,22801,117339981,univ,,,False,13.0,0,938719849,WALK,2.7089529835300503 +1877439705,2861950,1156849,univ,1,False,2,22766,22809,117339981,univ,22.262579096450402,22766,False,20.0,0,938719853,WALK_LOC,2.560406996243793 +1877439706,2861950,1156849,univ,2,False,2,22801,22766,117339981,home,,,False,21.0,0,938719854,WALK_LOC,2.3733339088312397 +1877440353,2861951,1156849,univ,1,True,1,22809,22801,117340022,univ,,,False,11.0,0,938720177,WALK,-0.656569218265208 +1877440361,2861951,1156849,univ,1,False,3,22767,22809,117340022,othdiscr,14.308117668699108,,False,12.0,0,938720181,WALK,-0.50518420043921 +1877440362,2861951,1156849,univ,2,False,3,22767,22767,117340022,shopping,17.816995526914052,,False,12.0,0,938720182,WALK,2.62825193059268 +1877440363,2861951,1156849,univ,3,False,3,22801,22767,117340022,home,,,False,13.0,0,938720183,WALK,1.890862363486975 +1877441009,2861952,1156849,univ,1,True,1,22809,22801,117340063,univ,,,False,7.0,0,938720505,WALK,-0.656569218265208 +1877441017,2861952,1156849,univ,1,False,1,22801,22809,117340063,home,,,False,11.0,0,938720509,WALK,-0.656569218265208 +1877441025,2861952,1156849,univ,1,True,1,22809,22801,117340064,univ,,,False,30.0,0,938720513,WALK,-0.656569218265208 +1877441033,2861952,1156849,univ,1,False,1,22801,22809,117340064,home,,,False,32.0,0,938720517,WALK,-0.656569218265208 +1877508577,2862055,1156884,univ,1,True,3,22767,22804,117344286,eatout,12.979710329497422,,False,19.0,0,938754289,WALK,1.8938019500606744 +1877508578,2862055,1156884,univ,2,True,3,22767,22767,117344286,work,15.06828301301832,,False,20.0,0,938754290,WALK,2.62825193059268 +1877508579,2862055,1156884,univ,3,True,3,22809,22767,117344286,univ,,,False,22.0,0,938754291,WALK,-0.50518420043921 +1877508585,2862055,1156884,univ,1,False,1,22804,22809,117344286,home,,,False,22.0,0,938754293,WALK,-0.44242952311569 +1877509233,2862056,1156884,univ,1,True,1,22809,22804,117344327,univ,,,False,12.0,0,938754617,WALK,2.816810400707295 +1877509241,2862056,1156884,univ,1,False,1,22804,22809,117344327,home,,,False,27.0,0,938754621,WALK,2.818487568540833 +1877509889,2862057,1156884,univ,1,True,1,22809,22804,117344368,univ,,,False,9.0,0,938754945,WALK,2.8149418226097684 +1877509897,2862057,1156884,univ,1,False,4,22767,22809,117344368,othdiscr,14.092132672154193,,False,29.0,0,938754949,WALK,2.8115136852150613 +1877509898,2862057,1156884,univ,2,False,4,22809,22767,117344368,univ,15.082414498585248,22809,False,29.0,0,938754950,WALK_LOC,5.421713617528955 +1877509899,2862057,1156884,univ,3,False,4,22809,22809,117344368,univ,11.07119990210677,22809,False,29.0,0,938754951,WALK,3.0039396426418254 +1877509900,2862057,1156884,univ,4,False,4,22804,22809,117344368,home,,,False,40.0,0,938754952,WALK,2.7990874824379435 +1878731969,2863920,1157823,othmaint,1,True,2,22795,22812,117420748,parking,,,False,7.0,1,939365985,DRIVEALONE,-0.09902637407657627 +1878731970,2863920,1157823,othmaint,2,True,2,22806,22795,117420748,othmaint,,,True,7.0,1,939365985,WALK,3.92788746633635 +1878731977,2863920,1157823,othmaint,1,False,7,22767,22806,117420748,eatout,11.485301584384368,,True,8.0,1,939365989,WALK,4.150955713093153 +1878731978,2863920,1157823,othmaint,2,False,7,22795,22767,117420748,parking,,,True,8.0,1,939365990,WALK,4.44330039201803 +1878731979,2863920,1157823,othmaint,3,False,7,22738,22795,117420748,shopping,11.358833245442405,,False,8.0,1,939365990,DRIVEALONE,-0.6103921916626766 +1878731980,2863920,1157823,othmaint,4,False,7,22795,22738,117420748,parking,,,False,9.0,1,939365991,DRIVEALONE,-0.39605030270985575 +1878731981,2863920,1157823,othmaint,5,False,7,22767,22795,117420748,eatout,11.192175326364463,,True,9.0,1,939365991,WALK,2.952615589445755 +1878731982,2863920,1157823,othmaint,6,False,7,22795,22767,117420748,parking,,,True,9.0,1,939365992,WALK,4.44330039201803 +1878731983,2863920,1157823,othmaint,7,False,7,22812,22795,117420748,home,,,False,9.0,1,939365992,DRIVEALONE,-0.11948535915923522 +1878732017,2863920,1157823,univ,1,True,1,22766,22812,117420751,univ,,,False,13.0,0,939366009,WALK,-1.5030831477846986 +1878732025,2863920,1157823,univ,1,False,1,22812,22766,117420751,home,,,False,32.0,0,939366013,WALK,-1.5030834911074449 +1878732673,2863921,1157823,univ,1,True,1,22809,22812,117420792,univ,,,False,14.0,0,939366337,WALK,-0.92894689390927 +1878732681,2863921,1157823,univ,1,False,1,22812,22809,117420792,home,,,False,15.0,0,939366341,WALK,-0.92894689390927 +1878732689,2863921,1157823,univ,1,True,1,22809,22812,117420793,univ,,,False,16.0,0,939366345,WALK_LOC,2.481692099564644 +1878732697,2863921,1157823,univ,1,False,3,22764,22809,117420793,univ,9.981605111059457,,False,24.0,0,939366349,WALK,2.459602060048778 +1878732698,2863921,1157823,univ,2,False,3,22764,22764,117420793,univ,9.84928711859661,,False,24.0,0,939366350,WALK,3.003985864868469 +1878732699,2863921,1157823,univ,3,False,3,22812,22764,117420793,home,,,False,26.0,0,939366351,WALK,2.316202097516391 +1878733329,2863922,1157823,univ,1,True,1,22764,22812,117420833,univ,,,False,16.0,0,939366665,WALK,-1.1568382386079819 +1878733337,2863922,1157823,univ,1,False,1,22812,22764,117420833,home,,,False,24.0,0,939366669,WALK,-1.1568382386079819 +1880695889,2866914,1159236,shopping,1,True,2,22738,22797,117543493,shopping,9.753195503246463,,False,20.0,1,940347945,SHARED2,-0.44959528911187474 +1880695890,2866914,1159236,shopping,2,True,2,22738,22738,117543493,shopping,,,False,21.0,1,940347946,SHARED2,-0.41569822407021684 +1880695897,2866914,1159236,shopping,1,False,2,22796,22738,117543493,parking,,,False,21.0,1,940347949,SHARED2,-0.618932986161138 +1880695898,2866914,1159236,shopping,2,False,2,22796,22796,117543493,parking,,,True,21.0,1,940347949,WALK,3.612657585008612 +1880695985,2866914,1159236,othdiscr,1,True,1,22733,22797,117543499,othdiscr,,,False,7.0,0,940347993,TNC_SHARED,-1.2334449201087534 +1880695993,2866914,1159236,othdiscr,1,False,1,22797,22733,117543499,home,,,False,9.0,0,940347997,SHARED3,-1.5864778947158276 +1880696209,2866914,1159236,work,1,True,2,22766,22797,117543513,parking,,,False,12.0,1,940348105,DRIVEALONE,-0.23277800252868205 +1880696210,2866914,1159236,work,2,True,2,22766,22766,117543513,work,,,True,12.0,1,940348105,WALK,2.014924960389029 +1880696217,2866914,1159236,work,1,False,1,22797,22766,117543513,home,,,True,18.0,1,940348109,WALK,0.7326263231237407 +1880696225,2866914,1159236,work,1,True,2,22766,22797,117543514,parking,,,False,25.0,1,940348113,DRIVEALONE,-0.2126150341505889 +1880696226,2866914,1159236,work,2,True,2,22766,22766,117543514,work,,,True,25.0,1,940348113,WALK,2.0149069444380916 +1880696233,2866914,1159236,work,1,False,1,22797,22766,117543514,home,,,True,27.0,1,940348117,WALK_LOC,0.7203611640018683 +1880696385,2866915,1159236,escort,1,True,1,22738,22797,117543524,escort,,,False,10.0,1,940348193,DRIVEALONE,-0.1859277636541118 +1880696393,2866915,1159236,escort,1,False,2,22796,22738,117543524,parking,,,False,10.0,1,940348197,DRIVEALONE,-0.3618327302148594 +1880696394,2866915,1159236,escort,2,False,2,22796,22796,117543524,parking,,,True,10.0,1,940348197,WALK,1.067154699334263 +1880696865,2866915,1159236,work,1,True,1,22801,22797,117543554,work,,,False,14.0,0,940348433,WALK,0.19014379979819185 +1880696873,2866915,1159236,work,1,False,4,22770,22801,117543554,shopping,11.568986593078103,,False,24.0,0,940348437,WALK,0.4209941097322794 +1880696874,2866915,1159236,work,2,False,4,22771,22770,117543554,eatout,13.008322605925745,,False,25.0,0,940348438,WALK,0.6865956537757166 +1880696875,2866915,1159236,work,3,False,4,22767,22771,117543554,shopping,12.587784804165072,,False,25.0,0,940348439,WALK,0.7791172141286921 +1880696876,2866915,1159236,work,4,False,4,22797,22767,117543554,home,,,False,26.0,0,940348440,WALK,0.4989418541157284 +1883150833,2870656,1160939,univ,1,True,1,22764,22740,117696927,univ,,,False,9.0,0,941575417,WALK,-0.4952620689018146 +1883150841,2870656,1160939,univ,1,False,1,22740,22764,117696927,home,,,False,20.0,0,941575421,SHARED2,-0.7088657694023863 +1885520561,2874269,1162627,eatout,1,True,1,22771,22758,117845035,eatout,,,False,19.0,0,942760281,WALK,0.8892845196071101 +1885520569,2874269,1162627,eatout,1,False,1,22758,22771,117845035,home,,,False,21.0,0,942760285,WALK,0.8892845196071101 +1885520961,2874269,1162627,univ,1,True,1,22766,22758,117845060,univ,,,False,33.0,0,942760481,WALK,-0.9851903695198061 +1885520969,2874269,1162627,univ,1,False,1,22758,22766,117845060,home,,,False,48.0,0,942760485,WALK,-0.9851903695198061 +1885521617,2874270,1162627,univ,1,True,1,22809,22758,117845101,univ,,,False,13.0,0,942760809,WALK,2.448568395465578 +1885521625,2874270,1162627,univ,1,False,3,22766,22809,117845101,univ,10.064918042210902,22766,False,29.0,0,942760813,WALK,2.5591208432062635 +1885521626,2874270,1162627,univ,2,False,3,22760,22766,117845101,eatout,12.726627067763937,,False,29.0,0,942760814,WALK,2.6006338904068236 +1885521627,2874270,1162627,univ,3,False,3,22758,22760,117845101,home,,,False,31.0,0,942760815,WALK,5.854019928994619 +1885522273,2874271,1162627,univ,1,True,1,22766,22758,117845142,univ,,,False,13.0,0,942761137,WALK,-0.9851903695198061 +1885522281,2874271,1162627,univ,1,False,3,22807,22766,117845142,social,12.701701490919964,,False,25.0,0,942761141,WALK,-0.7025935206727638 +1885522282,2874271,1162627,univ,2,False,3,22767,22807,117845142,eatout,18.97072944477215,,False,25.0,0,942761142,WALK,2.329516762852266 +1885522283,2874271,1162627,univ,3,False,3,22758,22767,117845142,home,,,False,26.0,0,942761143,WALK,2.1084560857351926 +3099151793,4724316,1944022,univ,1,True,2,22763,22765,193696987,escort,12.812689062193739,,False,26.0,0,1549575897,WALK,2.1351670996963623 +3099151794,4724316,1944022,univ,2,True,2,22766,22763,193696987,univ,,,False,27.0,0,1549575898,WALK,-0.8559965323806581 +3099151801,4724316,1944022,univ,1,False,2,22767,22766,193696987,eatout,13.154682962658466,,False,29.0,0,1549575901,WALK,-0.9673991559282129 +3099151802,4724316,1944022,univ,2,False,2,22765,22767,193696987,home,,,False,29.0,0,1549575902,WALK,2.1708672114306493 +3099404353,4724701,1944407,univ,1,True,1,22809,22808,193712772,univ,,,False,12.0,0,1549702177,BIKE,-0.2552725353403566 +3099404361,4724701,1944407,univ,1,False,2,22766,22809,193712772,univ,11.3195538505166,22766,False,37.0,0,1549702181,BIKE,-0.7042893633777787 +3099404362,4724701,1944407,univ,2,False,2,22808,22766,193712772,home,,,False,37.0,0,1549702182,BIKE,-0.6429800462056683 +3099416945,4724720,1944426,work,1,True,1,22738,22806,193713559,work,,,False,10.0,0,1549708473,WALK,3.424476741640782 +3099416953,4724720,1944426,work,1,False,1,22806,22738,193713559,home,,,False,25.0,0,1549708477,WALK,3.421200065013991 +3100974161,4727094,1946800,univ,1,True,2,22796,22808,193810885,parking,,,False,20.0,1,1550487081,SHARED2,-0.325497193352596 +3100974162,4727094,1946800,univ,2,True,2,22809,22796,193810885,univ,,,True,20.0,1,1550487081,WALK_LOC,2.1755216717496055 +3100974169,4727094,1946800,univ,1,False,1,22808,22809,193810885,home,,,True,27.0,1,1550487085,WALK,2.117177438190699 +3101586209,4728027,1947733,univ,1,True,1,22764,22806,193849138,univ,,,False,10.0,0,1550793105,WALK,-1.1092692699446687 +3101586217,4728027,1947733,univ,1,False,2,22767,22764,193849138,escort,13.485546584318579,,False,27.0,0,1550793109,WALK,-0.5148347167335139 +3101586218,4728027,1947733,univ,2,False,2,22806,22767,193849138,home,,,False,28.0,0,1550793110,WALK,1.9818127360573472 +3109893137,4740690,1970879,univ,1,True,1,22764,22745,194368321,univ,,,False,11.0,0,1554946569,WALK,2.576602218700462 +3109893145,4740690,1970879,univ,1,False,3,22768,22764,194368321,social,26.124396751214636,,False,31.0,0,1554946573,WALK,2.8253929561140825 +3109893146,4740690,1970879,univ,2,False,3,22760,22768,194368321,othdiscr,30.225849882595227,,False,32.0,0,1554946574,WALK,4.979971583696819 +3109893147,4740690,1970879,univ,3,False,3,22745,22760,194368321,home,,,False,32.0,0,1554946575,WALK,5.801965338203763 From bc56a8771071702d1df3ead232fa161be6ab865e Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Fri, 10 Apr 2026 16:32:14 +1000 Subject: [PATCH 203/298] PR noise, make sure alts_context built on un-modified alternatives in trip_dest --- .../test/test_trip_scheduling_consistency.py | 1 - activitysim/abm/models/trip_destination.py | 22 +++++++++---------- .../core/interaction_sample_simulate.py | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/activitysim/abm/models/test/test_trip_scheduling_consistency.py b/activitysim/abm/models/test/test_trip_scheduling_consistency.py index b773c6a06a..8f45cfac16 100644 --- a/activitysim/abm/models/test/test_trip_scheduling_consistency.py +++ b/activitysim/abm/models/test/test_trip_scheduling_consistency.py @@ -1,7 +1,6 @@ """ Tests confirming the SCHEDULE_ID in run_trip_scheduling_choice is not chunk-sensitive. """ -from __future__ import annotations import numpy as np import pandas as pd diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 9888d0606a..011fb9fc0b 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -30,13 +30,13 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LocationComponentSettings -from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.skim_dictionary import DataFrameMatrix from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, reindex +from activitysim.core.exceptions import InvalidTravelError, DuplicateWorkflowTableError logger = logging.getLogger(__name__) @@ -1083,6 +1083,11 @@ def choose_trip_destination( t0 = print_elapsed_time() + # use full index (including zero-size zones) to ensure stable random results + # fetch alts_context early so we don't worry about mutating alternatives first + alts_context = AltsContext.from_series(alternatives.index) + + # - trip_destination_sample destination_sample = trip_destination_sample( state, @@ -1129,11 +1134,6 @@ def choose_trip_destination( destination_sample["dp_logsum"] = 0.0 t0 = print_elapsed_time("%s.compute_logsums" % trace_label, t0, debug=True) - alt_dest_col_name = model_settings.ALT_DEST_COL_NAME - alts = alternatives.index - assert alts.name == alt_dest_col_name - # use full index (including zero-size zones) to ensure stable random results - alts_context = AltsContext.from_series(alts) destinations = trip_destination_simulate( state, primary_purpose=primary_purpose, @@ -1528,13 +1528,13 @@ def run_trip_destination( """ When using the trip destination model with sharrow, it is necessary - to set a value for `purpose_index_num` in the trip destination - annotate trips preprocessor. This allows for an optimized compiled + to set a value for `purpose_index_num` in the trip destination + annotate trips preprocessor. This allows for an optimized compiled lookup of the size term from the array of size terms. The value of - `purpose_index_num` should be the integer column position in the size - matrix, with usual zero-based numpy indexing semantics (i.e. the first + `purpose_index_num` should be the integer column position in the size + matrix, with usual zero-based numpy indexing semantics (i.e. the first column is zero). The preprocessor expression most likely needs to be - "size_terms.get_cols(df.purpose)" unless some unusual transform of + "size_terms.get_cols(df.purpose)" unless some unusual transform of size terms has been employed. """ diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 81897899a0..4828418177 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -9,9 +9,9 @@ from activitysim.core import chunk, interaction_simulate, logit, tracing, util, workflow from activitysim.core.configuration.base import ComputeSettings +from activitysim.core.simulate import set_skim_wrapper_targets from activitysim.core.exceptions import SegmentedSpecificationError from activitysim.core.logit import AltsContext -from activitysim.core.simulate import set_skim_wrapper_targets logger = logging.getLogger(__name__) From 531b56238fe717b92678188710fd793101415774 Mon Sep 17 00:00:00 2001 From: Tom Stephen Date: Fri, 10 Apr 2026 16:44:15 +1000 Subject: [PATCH 204/298] linting --- activitysim/abm/models/trip_destination.py | 1 - 1 file changed, 1 deletion(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 011fb9fc0b..df16941489 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -1087,7 +1087,6 @@ def choose_trip_destination( # fetch alts_context early so we don't worry about mutating alternatives first alts_context = AltsContext.from_series(alternatives.index) - # - trip_destination_sample destination_sample = trip_destination_sample( state, From 19750a0921e4d61d55f5ed475d3bd01ca717542d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 07:15:25 +1000 Subject: [PATCH 205/298] updates outdated comment --- activitysim/abm/models/location_choice.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index e52d9e9bfb..dfe1108783 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -794,12 +794,9 @@ def run_location_choice( if choosers.shape[0] == 0: logger.info(f"{trace_label} skipping segment {segment_name}: no choosers") continue - # using land use rather than size terms in case something goes 0 base -> nonzero project, double - # check if that would be in dest_size_terms as a zero - # use full index (including zero-size zones) to ensure stable random results + # dest_size_terms contains 0-attraction zones so using this directly here, important for stable error terms + # when a zone goes from 0 base -> nonzero project alts_context = AltsContext.from_series(dest_size_terms.index) - # assumes that dest_size_terms will always contain zeros for non-attractive zones, i.e. it will have the - # same length as land_use # - location_sample location_sample_df = run_location_sample( From 834fc907f2266c04f63cf545e1b684727422dda5 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 08:56:08 +1000 Subject: [PATCH 206/298] re-add arc test, remove parking choice column from regress trips --- .github/workflows/core_tests.yml | 1 + .../test/regress/final_trips.csv | 182 +++++++++--------- .../test/regress/final_trips_sh.csv | 182 +++++++++--------- 3 files changed, 183 insertions(+), 182 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 222527c465..1cc35874fe 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -141,6 +141,7 @@ jobs: matrix: region: - prototype_mtc + - prototype_arc - placeholder_psrc - prototype_marin - prototype_mtc_extended diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips.csv b/activitysim/examples/prototype_arc/test/regress/final_trips.csv index 3cfe9e642a..0bd93ac3e8 100644 --- a/activitysim/examples/prototype_arc/test/regress/final_trips.csv +++ b/activitysim/examples/prototype_arc/test/regress/final_trips.csv @@ -1,91 +1,91 @@ -trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum,parking_zone_id -37314161,113762,42730,othmaint,1,True,1,106,103,4664270,othmaint,,10,DRIVEALONEFREE,-0.3567815721035004,-1 -37314165,113762,42730,othmaint,1,False,1,103,106,4664270,home,,11,DRIVEALONEFREE,-0.356460303068161,-1 -38194977,116448,43843,atwork,1,True,1,106,101,4774372,atwork,,20,DRIVEALONEFREE,-0.3217517137527465,-1 -38194981,116448,43843,atwork,1,False,1,101,106,4774372,work,,21,DRIVEALONEFREE,-0.3217517137527465,-1 -38195065,116449,43843,othdiscr,1,True,1,106,103,4774383,othdiscr,,32,SHARED2FREE,0.7593915111282218,-1 -38195069,116449,43843,othdiscr,1,False,1,103,106,4774383,home,,37,SHARED2FREE,0.7593915111282218,-1 -38195257,116448,43843,work,1,True,2,107,103,4774407,othmaint,9.244319,10,DRIVEALONEFREE,-0.6671370863914491,-1 -38195258,116448,43843,work,2,True,2,101,107,4774407,work,,10,DRIVEALONEFREE,-0.5893840193748475,-1 -38195261,116448,43843,work,1,False,1,103,101,4774407,home,,30,DRIVEALONEFREE,-0.5012716650962832,-1 -38195585,116449,43843,work,1,True,2,106,103,4774448,othmaint,10.644734,12,DRIVEALONEFREE,0.05086306230852542,-1 -38195586,116449,43843,work,2,True,2,102,106,4774448,work,,13,DRIVEALONEFREE,0.03254505218598833,-1 -38195589,116449,43843,work,1,False,3,103,102,4774448,othmaint,10.796497,23,SHARED2FREE,0.0983521099924028,-1 -38195590,116449,43843,work,2,False,3,103,103,4774448,work,12.367123,24,DRIVEALONEFREE,0.24223826711784288,-1 -38195591,116449,43843,work,3,False,3,103,103,4774448,home,,26,DRIVEALONEFREE,0.2401515927071465,-1 -38195849,116450,43843,school,1,True,1,106,103,4774481,school,,9,SCHOOL_BUS,4.351044654846191,-1 -38195853,116450,43843,school,1,False,1,103,106,4774481,home,,27,SCHOOL_BUS,4.351044654846191,-1 -38195865,116450,43843,shopping,1,True,1,101,103,4774483,shopping,,27,SHARED2FREE,-0.4441019010696936,-1 -38195869,116450,43843,shopping,1,False,1,103,101,4774483,home,,30,SHARED2FREE,-0.45749089544283433,-1 -39613905,120774,45311,atwork,1,True,1,101,102,4951738,atwork,,20,DRIVEALONEFREE,-0.41128289699554443,-1 -39613909,120774,45311,atwork,1,False,1,102,101,4951738,work,,21,DRIVEALONEFREE,-0.4119255244731903,-1 -39614185,120774,45311,work,1,True,2,106,105,4951773,work,10.647319,10,DRIVEALONEFREE,-0.4328329563140868,-1 -39614186,120774,45311,work,2,True,2,102,106,4951773,work,,11,DRIVEALONEFREE,-0.34803289175033575,-1 -39614189,120774,45311,work,1,False,1,105,102,4951773,home,,30,DRIVEALONEFREE,-0.604685664176941,-1 -39614513,120775,45311,work,1,True,1,101,105,4951814,work,,9,DRIVEALONEFREE,-0.6009435653686525,-1 -39614517,120775,45311,work,1,False,3,101,101,4951814,work,10.767546,28,DRIVEALONEFREE,-0.3567099869251252,-1 -39614518,120775,45311,work,2,False,3,107,101,4951814,othmaint,9.370711,28,DRIVEALONEFREE,-0.5956825017929079,-1 -39614519,120775,45311,work,3,False,3,105,107,4951814,home,,29,DRIVEALONEFREE,-0.43356654047966,-1 -40387937,123133,46056,work,1,True,1,106,106,5048492,work,,20,DRIVEALONEFREE,-0.19777289032936102,-1 -40387941,123133,46056,work,1,False,1,106,106,5048492,home,,40,DRIVEALONEFREE,-0.1974023878574371,-1 -43308361,132037,49258,othmaint,1,True,1,122,110,5413545,othmaint,,23,DRIVEALONEFREE,-0.7390050888061525,-1 -43308365,132037,49258,othmaint,1,False,2,114,122,5413545,eatout,8.7858,24,DRIVEALONEFREE,-0.5175821781158448,-1 -43308366,132037,49258,othmaint,2,False,2,110,114,5413545,home,,24,DRIVEALONEFREE,-0.5938398838043213,-1 -43308537,132038,49258,escort,1,True,1,107,110,5413567,escort,,10,SHARED3FREE,-0.002601420005322437,-1 -43308541,132038,49258,escort,1,False,1,110,107,5413567,home,,22,SHARED3FREE,-0.002601420005322437,-1 -44930737,136983,50912,work,1,True,2,123,112,5616342,eatout,9.353397,31,DRIVEALONEFREE,-0.5493329763412477,-1 -44930738,136983,50912,work,2,True,2,104,123,5616342,work,,32,DRIVEALONEFREE,-0.6666110157966614,-1 -44930741,136983,50912,work,1,False,2,112,104,5616342,social,11.149774,34,DRIVEALONEFREE,-0.5302670001983643,-1 -44930742,136983,50912,work,2,False,2,112,112,5616342,home,,34,DRIVEALONEFREE,-0.18331599235534674,-1 -44931065,136984,50912,work,1,True,2,101,112,5616383,shopping,9.520916,11,DRIVEALONEFREE,-0.6129478216171266,-1 -44931066,136984,50912,work,2,True,2,107,101,5616383,work,,12,DRIVEALONEFREE,-0.6193944811820985,-1 -44931069,136984,50912,work,1,False,3,123,107,5616383,work,10.775923,28,DRIVEALONEFREE,-0.7651270031929017,-1 -44931070,136984,50912,work,2,False,3,104,123,5616383,escort,9.519634,29,DRIVEALONEFREE,-0.6666110157966614,-1 -44931071,136984,50912,work,3,False,3,112,104,5616383,home,,30,DRIVEALONEFREE,-0.5499035120010376,-1 -47621473,145187,53716,othmaint,1,True,3,121,116,5952684,social,9.947862,8,SHARED3FREE,-0.41955729937135083,-1 -47621474,145187,53716,othmaint,2,True,3,112,121,5952684,othmaint,9.261029,11,SHARED3FREE,-0.6422730088233947,-1 -47621475,145187,53716,othmaint,3,True,3,122,112,5952684,othmaint,,11,SHARED3FREE,-0.6419082880020143,-1 -47621477,145187,53716,othmaint,1,False,1,116,122,5952684,home,,20,SHARED3FREE,-0.6134629858242939,-1 -47621737,145188,53716,escort,1,True,1,114,116,5952717,escort,,29,DRIVEALONEFREE,-0.15083796859645277,-1 -47621741,145188,53716,escort,1,False,1,116,114,5952717,home,,30,SHARED2FREE,-0.15179812895272474,-1 -47622241,145189,53716,school,1,True,1,114,116,5952780,school,,10,SCHOOL_BUS,4.3079237937927255,-1 -47622245,145189,53716,school,1,False,1,116,114,5952780,home,,24,SCHOOL_BUS,4.3079237937927255,-1 -47622569,145190,53716,school,1,True,1,114,116,5952821,school,,9,SHARED2FREE,-0.20617904275545365,-1 -47622573,145190,53716,school,1,False,1,116,114,5952821,home,,24,SHARED2FREE,-0.20568500108204935,-1 -48258513,147129,54342,othdiscr,1,True,1,116,117,6032314,othdiscr,,27,DRIVEALONEFREE,-0.5246167778968812,-1 -48258517,147129,54342,othdiscr,1,False,1,117,116,6032314,home,,33,DRIVEALONEFREE,-0.49120157957077026,-1 -48258537,147129,54342,othmaint,1,True,1,114,117,6032317,othmaint,,34,DRIVEALONEFREE,-0.687132179737091,-1 -48258541,147129,54342,othmaint,1,False,2,114,114,6032317,shopping,9.148774,37,DRIVEALONEFREE,-0.42373609542846685,-1 -48258542,147129,54342,othmaint,2,False,2,117,114,6032317,home,,38,DRIVEALONEFREE,-0.6845617890357972,-1 -56357665,171822,63802,eatout,1,True,1,127,135,7044708,eatout,,31,DRIVEALONEFREE,-0.6526245474815369,-1 -56357669,171822,63802,eatout,1,False,1,135,127,7044708,home,,34,DRIVEALONEFREE,-0.6343104243278503,-1 -56357689,171822,63802,escort,1,True,1,135,135,7044711,escort,,28,SHARED3FREE,0.07706324286670248,-1 -56357693,171822,63802,escort,1,False,2,135,135,7044711,escort,11.356267,28,SHARED3FREE,0.07706324286670248,-1 -56357694,171822,63802,escort,2,False,2,135,135,7044711,home,,28,SHARED3FREE,0.07706324286670248,-1 -56357737,171822,63802,othdiscr,1,True,3,131,135,7044717,othdiscr,12.194779,13,SHARED2FREE,0.599977654783949,-1 -56357738,171822,63802,othdiscr,2,True,3,130,131,7044717,shopping,13.357507,14,SHARED2FREE,0.6200047250329787,-1 -56357739,171822,63802,othdiscr,3,True,3,130,130,7044717,othdiscr,,14,SHARED2FREE,0.6960546579187884,-1 -56357741,171822,63802,othdiscr,1,False,1,135,130,7044717,home,,14,SHARED2FREE,0.6487159186367744,-1 -56358209,171823,63802,shopping,1,True,4,131,135,7044776,othmaint,10.342613,24,SHARED3FREE,-0.14619837454037923,-1 -56358210,171823,63802,shopping,2,True,4,131,131,7044776,social,12.281772,25,SHARED3FREE,-0.012169709209450414,-1 -56358211,171823,63802,shopping,3,True,4,131,131,7044776,shopping,11.556939,26,SHARED3FREE,-0.012169709209450414,-1 -56358212,171823,63802,shopping,4,True,4,131,131,7044776,shopping,,26,SHARED3FREE,-0.012169709209450414,-1 -56358213,171823,63802,shopping,1,False,1,135,131,7044776,home,,27,DRIVEALONEFREE,-0.15095594351539895,-1 -56358473,171824,63802,othdiscr,1,True,1,131,135,7044809,othdiscr,,32,SHARED2FREE,-0.46024149381952484,-1 -56358477,171824,63802,othdiscr,1,False,1,135,131,7044809,home,,37,SHARED2FREE,-0.45329299190068956,-1 -56358521,171824,63802,school,1,True,2,135,135,7044815,escort,11.635028,10,SHARED2FREE,0.10569338088788001,-1 -56358522,171824,63802,school,2,True,2,135,135,7044815,school,,10,SHARED3FREE,0.10569338088788001,-1 -56358525,171824,63802,school,1,False,2,135,135,7044815,othdiscr,11.906311,25,SHARED3FREE,0.10545807803885715,-1 -56358526,171824,63802,school,2,False,2,135,135,7044815,home,,26,SHARED3FREE,0.10545807803885715,-1 -56358801,171825,63802,othdiscr,1,True,1,131,135,7044850,othdiscr,,29,SHARED3FREE,-0.281769477499857,-1 -56358805,171825,63802,othdiscr,1,False,2,132,131,7044850,social,10.225653,35,SHARED2FREE,-0.20277185632585107,-1 -56358806,171825,63802,othdiscr,2,False,2,135,132,7044850,home,,39,SHARED3FREE,-0.36521793162300004,-1 -56358809,171825,63802,othdiscr,1,True,4,135,135,7044851,othmaint,5.3795877,26,WALK,-0.7460585832595825,-1 -56358810,171825,63802,othdiscr,2,True,4,131,135,7044851,othmaint,5.4266872,27,WALK,-2.0398435592651363,-1 -56358811,171825,63802,othdiscr,3,True,4,130,131,7044851,othmaint,5.7105064,28,WALK,-1.2828608751297,-1 -56358812,171825,63802,othdiscr,4,True,4,130,130,7044851,othdiscr,,28,WALK,-0.78075897693634,-1 -56358813,171825,63802,othdiscr,1,False,1,135,130,7044851,home,,28,WALK,-1.4660019874572756,-1 -56358849,171825,63802,school,1,True,1,135,135,7044856,school,,9,SHARED3FREE,0.10569338088788001,-1 -56358853,171825,63802,school,1,False,1,135,135,7044856,home,,24,SHARED3FREE,0.10569338088788001,-1 -56359177,171826,63802,school,1,True,1,135,135,7044897,school,,10,SHARED3FREE,0.10569338088788001,-1 -56359181,171826,63802,school,1,False,1,135,135,7044897,home,,22,SHARED3FREE,0.10569338088788001,-1 +trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum +37314161,113762,42730,othmaint,1,TRUE,1,106,103,4664270,othmaint,,10,DRIVEALONEFREE,-0.356781572 +37314165,113762,42730,othmaint,1,FALSE,1,103,106,4664270,home,,11,DRIVEALONEFREE,-0.356460303 +38194977,116448,43843,atwork,1,TRUE,1,106,101,4774372,atwork,,20,DRIVEALONEFREE,-0.321751714 +38194981,116448,43843,atwork,1,FALSE,1,101,106,4774372,work,,21,DRIVEALONEFREE,-0.321751714 +38195065,116449,43843,othdiscr,1,TRUE,1,106,103,4774383,othdiscr,,32,SHARED2FREE,0.7593915111282218 +38195069,116449,43843,othdiscr,1,FALSE,1,103,106,4774383,home,,37,SHARED2FREE,0.7593915111282218 +38195257,116448,43843,work,1,TRUE,2,107,103,4774407,othmaint,9.244319,10,DRIVEALONEFREE,-0.667137086 +38195258,116448,43843,work,2,TRUE,2,101,107,4774407,work,,10,DRIVEALONEFREE,-0.589384019 +38195261,116448,43843,work,1,FALSE,1,103,101,4774407,home,,30,DRIVEALONEFREE,-0.501271665 +38195585,116449,43843,work,1,TRUE,2,106,103,4774448,othmaint,10.644734,12,DRIVEALONEFREE,0.050863062 +38195586,116449,43843,work,2,TRUE,2,102,106,4774448,work,,13,DRIVEALONEFREE,0.032545052 +38195589,116449,43843,work,1,FALSE,3,103,102,4774448,othmaint,10.796497,23,SHARED2FREE,0.09835211 +38195590,116449,43843,work,2,FALSE,3,103,103,4774448,work,12.367123,24,DRIVEALONEFREE,0.24223826711784288 +38195591,116449,43843,work,3,FALSE,3,103,103,4774448,home,,26,DRIVEALONEFREE,0.2401515927071465 +38195849,116450,43843,school,1,TRUE,1,106,103,4774481,school,,9,SCHOOL_BUS,4.351044654846191 +38195853,116450,43843,school,1,FALSE,1,103,106,4774481,home,,27,SCHOOL_BUS,4.351044654846191 +38195865,116450,43843,shopping,1,TRUE,1,101,103,4774483,shopping,,27,SHARED2FREE,-0.444101901 +38195869,116450,43843,shopping,1,FALSE,1,103,101,4774483,home,,30,SHARED2FREE,-0.457490895 +39613905,120774,45311,atwork,1,TRUE,1,101,102,4951738,atwork,,20,DRIVEALONEFREE,-0.411282897 +39613909,120774,45311,atwork,1,FALSE,1,102,101,4951738,work,,21,DRIVEALONEFREE,-0.411925524 +39614185,120774,45311,work,1,TRUE,2,106,105,4951773,work,10.647319,10,DRIVEALONEFREE,-0.432832956 +39614186,120774,45311,work,2,TRUE,2,102,106,4951773,work,,11,DRIVEALONEFREE,-0.348032892 +39614189,120774,45311,work,1,FALSE,1,105,102,4951773,home,,30,DRIVEALONEFREE,-0.604685664 +39614513,120775,45311,work,1,TRUE,1,101,105,4951814,work,,9,DRIVEALONEFREE,-0.600943565 +39614517,120775,45311,work,1,FALSE,3,101,101,4951814,work,10.767546,28,DRIVEALONEFREE,-0.356709987 +39614518,120775,45311,work,2,FALSE,3,107,101,4951814,othmaint,9.370711,28,DRIVEALONEFREE,-0.595682502 +39614519,120775,45311,work,3,FALSE,3,105,107,4951814,home,,29,DRIVEALONEFREE,-0.43356654 +40387937,123133,46056,work,1,TRUE,1,106,106,5048492,work,,20,DRIVEALONEFREE,-0.19777289 +40387941,123133,46056,work,1,FALSE,1,106,106,5048492,home,,40,DRIVEALONEFREE,-0.197402388 +43308361,132037,49258,othmaint,1,TRUE,1,122,110,5413545,othmaint,,23,DRIVEALONEFREE,-0.739005089 +43308365,132037,49258,othmaint,1,FALSE,2,114,122,5413545,eatout,8.7858,24,DRIVEALONEFREE,-0.517582178 +43308366,132037,49258,othmaint,2,FALSE,2,110,114,5413545,home,,24,DRIVEALONEFREE,-0.593839884 +43308537,132038,49258,escort,1,TRUE,1,107,110,5413567,escort,,10,SHARED3FREE,-0.00260142 +43308541,132038,49258,escort,1,FALSE,1,110,107,5413567,home,,22,SHARED3FREE,-0.00260142 +44930737,136983,50912,work,1,TRUE,2,123,112,5616342,eatout,9.353397,31,DRIVEALONEFREE,-0.549332976 +44930738,136983,50912,work,2,TRUE,2,104,123,5616342,work,,32,DRIVEALONEFREE,-0.666611016 +44930741,136983,50912,work,1,FALSE,2,112,104,5616342,social,11.149774,34,DRIVEALONEFREE,-0.530267 +44930742,136983,50912,work,2,FALSE,2,112,112,5616342,home,,34,DRIVEALONEFREE,-0.183315992 +44931065,136984,50912,work,1,TRUE,2,101,112,5616383,shopping,9.520916,11,DRIVEALONEFREE,-0.612947822 +44931066,136984,50912,work,2,TRUE,2,107,101,5616383,work,,12,DRIVEALONEFREE,-0.619394481 +44931069,136984,50912,work,1,FALSE,3,123,107,5616383,work,10.775923,28,DRIVEALONEFREE,-0.765127003 +44931070,136984,50912,work,2,FALSE,3,104,123,5616383,escort,9.519634,29,DRIVEALONEFREE,-0.666611016 +44931071,136984,50912,work,3,FALSE,3,112,104,5616383,home,,30,DRIVEALONEFREE,-0.549903512 +47621473,145187,53716,othmaint,1,TRUE,3,121,116,5952684,social,9.947862,8,SHARED3FREE,-0.419557299 +47621474,145187,53716,othmaint,2,TRUE,3,112,121,5952684,othmaint,9.261029,11,SHARED3FREE,-0.642273009 +47621475,145187,53716,othmaint,3,TRUE,3,122,112,5952684,othmaint,,11,SHARED3FREE,-0.641908288 +47621477,145187,53716,othmaint,1,FALSE,1,116,122,5952684,home,,20,SHARED3FREE,-0.613462986 +47621737,145188,53716,escort,1,TRUE,1,114,116,5952717,escort,,29,DRIVEALONEFREE,-0.150837969 +47621741,145188,53716,escort,1,FALSE,1,116,114,5952717,home,,30,SHARED2FREE,-0.151798129 +47622241,145189,53716,school,1,TRUE,1,114,116,5952780,school,,10,SCHOOL_BUS,4.3079237937927255 +47622245,145189,53716,school,1,FALSE,1,116,114,5952780,home,,24,SCHOOL_BUS,4.3079237937927255 +47622569,145190,53716,school,1,TRUE,1,114,116,5952821,school,,9,SHARED2FREE,-0.206179043 +47622573,145190,53716,school,1,FALSE,1,116,114,5952821,home,,24,SHARED2FREE,-0.205685001 +48258513,147129,54342,othdiscr,1,TRUE,1,116,117,6032314,othdiscr,,27,DRIVEALONEFREE,-0.524616778 +48258517,147129,54342,othdiscr,1,FALSE,1,117,116,6032314,home,,33,DRIVEALONEFREE,-0.49120158 +48258537,147129,54342,othmaint,1,TRUE,1,114,117,6032317,othmaint,,34,DRIVEALONEFREE,-0.68713218 +48258541,147129,54342,othmaint,1,FALSE,2,114,114,6032317,shopping,9.148774,37,DRIVEALONEFREE,-0.423736095 +48258542,147129,54342,othmaint,2,FALSE,2,117,114,6032317,home,,38,DRIVEALONEFREE,-0.684561789 +56357665,171822,63802,eatout,1,TRUE,1,127,135,7044708,eatout,,31,DRIVEALONEFREE,-0.652624547 +56357669,171822,63802,eatout,1,FALSE,1,135,127,7044708,home,,34,DRIVEALONEFREE,-0.634310424 +56357689,171822,63802,escort,1,TRUE,1,135,135,7044711,escort,,28,SHARED3FREE,0.077063243 +56357693,171822,63802,escort,1,FALSE,2,135,135,7044711,escort,11.356267,28,SHARED3FREE,0.077063243 +56357694,171822,63802,escort,2,FALSE,2,135,135,7044711,home,,28,SHARED3FREE,0.077063243 +56357737,171822,63802,othdiscr,1,TRUE,3,131,135,7044717,othdiscr,12.194779,13,SHARED2FREE,0.599977655 +56357738,171822,63802,othdiscr,2,TRUE,3,130,131,7044717,shopping,13.357507,14,SHARED2FREE,0.6200047250329787 +56357739,171822,63802,othdiscr,3,TRUE,3,130,130,7044717,othdiscr,,14,SHARED2FREE,0.6960546579187884 +56357741,171822,63802,othdiscr,1,FALSE,1,135,130,7044717,home,,14,SHARED2FREE,0.6487159186367744 +56358209,171823,63802,shopping,1,TRUE,4,131,135,7044776,othmaint,10.342613,24,SHARED3FREE,-0.146198375 +56358210,171823,63802,shopping,2,TRUE,4,131,131,7044776,social,12.281772,25,SHARED3FREE,-0.012169709 +56358211,171823,63802,shopping,3,TRUE,4,131,131,7044776,shopping,11.556939,26,SHARED3FREE,-0.012169709 +56358212,171823,63802,shopping,4,TRUE,4,131,131,7044776,shopping,,26,SHARED3FREE,-0.012169709 +56358213,171823,63802,shopping,1,FALSE,1,135,131,7044776,home,,27,DRIVEALONEFREE,-0.150955944 +56358473,171824,63802,othdiscr,1,TRUE,1,131,135,7044809,othdiscr,,32,SHARED2FREE,-0.460241494 +56358477,171824,63802,othdiscr,1,FALSE,1,135,131,7044809,home,,37,SHARED2FREE,-0.453292992 +56358521,171824,63802,school,1,TRUE,2,135,135,7044815,escort,11.635028,10,SHARED2FREE,0.10569338088788001 +56358522,171824,63802,school,2,TRUE,2,135,135,7044815,school,,10,SHARED3FREE,0.10569338088788001 +56358525,171824,63802,school,1,FALSE,2,135,135,7044815,othdiscr,11.906311,25,SHARED3FREE,0.10545807803885715 +56358526,171824,63802,school,2,FALSE,2,135,135,7044815,home,,26,SHARED3FREE,0.10545807803885715 +56358801,171825,63802,othdiscr,1,TRUE,1,131,135,7044850,othdiscr,,29,SHARED3FREE,-0.281769477 +56358805,171825,63802,othdiscr,1,FALSE,2,132,131,7044850,social,10.225653,35,SHARED2FREE,-0.202771856 +56358806,171825,63802,othdiscr,2,FALSE,2,135,132,7044850,home,,39,SHARED3FREE,-0.365217932 +56358809,171825,63802,othdiscr,1,TRUE,4,135,135,7044851,othmaint,5.3795877,26,WALK,-0.746058583 +56358810,171825,63802,othdiscr,2,TRUE,4,131,135,7044851,othmaint,5.4266872,27,WALK,-2.039843559 +56358811,171825,63802,othdiscr,3,TRUE,4,130,131,7044851,othmaint,5.7105064,28,WALK,-1.282860875 +56358812,171825,63802,othdiscr,4,TRUE,4,130,130,7044851,othdiscr,,28,WALK,-0.780758977 +56358813,171825,63802,othdiscr,1,FALSE,1,135,130,7044851,home,,28,WALK,-1.466001987 +56358849,171825,63802,school,1,TRUE,1,135,135,7044856,school,,9,SHARED3FREE,0.10569338088788001 +56358853,171825,63802,school,1,FALSE,1,135,135,7044856,home,,24,SHARED3FREE,0.10569338088788001 +56359177,171826,63802,school,1,TRUE,1,135,135,7044897,school,,10,SHARED3FREE,0.10569338088788001 +56359181,171826,63802,school,1,FALSE,1,135,135,7044897,home,,22,SHARED3FREE,0.10569338088788001 diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv b/activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv index 3cfe9e642a..0bd93ac3e8 100644 --- a/activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv +++ b/activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv @@ -1,91 +1,91 @@ -trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum,parking_zone_id -37314161,113762,42730,othmaint,1,True,1,106,103,4664270,othmaint,,10,DRIVEALONEFREE,-0.3567815721035004,-1 -37314165,113762,42730,othmaint,1,False,1,103,106,4664270,home,,11,DRIVEALONEFREE,-0.356460303068161,-1 -38194977,116448,43843,atwork,1,True,1,106,101,4774372,atwork,,20,DRIVEALONEFREE,-0.3217517137527465,-1 -38194981,116448,43843,atwork,1,False,1,101,106,4774372,work,,21,DRIVEALONEFREE,-0.3217517137527465,-1 -38195065,116449,43843,othdiscr,1,True,1,106,103,4774383,othdiscr,,32,SHARED2FREE,0.7593915111282218,-1 -38195069,116449,43843,othdiscr,1,False,1,103,106,4774383,home,,37,SHARED2FREE,0.7593915111282218,-1 -38195257,116448,43843,work,1,True,2,107,103,4774407,othmaint,9.244319,10,DRIVEALONEFREE,-0.6671370863914491,-1 -38195258,116448,43843,work,2,True,2,101,107,4774407,work,,10,DRIVEALONEFREE,-0.5893840193748475,-1 -38195261,116448,43843,work,1,False,1,103,101,4774407,home,,30,DRIVEALONEFREE,-0.5012716650962832,-1 -38195585,116449,43843,work,1,True,2,106,103,4774448,othmaint,10.644734,12,DRIVEALONEFREE,0.05086306230852542,-1 -38195586,116449,43843,work,2,True,2,102,106,4774448,work,,13,DRIVEALONEFREE,0.03254505218598833,-1 -38195589,116449,43843,work,1,False,3,103,102,4774448,othmaint,10.796497,23,SHARED2FREE,0.0983521099924028,-1 -38195590,116449,43843,work,2,False,3,103,103,4774448,work,12.367123,24,DRIVEALONEFREE,0.24223826711784288,-1 -38195591,116449,43843,work,3,False,3,103,103,4774448,home,,26,DRIVEALONEFREE,0.2401515927071465,-1 -38195849,116450,43843,school,1,True,1,106,103,4774481,school,,9,SCHOOL_BUS,4.351044654846191,-1 -38195853,116450,43843,school,1,False,1,103,106,4774481,home,,27,SCHOOL_BUS,4.351044654846191,-1 -38195865,116450,43843,shopping,1,True,1,101,103,4774483,shopping,,27,SHARED2FREE,-0.4441019010696936,-1 -38195869,116450,43843,shopping,1,False,1,103,101,4774483,home,,30,SHARED2FREE,-0.45749089544283433,-1 -39613905,120774,45311,atwork,1,True,1,101,102,4951738,atwork,,20,DRIVEALONEFREE,-0.41128289699554443,-1 -39613909,120774,45311,atwork,1,False,1,102,101,4951738,work,,21,DRIVEALONEFREE,-0.4119255244731903,-1 -39614185,120774,45311,work,1,True,2,106,105,4951773,work,10.647319,10,DRIVEALONEFREE,-0.4328329563140868,-1 -39614186,120774,45311,work,2,True,2,102,106,4951773,work,,11,DRIVEALONEFREE,-0.34803289175033575,-1 -39614189,120774,45311,work,1,False,1,105,102,4951773,home,,30,DRIVEALONEFREE,-0.604685664176941,-1 -39614513,120775,45311,work,1,True,1,101,105,4951814,work,,9,DRIVEALONEFREE,-0.6009435653686525,-1 -39614517,120775,45311,work,1,False,3,101,101,4951814,work,10.767546,28,DRIVEALONEFREE,-0.3567099869251252,-1 -39614518,120775,45311,work,2,False,3,107,101,4951814,othmaint,9.370711,28,DRIVEALONEFREE,-0.5956825017929079,-1 -39614519,120775,45311,work,3,False,3,105,107,4951814,home,,29,DRIVEALONEFREE,-0.43356654047966,-1 -40387937,123133,46056,work,1,True,1,106,106,5048492,work,,20,DRIVEALONEFREE,-0.19777289032936102,-1 -40387941,123133,46056,work,1,False,1,106,106,5048492,home,,40,DRIVEALONEFREE,-0.1974023878574371,-1 -43308361,132037,49258,othmaint,1,True,1,122,110,5413545,othmaint,,23,DRIVEALONEFREE,-0.7390050888061525,-1 -43308365,132037,49258,othmaint,1,False,2,114,122,5413545,eatout,8.7858,24,DRIVEALONEFREE,-0.5175821781158448,-1 -43308366,132037,49258,othmaint,2,False,2,110,114,5413545,home,,24,DRIVEALONEFREE,-0.5938398838043213,-1 -43308537,132038,49258,escort,1,True,1,107,110,5413567,escort,,10,SHARED3FREE,-0.002601420005322437,-1 -43308541,132038,49258,escort,1,False,1,110,107,5413567,home,,22,SHARED3FREE,-0.002601420005322437,-1 -44930737,136983,50912,work,1,True,2,123,112,5616342,eatout,9.353397,31,DRIVEALONEFREE,-0.5493329763412477,-1 -44930738,136983,50912,work,2,True,2,104,123,5616342,work,,32,DRIVEALONEFREE,-0.6666110157966614,-1 -44930741,136983,50912,work,1,False,2,112,104,5616342,social,11.149774,34,DRIVEALONEFREE,-0.5302670001983643,-1 -44930742,136983,50912,work,2,False,2,112,112,5616342,home,,34,DRIVEALONEFREE,-0.18331599235534674,-1 -44931065,136984,50912,work,1,True,2,101,112,5616383,shopping,9.520916,11,DRIVEALONEFREE,-0.6129478216171266,-1 -44931066,136984,50912,work,2,True,2,107,101,5616383,work,,12,DRIVEALONEFREE,-0.6193944811820985,-1 -44931069,136984,50912,work,1,False,3,123,107,5616383,work,10.775923,28,DRIVEALONEFREE,-0.7651270031929017,-1 -44931070,136984,50912,work,2,False,3,104,123,5616383,escort,9.519634,29,DRIVEALONEFREE,-0.6666110157966614,-1 -44931071,136984,50912,work,3,False,3,112,104,5616383,home,,30,DRIVEALONEFREE,-0.5499035120010376,-1 -47621473,145187,53716,othmaint,1,True,3,121,116,5952684,social,9.947862,8,SHARED3FREE,-0.41955729937135083,-1 -47621474,145187,53716,othmaint,2,True,3,112,121,5952684,othmaint,9.261029,11,SHARED3FREE,-0.6422730088233947,-1 -47621475,145187,53716,othmaint,3,True,3,122,112,5952684,othmaint,,11,SHARED3FREE,-0.6419082880020143,-1 -47621477,145187,53716,othmaint,1,False,1,116,122,5952684,home,,20,SHARED3FREE,-0.6134629858242939,-1 -47621737,145188,53716,escort,1,True,1,114,116,5952717,escort,,29,DRIVEALONEFREE,-0.15083796859645277,-1 -47621741,145188,53716,escort,1,False,1,116,114,5952717,home,,30,SHARED2FREE,-0.15179812895272474,-1 -47622241,145189,53716,school,1,True,1,114,116,5952780,school,,10,SCHOOL_BUS,4.3079237937927255,-1 -47622245,145189,53716,school,1,False,1,116,114,5952780,home,,24,SCHOOL_BUS,4.3079237937927255,-1 -47622569,145190,53716,school,1,True,1,114,116,5952821,school,,9,SHARED2FREE,-0.20617904275545365,-1 -47622573,145190,53716,school,1,False,1,116,114,5952821,home,,24,SHARED2FREE,-0.20568500108204935,-1 -48258513,147129,54342,othdiscr,1,True,1,116,117,6032314,othdiscr,,27,DRIVEALONEFREE,-0.5246167778968812,-1 -48258517,147129,54342,othdiscr,1,False,1,117,116,6032314,home,,33,DRIVEALONEFREE,-0.49120157957077026,-1 -48258537,147129,54342,othmaint,1,True,1,114,117,6032317,othmaint,,34,DRIVEALONEFREE,-0.687132179737091,-1 -48258541,147129,54342,othmaint,1,False,2,114,114,6032317,shopping,9.148774,37,DRIVEALONEFREE,-0.42373609542846685,-1 -48258542,147129,54342,othmaint,2,False,2,117,114,6032317,home,,38,DRIVEALONEFREE,-0.6845617890357972,-1 -56357665,171822,63802,eatout,1,True,1,127,135,7044708,eatout,,31,DRIVEALONEFREE,-0.6526245474815369,-1 -56357669,171822,63802,eatout,1,False,1,135,127,7044708,home,,34,DRIVEALONEFREE,-0.6343104243278503,-1 -56357689,171822,63802,escort,1,True,1,135,135,7044711,escort,,28,SHARED3FREE,0.07706324286670248,-1 -56357693,171822,63802,escort,1,False,2,135,135,7044711,escort,11.356267,28,SHARED3FREE,0.07706324286670248,-1 -56357694,171822,63802,escort,2,False,2,135,135,7044711,home,,28,SHARED3FREE,0.07706324286670248,-1 -56357737,171822,63802,othdiscr,1,True,3,131,135,7044717,othdiscr,12.194779,13,SHARED2FREE,0.599977654783949,-1 -56357738,171822,63802,othdiscr,2,True,3,130,131,7044717,shopping,13.357507,14,SHARED2FREE,0.6200047250329787,-1 -56357739,171822,63802,othdiscr,3,True,3,130,130,7044717,othdiscr,,14,SHARED2FREE,0.6960546579187884,-1 -56357741,171822,63802,othdiscr,1,False,1,135,130,7044717,home,,14,SHARED2FREE,0.6487159186367744,-1 -56358209,171823,63802,shopping,1,True,4,131,135,7044776,othmaint,10.342613,24,SHARED3FREE,-0.14619837454037923,-1 -56358210,171823,63802,shopping,2,True,4,131,131,7044776,social,12.281772,25,SHARED3FREE,-0.012169709209450414,-1 -56358211,171823,63802,shopping,3,True,4,131,131,7044776,shopping,11.556939,26,SHARED3FREE,-0.012169709209450414,-1 -56358212,171823,63802,shopping,4,True,4,131,131,7044776,shopping,,26,SHARED3FREE,-0.012169709209450414,-1 -56358213,171823,63802,shopping,1,False,1,135,131,7044776,home,,27,DRIVEALONEFREE,-0.15095594351539895,-1 -56358473,171824,63802,othdiscr,1,True,1,131,135,7044809,othdiscr,,32,SHARED2FREE,-0.46024149381952484,-1 -56358477,171824,63802,othdiscr,1,False,1,135,131,7044809,home,,37,SHARED2FREE,-0.45329299190068956,-1 -56358521,171824,63802,school,1,True,2,135,135,7044815,escort,11.635028,10,SHARED2FREE,0.10569338088788001,-1 -56358522,171824,63802,school,2,True,2,135,135,7044815,school,,10,SHARED3FREE,0.10569338088788001,-1 -56358525,171824,63802,school,1,False,2,135,135,7044815,othdiscr,11.906311,25,SHARED3FREE,0.10545807803885715,-1 -56358526,171824,63802,school,2,False,2,135,135,7044815,home,,26,SHARED3FREE,0.10545807803885715,-1 -56358801,171825,63802,othdiscr,1,True,1,131,135,7044850,othdiscr,,29,SHARED3FREE,-0.281769477499857,-1 -56358805,171825,63802,othdiscr,1,False,2,132,131,7044850,social,10.225653,35,SHARED2FREE,-0.20277185632585107,-1 -56358806,171825,63802,othdiscr,2,False,2,135,132,7044850,home,,39,SHARED3FREE,-0.36521793162300004,-1 -56358809,171825,63802,othdiscr,1,True,4,135,135,7044851,othmaint,5.3795877,26,WALK,-0.7460585832595825,-1 -56358810,171825,63802,othdiscr,2,True,4,131,135,7044851,othmaint,5.4266872,27,WALK,-2.0398435592651363,-1 -56358811,171825,63802,othdiscr,3,True,4,130,131,7044851,othmaint,5.7105064,28,WALK,-1.2828608751297,-1 -56358812,171825,63802,othdiscr,4,True,4,130,130,7044851,othdiscr,,28,WALK,-0.78075897693634,-1 -56358813,171825,63802,othdiscr,1,False,1,135,130,7044851,home,,28,WALK,-1.4660019874572756,-1 -56358849,171825,63802,school,1,True,1,135,135,7044856,school,,9,SHARED3FREE,0.10569338088788001,-1 -56358853,171825,63802,school,1,False,1,135,135,7044856,home,,24,SHARED3FREE,0.10569338088788001,-1 -56359177,171826,63802,school,1,True,1,135,135,7044897,school,,10,SHARED3FREE,0.10569338088788001,-1 -56359181,171826,63802,school,1,False,1,135,135,7044897,home,,22,SHARED3FREE,0.10569338088788001,-1 +trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum +37314161,113762,42730,othmaint,1,TRUE,1,106,103,4664270,othmaint,,10,DRIVEALONEFREE,-0.356781572 +37314165,113762,42730,othmaint,1,FALSE,1,103,106,4664270,home,,11,DRIVEALONEFREE,-0.356460303 +38194977,116448,43843,atwork,1,TRUE,1,106,101,4774372,atwork,,20,DRIVEALONEFREE,-0.321751714 +38194981,116448,43843,atwork,1,FALSE,1,101,106,4774372,work,,21,DRIVEALONEFREE,-0.321751714 +38195065,116449,43843,othdiscr,1,TRUE,1,106,103,4774383,othdiscr,,32,SHARED2FREE,0.7593915111282218 +38195069,116449,43843,othdiscr,1,FALSE,1,103,106,4774383,home,,37,SHARED2FREE,0.7593915111282218 +38195257,116448,43843,work,1,TRUE,2,107,103,4774407,othmaint,9.244319,10,DRIVEALONEFREE,-0.667137086 +38195258,116448,43843,work,2,TRUE,2,101,107,4774407,work,,10,DRIVEALONEFREE,-0.589384019 +38195261,116448,43843,work,1,FALSE,1,103,101,4774407,home,,30,DRIVEALONEFREE,-0.501271665 +38195585,116449,43843,work,1,TRUE,2,106,103,4774448,othmaint,10.644734,12,DRIVEALONEFREE,0.050863062 +38195586,116449,43843,work,2,TRUE,2,102,106,4774448,work,,13,DRIVEALONEFREE,0.032545052 +38195589,116449,43843,work,1,FALSE,3,103,102,4774448,othmaint,10.796497,23,SHARED2FREE,0.09835211 +38195590,116449,43843,work,2,FALSE,3,103,103,4774448,work,12.367123,24,DRIVEALONEFREE,0.24223826711784288 +38195591,116449,43843,work,3,FALSE,3,103,103,4774448,home,,26,DRIVEALONEFREE,0.2401515927071465 +38195849,116450,43843,school,1,TRUE,1,106,103,4774481,school,,9,SCHOOL_BUS,4.351044654846191 +38195853,116450,43843,school,1,FALSE,1,103,106,4774481,home,,27,SCHOOL_BUS,4.351044654846191 +38195865,116450,43843,shopping,1,TRUE,1,101,103,4774483,shopping,,27,SHARED2FREE,-0.444101901 +38195869,116450,43843,shopping,1,FALSE,1,103,101,4774483,home,,30,SHARED2FREE,-0.457490895 +39613905,120774,45311,atwork,1,TRUE,1,101,102,4951738,atwork,,20,DRIVEALONEFREE,-0.411282897 +39613909,120774,45311,atwork,1,FALSE,1,102,101,4951738,work,,21,DRIVEALONEFREE,-0.411925524 +39614185,120774,45311,work,1,TRUE,2,106,105,4951773,work,10.647319,10,DRIVEALONEFREE,-0.432832956 +39614186,120774,45311,work,2,TRUE,2,102,106,4951773,work,,11,DRIVEALONEFREE,-0.348032892 +39614189,120774,45311,work,1,FALSE,1,105,102,4951773,home,,30,DRIVEALONEFREE,-0.604685664 +39614513,120775,45311,work,1,TRUE,1,101,105,4951814,work,,9,DRIVEALONEFREE,-0.600943565 +39614517,120775,45311,work,1,FALSE,3,101,101,4951814,work,10.767546,28,DRIVEALONEFREE,-0.356709987 +39614518,120775,45311,work,2,FALSE,3,107,101,4951814,othmaint,9.370711,28,DRIVEALONEFREE,-0.595682502 +39614519,120775,45311,work,3,FALSE,3,105,107,4951814,home,,29,DRIVEALONEFREE,-0.43356654 +40387937,123133,46056,work,1,TRUE,1,106,106,5048492,work,,20,DRIVEALONEFREE,-0.19777289 +40387941,123133,46056,work,1,FALSE,1,106,106,5048492,home,,40,DRIVEALONEFREE,-0.197402388 +43308361,132037,49258,othmaint,1,TRUE,1,122,110,5413545,othmaint,,23,DRIVEALONEFREE,-0.739005089 +43308365,132037,49258,othmaint,1,FALSE,2,114,122,5413545,eatout,8.7858,24,DRIVEALONEFREE,-0.517582178 +43308366,132037,49258,othmaint,2,FALSE,2,110,114,5413545,home,,24,DRIVEALONEFREE,-0.593839884 +43308537,132038,49258,escort,1,TRUE,1,107,110,5413567,escort,,10,SHARED3FREE,-0.00260142 +43308541,132038,49258,escort,1,FALSE,1,110,107,5413567,home,,22,SHARED3FREE,-0.00260142 +44930737,136983,50912,work,1,TRUE,2,123,112,5616342,eatout,9.353397,31,DRIVEALONEFREE,-0.549332976 +44930738,136983,50912,work,2,TRUE,2,104,123,5616342,work,,32,DRIVEALONEFREE,-0.666611016 +44930741,136983,50912,work,1,FALSE,2,112,104,5616342,social,11.149774,34,DRIVEALONEFREE,-0.530267 +44930742,136983,50912,work,2,FALSE,2,112,112,5616342,home,,34,DRIVEALONEFREE,-0.183315992 +44931065,136984,50912,work,1,TRUE,2,101,112,5616383,shopping,9.520916,11,DRIVEALONEFREE,-0.612947822 +44931066,136984,50912,work,2,TRUE,2,107,101,5616383,work,,12,DRIVEALONEFREE,-0.619394481 +44931069,136984,50912,work,1,FALSE,3,123,107,5616383,work,10.775923,28,DRIVEALONEFREE,-0.765127003 +44931070,136984,50912,work,2,FALSE,3,104,123,5616383,escort,9.519634,29,DRIVEALONEFREE,-0.666611016 +44931071,136984,50912,work,3,FALSE,3,112,104,5616383,home,,30,DRIVEALONEFREE,-0.549903512 +47621473,145187,53716,othmaint,1,TRUE,3,121,116,5952684,social,9.947862,8,SHARED3FREE,-0.419557299 +47621474,145187,53716,othmaint,2,TRUE,3,112,121,5952684,othmaint,9.261029,11,SHARED3FREE,-0.642273009 +47621475,145187,53716,othmaint,3,TRUE,3,122,112,5952684,othmaint,,11,SHARED3FREE,-0.641908288 +47621477,145187,53716,othmaint,1,FALSE,1,116,122,5952684,home,,20,SHARED3FREE,-0.613462986 +47621737,145188,53716,escort,1,TRUE,1,114,116,5952717,escort,,29,DRIVEALONEFREE,-0.150837969 +47621741,145188,53716,escort,1,FALSE,1,116,114,5952717,home,,30,SHARED2FREE,-0.151798129 +47622241,145189,53716,school,1,TRUE,1,114,116,5952780,school,,10,SCHOOL_BUS,4.3079237937927255 +47622245,145189,53716,school,1,FALSE,1,116,114,5952780,home,,24,SCHOOL_BUS,4.3079237937927255 +47622569,145190,53716,school,1,TRUE,1,114,116,5952821,school,,9,SHARED2FREE,-0.206179043 +47622573,145190,53716,school,1,FALSE,1,116,114,5952821,home,,24,SHARED2FREE,-0.205685001 +48258513,147129,54342,othdiscr,1,TRUE,1,116,117,6032314,othdiscr,,27,DRIVEALONEFREE,-0.524616778 +48258517,147129,54342,othdiscr,1,FALSE,1,117,116,6032314,home,,33,DRIVEALONEFREE,-0.49120158 +48258537,147129,54342,othmaint,1,TRUE,1,114,117,6032317,othmaint,,34,DRIVEALONEFREE,-0.68713218 +48258541,147129,54342,othmaint,1,FALSE,2,114,114,6032317,shopping,9.148774,37,DRIVEALONEFREE,-0.423736095 +48258542,147129,54342,othmaint,2,FALSE,2,117,114,6032317,home,,38,DRIVEALONEFREE,-0.684561789 +56357665,171822,63802,eatout,1,TRUE,1,127,135,7044708,eatout,,31,DRIVEALONEFREE,-0.652624547 +56357669,171822,63802,eatout,1,FALSE,1,135,127,7044708,home,,34,DRIVEALONEFREE,-0.634310424 +56357689,171822,63802,escort,1,TRUE,1,135,135,7044711,escort,,28,SHARED3FREE,0.077063243 +56357693,171822,63802,escort,1,FALSE,2,135,135,7044711,escort,11.356267,28,SHARED3FREE,0.077063243 +56357694,171822,63802,escort,2,FALSE,2,135,135,7044711,home,,28,SHARED3FREE,0.077063243 +56357737,171822,63802,othdiscr,1,TRUE,3,131,135,7044717,othdiscr,12.194779,13,SHARED2FREE,0.599977655 +56357738,171822,63802,othdiscr,2,TRUE,3,130,131,7044717,shopping,13.357507,14,SHARED2FREE,0.6200047250329787 +56357739,171822,63802,othdiscr,3,TRUE,3,130,130,7044717,othdiscr,,14,SHARED2FREE,0.6960546579187884 +56357741,171822,63802,othdiscr,1,FALSE,1,135,130,7044717,home,,14,SHARED2FREE,0.6487159186367744 +56358209,171823,63802,shopping,1,TRUE,4,131,135,7044776,othmaint,10.342613,24,SHARED3FREE,-0.146198375 +56358210,171823,63802,shopping,2,TRUE,4,131,131,7044776,social,12.281772,25,SHARED3FREE,-0.012169709 +56358211,171823,63802,shopping,3,TRUE,4,131,131,7044776,shopping,11.556939,26,SHARED3FREE,-0.012169709 +56358212,171823,63802,shopping,4,TRUE,4,131,131,7044776,shopping,,26,SHARED3FREE,-0.012169709 +56358213,171823,63802,shopping,1,FALSE,1,135,131,7044776,home,,27,DRIVEALONEFREE,-0.150955944 +56358473,171824,63802,othdiscr,1,TRUE,1,131,135,7044809,othdiscr,,32,SHARED2FREE,-0.460241494 +56358477,171824,63802,othdiscr,1,FALSE,1,135,131,7044809,home,,37,SHARED2FREE,-0.453292992 +56358521,171824,63802,school,1,TRUE,2,135,135,7044815,escort,11.635028,10,SHARED2FREE,0.10569338088788001 +56358522,171824,63802,school,2,TRUE,2,135,135,7044815,school,,10,SHARED3FREE,0.10569338088788001 +56358525,171824,63802,school,1,FALSE,2,135,135,7044815,othdiscr,11.906311,25,SHARED3FREE,0.10545807803885715 +56358526,171824,63802,school,2,FALSE,2,135,135,7044815,home,,26,SHARED3FREE,0.10545807803885715 +56358801,171825,63802,othdiscr,1,TRUE,1,131,135,7044850,othdiscr,,29,SHARED3FREE,-0.281769477 +56358805,171825,63802,othdiscr,1,FALSE,2,132,131,7044850,social,10.225653,35,SHARED2FREE,-0.202771856 +56358806,171825,63802,othdiscr,2,FALSE,2,135,132,7044850,home,,39,SHARED3FREE,-0.365217932 +56358809,171825,63802,othdiscr,1,TRUE,4,135,135,7044851,othmaint,5.3795877,26,WALK,-0.746058583 +56358810,171825,63802,othdiscr,2,TRUE,4,131,135,7044851,othmaint,5.4266872,27,WALK,-2.039843559 +56358811,171825,63802,othdiscr,3,TRUE,4,130,131,7044851,othmaint,5.7105064,28,WALK,-1.282860875 +56358812,171825,63802,othdiscr,4,TRUE,4,130,130,7044851,othdiscr,,28,WALK,-0.780758977 +56358813,171825,63802,othdiscr,1,FALSE,1,135,130,7044851,home,,28,WALK,-1.466001987 +56358849,171825,63802,school,1,TRUE,1,135,135,7044856,school,,9,SHARED3FREE,0.10569338088788001 +56358853,171825,63802,school,1,FALSE,1,135,135,7044856,home,,24,SHARED3FREE,0.10569338088788001 +56359177,171826,63802,school,1,TRUE,1,135,135,7044897,school,,10,SHARED3FREE,0.10569338088788001 +56359181,171826,63802,school,1,FALSE,1,135,135,7044897,home,,22,SHARED3FREE,0.10569338088788001 From 6d29f18ecc43afb3638dd678f86c93076bc2f499 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 13:01:59 +1000 Subject: [PATCH 207/298] re-instate arc tests --- .../test/configs_eet/settings.yaml | 3 + .../examples/prototype_arc/test/test_arc.py | 62 +++++++++---------- 2 files changed, 32 insertions(+), 33 deletions(-) create mode 100644 activitysim/examples/prototype_arc/test/configs_eet/settings.yaml diff --git a/activitysim/examples/prototype_arc/test/configs_eet/settings.yaml b/activitysim/examples/prototype_arc/test/configs_eet/settings.yaml new file mode 100644 index 0000000000..08c06d702e --- /dev/null +++ b/activitysim/examples/prototype_arc/test/configs_eet/settings.yaml @@ -0,0 +1,3 @@ +inherit_settings: True + +use_explicit_error_terms: True diff --git a/activitysim/examples/prototype_arc/test/test_arc.py b/activitysim/examples/prototype_arc/test/test_arc.py index 3e637289c5..bdb61ad955 100644 --- a/activitysim/examples/prototype_arc/test/test_arc.py +++ b/activitysim/examples/prototype_arc/test/test_arc.py @@ -13,7 +13,7 @@ from activitysim.core.test import assert_frame_substantively_equal -def _test_arc(recode=False, sharrow=False): +def _test_arc(recode=False, sharrow=False, eet=False): def example_path(dirname): resource = os.path.join("examples", "prototype_arc", dirname) return str(importlib.resources.files("activitysim").joinpath(resource)) @@ -24,9 +24,13 @@ def test_path(dirname): def regress(): if sharrow: # sharrow results in tiny changes (one trip moving one time period earlier) - regress_trips_df = pd.read_csv(test_path("regress/final_trips_sh.csv")) + regress_trips_df = pd.read_csv( + test_path(f"regress/final_trips{'_eet' if eet else ''}_sh.csv") + ) else: - regress_trips_df = pd.read_csv(test_path("regress/final_trips.csv")) + regress_trips_df = pd.read_csv( + test_path(f"regress/final_trips{'_eet' if eet else ''}.csv") + ) final_trips_df = pd.read_csv(test_path("output/final_trips.csv")) # person_id,household_id,tour_id,primary_purpose,trip_num,outbound,trip_count,purpose, @@ -36,39 +40,26 @@ def regress(): file_path = os.path.join(os.path.dirname(__file__), "simulation.py") + test_configs = [] + if eet: + test_configs.extend(["-c", test_path("configs_eet")]) + if recode: - run_args = [ - "-c", - test_path("configs_recode"), - "-c", - example_path("configs"), - "-d", - example_path("data"), - "-o", - test_path("output"), - ] + test_configs.extend(["-c", test_path("configs_recode")]) elif sharrow: - run_args = [ - "-c", - test_path("configs_sharrow"), - "-c", - example_path("configs"), - "-d", - example_path("data"), - "-o", - test_path("output"), - ] + test_configs.extend(["-c", test_path("configs_sharrow")]) else: - run_args = [ - "-c", - test_path("configs"), - "-c", - example_path("configs"), - "-d", - example_path("data"), - "-o", - test_path("output"), - ] + test_configs.extend(["-c", test_path("configs")]) + + run_args = [ + *test_configs, + "-c", + example_path("configs"), + "-d", + example_path("data"), + "-o", + test_path("output"), + ] if os.environ.get("GITHUB_ACTIONS") == "true": subprocess.run(["coverage", "run", "-a", file_path] + run_args, check=True) @@ -82,6 +73,10 @@ def test_arc(): _test_arc() +def test_arc_eet(): + _test_arc(eet=True) + + def test_arc_recode(): _test_arc(recode=True) @@ -92,5 +87,6 @@ def test_arc_sharrow(): if __name__ == "__main__": _test_arc() + _test_arc(eet=True) _test_arc(recode=True) _test_arc(sharrow=True) From 12a3d8acc74816c2abcb6a3fd75ca91440e768da Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 13:05:01 +1000 Subject: [PATCH 208/298] Revert "Deterministic randoms when adding/removing tours for trip scheduling choice" This reverts commit 2d181b2a9ada7d22d7ed8e8f7765360e9150d2ba. --- .../test/test_trip_scheduling_consistency.py | 97 ------------------- .../abm/models/trip_scheduling_choice.py | 51 +++------- .../test_misc/test_trip_scheduling_choice.py | 12 +-- 3 files changed, 17 insertions(+), 143 deletions(-) delete mode 100644 activitysim/abm/models/test/test_trip_scheduling_consistency.py diff --git a/activitysim/abm/models/test/test_trip_scheduling_consistency.py b/activitysim/abm/models/test/test_trip_scheduling_consistency.py deleted file mode 100644 index 8f45cfac16..0000000000 --- a/activitysim/abm/models/test/test_trip_scheduling_consistency.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Tests confirming the SCHEDULE_ID in run_trip_scheduling_choice is not chunk-sensitive. -""" - -import numpy as np -import pandas as pd - -from activitysim.abm.models import trip_scheduling_choice as tsc - - -def _make_two_way_stop_tours(tour_ids, duration=4): - """Return a minimal tours DataFrame where every tour has stops on both legs.""" - n = len(tour_ids) - return pd.DataFrame( - { - tsc.TOUR_DURATION_COLUMN: [duration] * n, - tsc.NUM_OB_STOPS: [1] * n, - tsc.NUM_IB_STOPS: [1] * n, - tsc.HAS_OB_STOPS: [True] * n, - tsc.HAS_IB_STOPS: [True] * n, - }, - index=pd.Index(tour_ids, name="tour_id"), - ) - - -def test_schedule_ids_shift_with_different_co_chunked_tours(): - """ - Confirm that SCHEDULE_IDs assigned to a given tour's alternatives do not change - depending on which other tours are present in the same chunk. - - generate_schedule_alternatives numbers alternatives sequentially starting at 1 - across the full set of input tours. A tour processed alongside tours with lower - IDs will therefore have its alternatives numbered beginning at a higher offset - than if it were processed alone. - """ - tours_both = _make_two_way_stop_tours([0, 1], duration=4) - tours_solo = _make_two_way_stop_tours([1], duration=4) - - alts_both = tsc.generate_schedule_alternatives(tours_both) - alts_solo = tsc.generate_schedule_alternatives(tours_solo) - - ids_with_tour0 = alts_both.loc[alts_both.index == 1, tsc.SCHEDULE_ID].values - ids_without_tour0 = alts_solo.loc[alts_solo.index == 1, tsc.SCHEDULE_ID].values - - # Same number of schedule alternatives for tour 1 regardless of co-tours - assert len(ids_with_tour0) == len(ids_without_tour0), ( - "Tour 1 should have the same number of alternatives whether processed " - "alone or together with tour 0." - ) - - # and the IDs themselves don't differ - assert np.array_equal( - ids_with_tour0, ids_without_tour0 - ), "SCHEDULE_IDs for tour 1 changed when tour 0 was added to the chunk." - - -def test_shifted_schedule_ids_produce_same_gumbel_draws(): - """ - Confirm that the SCHEDULE_ID shift documented in - test_schedule_ids_shift_with_different_co_chunked_tours translates directly - into different Gumbel error terms under the AltsContext indexing scheme. - - add_ev1_random generates a dense array of random numbers with length - alt_info.n_alts_to_cover_max_id, then selects per-alternative values via - np.take_along_axis indexed by the SCHEDULE_IDs. When those IDs change, the - selected values change too — meaning a tour can receive different error terms - (and make a different choice) solely because of who else is in its chunk. - """ - tours_both = _make_two_way_stop_tours([0, 1], duration=4) - tours_solo = _make_two_way_stop_tours([1], duration=4) - - alts_both = tsc.generate_schedule_alternatives(tours_both) - alts_solo = tsc.generate_schedule_alternatives(tours_solo) - - ids_with_tour0 = alts_both.loc[alts_both.index == 1, tsc.SCHEDULE_ID].values - ids_without_tour0 = alts_solo.loc[alts_solo.index == 1, tsc.SCHEDULE_ID].values - - # Reproduce the dense random draw that add_ev1_random would make for tour 1. - # Use a fixed seed to make the test deterministic. - max_alt_id_both = int(alts_both[tsc.SCHEDULE_ID].max()) - rng = np.random.RandomState(42) - # n_alts_to_cover_max_id = max_alt_id + 1 (see AltsContext.__post_init__) - rands_dense = rng.gumbel(size=max_alt_id_both + 1) - - gumbel_with_tour0 = rands_dense[ids_with_tour0] - - # For the solo run, the dense array is shorter; regenerate from the same seed - max_alt_id_solo = int(alts_solo[tsc.SCHEDULE_ID].max()) - rng2 = np.random.RandomState(42) - rands_dense_solo = rng2.gumbel(size=max_alt_id_solo + 1) - - gumbel_without_tour0 = rands_dense_solo[ids_without_tour0] - - assert np.array_equal(gumbel_with_tour0, gumbel_without_tour0), ( - "Gumbel draws for tour 1's alternatives should not differ when SCHEDULE_IDs " - "are shifted by the presence of tour 0." - ) diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index ed316f2279..3eb695feb5 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -84,12 +84,7 @@ def generate_schedule_alternatives(tours): schedules = pd.concat([no_stops, one_way, two_way], sort=True) schedules[SCHEDULE_ID] = np.arange(1, schedules.shape[0] + 1) # this sort is necessary to keep single process and multiprocess results the same! - # sort_values works here because the index is named "tour_id". schedules.sort_values(by=["tour_id", SCHEDULE_ID], inplace=True) - # Promote the named tour_id index to a plain column, then re-index by SCHEDULE_ID - # (drop=False keeps SCHEDULE_ID accessible as a column too). Callers can then - # identify each alternative's tour via the "tour_id" column rather than the index. - schedules = schedules.reset_index().set_index(SCHEDULE_ID, drop=False) return schedules @@ -275,19 +270,6 @@ def run_trip_scheduling_choice( indirect_tours = tours.loc[tours[HAS_OB_STOPS] | tours[HAS_IB_STOPS]] if len(indirect_tours) > 0: - # Generate all schedule alternatives upfront over the full indirect-tour set so - # that SCHEDULE_IDs are globally stable. If we generated per-chunk instead, a - # tour's alternatives would receive different IDs depending on the other tours in - # its chunk, which would cause add_ev1_random to index into a different position - # in the dense Gumbel draw array and produce different (chunk-sensitive) error - # terms for the same tour. - all_schedules = generate_schedule_alternatives(indirect_tours) - # Build the AltsContext once from the global ID range so every chunk uses the - # same dense-random-draw width, giving each alternative a stable Gumbel draw. - global_alts_context = AltsContext( - all_schedules[SCHEDULE_ID].min(), all_schedules[SCHEDULE_ID].max() - ) - # Iterate through the chunks result_list = [] for ( @@ -296,22 +278,14 @@ def run_trip_scheduling_choice( chunk_trace_label, chunk_sizer, ) in chunk.adaptive_chunked_choosers(state, indirect_tours, trace_label): - # Sort the choosers and filter the pre-computed alternatives to this chunk. + # Sort the choosers and get the schedule alternatives choosers = choosers.sort_index() - schedules = all_schedules[ - all_schedules["tour_id"].isin(choosers.index) - ].sort_index() - - # _interaction_sample_simulate requires alternatives indexed by chooser - # (tour_id), so create a view with tour_id as the index. - schedules_for_sim = ( - schedules.reset_index(drop=True).set_index("tour_id").sort_index() - ) + schedules = generate_schedule_alternatives(choosers).sort_index() # preprocessing alternatives expressions.annotate_preprocessors( state, - df=schedules_for_sim, + df=schedules, locals_dict=locals_dict, skims=None, model_settings=model_settings, @@ -321,13 +295,13 @@ def run_trip_scheduling_choice( # Assuming we did the max_alt_size calculation correctly, # we should get the same sizes here. - assert choosers[NUM_ALTERNATIVES].sum() == schedules_for_sim.shape[0] + assert choosers[NUM_ALTERNATIVES].sum() == schedules.shape[0] # Run the simulation choices = _interaction_sample_simulate( state, choosers=choosers, - alternatives=schedules_for_sim, + alternatives=schedules, spec=spec, choice_column=SCHEDULE_ID, allow_zero_probs=False, @@ -341,14 +315,14 @@ def run_trip_scheduling_choice( estimator=None, chunk_sizer=chunk_sizer, compute_settings=model_settings.compute_settings, - alts_context=global_alts_context, + alts_context=AltsContext( + schedules[SCHEDULE_ID].min(), schedules[SCHEDULE_ID].max() + ), ) assert len(choices.index) == len(choosers.index) - # choices is a Series of chosen SCHEDULE_IDs; look them up against the - # SCHEDULE_ID-indexed schedules to retrieve the duration columns. - choices = schedules.loc[schedules[SCHEDULE_ID].isin(choices)] + choices = schedules[schedules[SCHEDULE_ID].isin(choices)] result_list.append(choices) @@ -363,11 +337,8 @@ def run_trip_scheduling_choice( assert len(choices.index) == len(indirect_tours.index) # The choices here are only the indirect tours, so the durations - # need to be updated on the main tour dataframe. Re-index by tour_id - # (stored as a column by generate_schedule_alternatives) for alignment. - tours.update( - choices.set_index("tour_id")[[MAIN_LEG_DURATION, OB_DURATION, IB_DURATION]] - ) + # need to be updated on the main tour dataframe. + tours.update(choices[[MAIN_LEG_DURATION, OB_DURATION, IB_DURATION]]) # Cleanup data types and drop temporary columns tours[[MAIN_LEG_DURATION, OB_DURATION, IB_DURATION]] = tours[ diff --git a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py index 3f1bd6985a..8401c785c1 100644 --- a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py @@ -1,9 +1,11 @@ -import numpy as np -import pandas as pd -import pytest +from __future__ import annotations + import os from pathlib import Path +import numpy as np +import pandas as pd +import pytest from activitysim.abm.models import trip_scheduling_choice as tsc from activitysim.abm.tables.skims import skim_dict @@ -150,9 +152,7 @@ def initialize_network_los() -> bool: def test_generate_schedule_alternatives(tours): windows = tsc.generate_schedule_alternatives(tours) assert windows.shape[0] == 296 - assert ( - windows.shape[1] == 5 - ) # tour_id, schedule_id, main_leg_duration, ob_duration, ib_duration + assert windows.shape[1] == 4 output_columns = [ tsc.SCHEDULE_ID, From 3bd32515232b802d85cfa0672107aada023a29ac Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 13:09:51 +1000 Subject: [PATCH 209/298] no alts_context for trip_scheduling --- activitysim/abm/models/trip_scheduling_choice.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index 3eb695feb5..81d908ef1b 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -20,7 +20,6 @@ ) from activitysim.core.configuration.logit import LogitComponentSettings from activitysim.core.interaction_sample_simulate import _interaction_sample_simulate -from activitysim.core.logit import AltsContext from activitysim.core.skim_dataset import SkimDataset from activitysim.core.skim_dictionary import SkimDict @@ -315,9 +314,6 @@ def run_trip_scheduling_choice( estimator=None, chunk_sizer=chunk_sizer, compute_settings=model_settings.compute_settings, - alts_context=AltsContext( - schedules[SCHEDULE_ID].min(), schedules[SCHEDULE_ID].max() - ), ) assert len(choices.index) == len(choosers.index) From 8d7ae417629d43fdcbc8192055c7b59261c594a6 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 13:25:01 +1000 Subject: [PATCH 210/298] updates arc regress files --- .../test/regress/final_trips.csv | 182 +++++++++--------- .../test/regress/final_trips_eet.csv | 84 ++++++++ 2 files changed, 175 insertions(+), 91 deletions(-) create mode 100644 activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips.csv b/activitysim/examples/prototype_arc/test/regress/final_trips.csv index 0bd93ac3e8..79f1c9b937 100644 --- a/activitysim/examples/prototype_arc/test/regress/final_trips.csv +++ b/activitysim/examples/prototype_arc/test/regress/final_trips.csv @@ -1,91 +1,91 @@ -trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum -37314161,113762,42730,othmaint,1,TRUE,1,106,103,4664270,othmaint,,10,DRIVEALONEFREE,-0.356781572 -37314165,113762,42730,othmaint,1,FALSE,1,103,106,4664270,home,,11,DRIVEALONEFREE,-0.356460303 -38194977,116448,43843,atwork,1,TRUE,1,106,101,4774372,atwork,,20,DRIVEALONEFREE,-0.321751714 -38194981,116448,43843,atwork,1,FALSE,1,101,106,4774372,work,,21,DRIVEALONEFREE,-0.321751714 -38195065,116449,43843,othdiscr,1,TRUE,1,106,103,4774383,othdiscr,,32,SHARED2FREE,0.7593915111282218 -38195069,116449,43843,othdiscr,1,FALSE,1,103,106,4774383,home,,37,SHARED2FREE,0.7593915111282218 -38195257,116448,43843,work,1,TRUE,2,107,103,4774407,othmaint,9.244319,10,DRIVEALONEFREE,-0.667137086 -38195258,116448,43843,work,2,TRUE,2,101,107,4774407,work,,10,DRIVEALONEFREE,-0.589384019 -38195261,116448,43843,work,1,FALSE,1,103,101,4774407,home,,30,DRIVEALONEFREE,-0.501271665 -38195585,116449,43843,work,1,TRUE,2,106,103,4774448,othmaint,10.644734,12,DRIVEALONEFREE,0.050863062 -38195586,116449,43843,work,2,TRUE,2,102,106,4774448,work,,13,DRIVEALONEFREE,0.032545052 -38195589,116449,43843,work,1,FALSE,3,103,102,4774448,othmaint,10.796497,23,SHARED2FREE,0.09835211 -38195590,116449,43843,work,2,FALSE,3,103,103,4774448,work,12.367123,24,DRIVEALONEFREE,0.24223826711784288 -38195591,116449,43843,work,3,FALSE,3,103,103,4774448,home,,26,DRIVEALONEFREE,0.2401515927071465 -38195849,116450,43843,school,1,TRUE,1,106,103,4774481,school,,9,SCHOOL_BUS,4.351044654846191 -38195853,116450,43843,school,1,FALSE,1,103,106,4774481,home,,27,SCHOOL_BUS,4.351044654846191 -38195865,116450,43843,shopping,1,TRUE,1,101,103,4774483,shopping,,27,SHARED2FREE,-0.444101901 -38195869,116450,43843,shopping,1,FALSE,1,103,101,4774483,home,,30,SHARED2FREE,-0.457490895 -39613905,120774,45311,atwork,1,TRUE,1,101,102,4951738,atwork,,20,DRIVEALONEFREE,-0.411282897 -39613909,120774,45311,atwork,1,FALSE,1,102,101,4951738,work,,21,DRIVEALONEFREE,-0.411925524 -39614185,120774,45311,work,1,TRUE,2,106,105,4951773,work,10.647319,10,DRIVEALONEFREE,-0.432832956 -39614186,120774,45311,work,2,TRUE,2,102,106,4951773,work,,11,DRIVEALONEFREE,-0.348032892 -39614189,120774,45311,work,1,FALSE,1,105,102,4951773,home,,30,DRIVEALONEFREE,-0.604685664 -39614513,120775,45311,work,1,TRUE,1,101,105,4951814,work,,9,DRIVEALONEFREE,-0.600943565 -39614517,120775,45311,work,1,FALSE,3,101,101,4951814,work,10.767546,28,DRIVEALONEFREE,-0.356709987 -39614518,120775,45311,work,2,FALSE,3,107,101,4951814,othmaint,9.370711,28,DRIVEALONEFREE,-0.595682502 -39614519,120775,45311,work,3,FALSE,3,105,107,4951814,home,,29,DRIVEALONEFREE,-0.43356654 -40387937,123133,46056,work,1,TRUE,1,106,106,5048492,work,,20,DRIVEALONEFREE,-0.19777289 -40387941,123133,46056,work,1,FALSE,1,106,106,5048492,home,,40,DRIVEALONEFREE,-0.197402388 -43308361,132037,49258,othmaint,1,TRUE,1,122,110,5413545,othmaint,,23,DRIVEALONEFREE,-0.739005089 -43308365,132037,49258,othmaint,1,FALSE,2,114,122,5413545,eatout,8.7858,24,DRIVEALONEFREE,-0.517582178 -43308366,132037,49258,othmaint,2,FALSE,2,110,114,5413545,home,,24,DRIVEALONEFREE,-0.593839884 -43308537,132038,49258,escort,1,TRUE,1,107,110,5413567,escort,,10,SHARED3FREE,-0.00260142 -43308541,132038,49258,escort,1,FALSE,1,110,107,5413567,home,,22,SHARED3FREE,-0.00260142 -44930737,136983,50912,work,1,TRUE,2,123,112,5616342,eatout,9.353397,31,DRIVEALONEFREE,-0.549332976 -44930738,136983,50912,work,2,TRUE,2,104,123,5616342,work,,32,DRIVEALONEFREE,-0.666611016 -44930741,136983,50912,work,1,FALSE,2,112,104,5616342,social,11.149774,34,DRIVEALONEFREE,-0.530267 -44930742,136983,50912,work,2,FALSE,2,112,112,5616342,home,,34,DRIVEALONEFREE,-0.183315992 -44931065,136984,50912,work,1,TRUE,2,101,112,5616383,shopping,9.520916,11,DRIVEALONEFREE,-0.612947822 -44931066,136984,50912,work,2,TRUE,2,107,101,5616383,work,,12,DRIVEALONEFREE,-0.619394481 -44931069,136984,50912,work,1,FALSE,3,123,107,5616383,work,10.775923,28,DRIVEALONEFREE,-0.765127003 -44931070,136984,50912,work,2,FALSE,3,104,123,5616383,escort,9.519634,29,DRIVEALONEFREE,-0.666611016 -44931071,136984,50912,work,3,FALSE,3,112,104,5616383,home,,30,DRIVEALONEFREE,-0.549903512 -47621473,145187,53716,othmaint,1,TRUE,3,121,116,5952684,social,9.947862,8,SHARED3FREE,-0.419557299 -47621474,145187,53716,othmaint,2,TRUE,3,112,121,5952684,othmaint,9.261029,11,SHARED3FREE,-0.642273009 -47621475,145187,53716,othmaint,3,TRUE,3,122,112,5952684,othmaint,,11,SHARED3FREE,-0.641908288 -47621477,145187,53716,othmaint,1,FALSE,1,116,122,5952684,home,,20,SHARED3FREE,-0.613462986 -47621737,145188,53716,escort,1,TRUE,1,114,116,5952717,escort,,29,DRIVEALONEFREE,-0.150837969 -47621741,145188,53716,escort,1,FALSE,1,116,114,5952717,home,,30,SHARED2FREE,-0.151798129 -47622241,145189,53716,school,1,TRUE,1,114,116,5952780,school,,10,SCHOOL_BUS,4.3079237937927255 -47622245,145189,53716,school,1,FALSE,1,116,114,5952780,home,,24,SCHOOL_BUS,4.3079237937927255 -47622569,145190,53716,school,1,TRUE,1,114,116,5952821,school,,9,SHARED2FREE,-0.206179043 -47622573,145190,53716,school,1,FALSE,1,116,114,5952821,home,,24,SHARED2FREE,-0.205685001 -48258513,147129,54342,othdiscr,1,TRUE,1,116,117,6032314,othdiscr,,27,DRIVEALONEFREE,-0.524616778 -48258517,147129,54342,othdiscr,1,FALSE,1,117,116,6032314,home,,33,DRIVEALONEFREE,-0.49120158 -48258537,147129,54342,othmaint,1,TRUE,1,114,117,6032317,othmaint,,34,DRIVEALONEFREE,-0.68713218 -48258541,147129,54342,othmaint,1,FALSE,2,114,114,6032317,shopping,9.148774,37,DRIVEALONEFREE,-0.423736095 -48258542,147129,54342,othmaint,2,FALSE,2,117,114,6032317,home,,38,DRIVEALONEFREE,-0.684561789 -56357665,171822,63802,eatout,1,TRUE,1,127,135,7044708,eatout,,31,DRIVEALONEFREE,-0.652624547 -56357669,171822,63802,eatout,1,FALSE,1,135,127,7044708,home,,34,DRIVEALONEFREE,-0.634310424 -56357689,171822,63802,escort,1,TRUE,1,135,135,7044711,escort,,28,SHARED3FREE,0.077063243 -56357693,171822,63802,escort,1,FALSE,2,135,135,7044711,escort,11.356267,28,SHARED3FREE,0.077063243 -56357694,171822,63802,escort,2,FALSE,2,135,135,7044711,home,,28,SHARED3FREE,0.077063243 -56357737,171822,63802,othdiscr,1,TRUE,3,131,135,7044717,othdiscr,12.194779,13,SHARED2FREE,0.599977655 -56357738,171822,63802,othdiscr,2,TRUE,3,130,131,7044717,shopping,13.357507,14,SHARED2FREE,0.6200047250329787 -56357739,171822,63802,othdiscr,3,TRUE,3,130,130,7044717,othdiscr,,14,SHARED2FREE,0.6960546579187884 -56357741,171822,63802,othdiscr,1,FALSE,1,135,130,7044717,home,,14,SHARED2FREE,0.6487159186367744 -56358209,171823,63802,shopping,1,TRUE,4,131,135,7044776,othmaint,10.342613,24,SHARED3FREE,-0.146198375 -56358210,171823,63802,shopping,2,TRUE,4,131,131,7044776,social,12.281772,25,SHARED3FREE,-0.012169709 -56358211,171823,63802,shopping,3,TRUE,4,131,131,7044776,shopping,11.556939,26,SHARED3FREE,-0.012169709 -56358212,171823,63802,shopping,4,TRUE,4,131,131,7044776,shopping,,26,SHARED3FREE,-0.012169709 -56358213,171823,63802,shopping,1,FALSE,1,135,131,7044776,home,,27,DRIVEALONEFREE,-0.150955944 -56358473,171824,63802,othdiscr,1,TRUE,1,131,135,7044809,othdiscr,,32,SHARED2FREE,-0.460241494 -56358477,171824,63802,othdiscr,1,FALSE,1,135,131,7044809,home,,37,SHARED2FREE,-0.453292992 -56358521,171824,63802,school,1,TRUE,2,135,135,7044815,escort,11.635028,10,SHARED2FREE,0.10569338088788001 -56358522,171824,63802,school,2,TRUE,2,135,135,7044815,school,,10,SHARED3FREE,0.10569338088788001 -56358525,171824,63802,school,1,FALSE,2,135,135,7044815,othdiscr,11.906311,25,SHARED3FREE,0.10545807803885715 -56358526,171824,63802,school,2,FALSE,2,135,135,7044815,home,,26,SHARED3FREE,0.10545807803885715 -56358801,171825,63802,othdiscr,1,TRUE,1,131,135,7044850,othdiscr,,29,SHARED3FREE,-0.281769477 -56358805,171825,63802,othdiscr,1,FALSE,2,132,131,7044850,social,10.225653,35,SHARED2FREE,-0.202771856 -56358806,171825,63802,othdiscr,2,FALSE,2,135,132,7044850,home,,39,SHARED3FREE,-0.365217932 -56358809,171825,63802,othdiscr,1,TRUE,4,135,135,7044851,othmaint,5.3795877,26,WALK,-0.746058583 -56358810,171825,63802,othdiscr,2,TRUE,4,131,135,7044851,othmaint,5.4266872,27,WALK,-2.039843559 -56358811,171825,63802,othdiscr,3,TRUE,4,130,131,7044851,othmaint,5.7105064,28,WALK,-1.282860875 -56358812,171825,63802,othdiscr,4,TRUE,4,130,130,7044851,othdiscr,,28,WALK,-0.780758977 -56358813,171825,63802,othdiscr,1,FALSE,1,135,130,7044851,home,,28,WALK,-1.466001987 -56358849,171825,63802,school,1,TRUE,1,135,135,7044856,school,,9,SHARED3FREE,0.10569338088788001 -56358853,171825,63802,school,1,FALSE,1,135,135,7044856,home,,24,SHARED3FREE,0.10569338088788001 -56359177,171826,63802,school,1,TRUE,1,135,135,7044897,school,,10,SHARED3FREE,0.10569338088788001 -56359181,171826,63802,school,1,FALSE,1,135,135,7044897,home,,22,SHARED3FREE,0.10569338088788001 +"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","depart","trip_mode","mode_choice_logsum","trip_id" +113762,42730,"othmaint",1,true,1,106,103,4664270,"othmaint",,10,"DRIVEALONEFREE",-0.3567815833091734,37314161 +113762,42730,"othmaint",1,false,1,103,106,4664270,"home",,11,"DRIVEALONEFREE",-0.3564603142738344,37314165 +116448,43843,"atwork",1,true,1,106,101,4774372,"atwork",,20,"DRIVEALONEFREE",-0.3217517094135284,38194977 +116448,43843,"atwork",1,false,1,101,106,4774372,"work",,21,"DRIVEALONEFREE",-0.3217517094135284,38194981 +116449,43843,"othdiscr",1,true,1,106,103,4774383,"othdiscr",,32,"SHARED2FREE",0.7593914979829192,38195065 +116449,43843,"othdiscr",1,false,1,103,106,4774383,"home",,37,"SHARED2FREE",0.7593914979829192,38195069 +116448,43843,"work",1,true,2,107,103,4774407,"othmaint",9.244319214996622,10,"DRIVEALONEFREE",-0.6671371741294861,38195257 +116448,43843,"work",2,true,2,101,107,4774407,"work",,11,"DRIVEALONEFREE",-0.5893840121269226,38195258 +116448,43843,"work",1,false,1,103,101,4774407,"home",,30,"DRIVEALONEFREE",-0.5012717045307159,38195261 +116449,43843,"work",1,true,2,106,103,4774448,"othmaint",10.644734946815246,12,"DRIVEALONEFREE",0.05086305622830629,38195585 +116449,43843,"work",2,true,2,102,106,4774448,"work",,15,"DRIVEALONEFREE",0.03291252148410589,38195586 +116449,43843,"work",1,false,3,103,102,4774448,"othmaint",10.796498240479236,24,"SHARED2FREE",0.09835208854623434,38195589 +116449,43843,"work",2,false,3,103,103,4774448,"work",12.367122837815295,26,"DRIVEALONEFREE",0.24015159118542195,38195590 +116449,43843,"work",3,false,3,103,103,4774448,"home",,26,"DRIVEALONEFREE",0.24015159118542195,38195591 +116450,43843,"school",1,true,1,106,103,4774481,"school",,9,"SCHOOL_BUS",4.351044797545671,38195849 +116450,43843,"school",1,false,1,103,106,4774481,"home",,27,"SCHOOL_BUS",4.351044797545671,38195853 +116450,43843,"shopping",1,true,1,101,103,4774483,"shopping",,27,"SHARED2FREE",-0.44410188801130307,38195865 +116450,43843,"shopping",1,false,1,103,101,4774483,"home",,30,"SHARED2FREE",-0.4574908823858229,38195869 +120774,45311,"atwork",1,true,1,101,102,4951738,"atwork",,20,"DRIVEALONEFREE",-0.4112828999996184,39613905 +120774,45311,"atwork",1,false,1,102,101,4951738,"work",,21,"DRIVEALONEFREE",-0.4119254976749421,39613909 +120774,45311,"work",1,true,2,106,105,4951773,"work",10.647318549180723,10,"DRIVEALONEFREE",-0.4328329442501069,39614185 +120774,45311,"work",2,true,2,102,106,4951773,"work",,11,"DRIVEALONEFREE",-0.3480328878879547,39614186 +120774,45311,"work",1,false,1,105,102,4951773,"home",,30,"DRIVEALONEFREE",-0.6046856504917144,39614189 +120775,45311,"work",1,true,1,101,105,4951814,"work",,9,"DRIVEALONEFREE",-0.6009435992240908,39614513 +120775,45311,"work",1,false,3,101,101,4951814,"work",10.767545755383658,26,"DRIVEALONEFREE",-0.35670998854637154,39614517 +120775,45311,"work",2,false,3,107,101,4951814,"othmaint",9.370711100307654,26,"DRIVEALONEFREE",-0.5956824945449828,39614518 +120775,45311,"work",3,false,3,105,107,4951814,"home",,29,"DRIVEALONEFREE",-0.43356653208732604,39614519 +123133,46056,"work",1,true,1,106,106,5048492,"work",,20,"DRIVEALONEFREE",-0.19777291302680963,40387937 +123133,46056,"work",1,false,1,106,106,5048492,"home",,40,"DRIVEALONEFREE",-0.1974023956537246,40387941 +132037,49258,"othmaint",1,true,1,122,110,5413545,"othmaint",,23,"DRIVEALONEFREE",-0.7390051318168641,43308361 +132037,49258,"othmaint",1,false,2,114,122,5413545,"eatout",8.785799297132586,24,"DRIVEALONEFREE",-0.5175821724891663,43308365 +132037,49258,"othmaint",2,false,2,110,114,5413545,"home",,24,"DRIVEALONEFREE",-0.5938398692131043,43308366 +132038,49258,"escort",1,true,1,107,110,5413567,"escort",,10,"SHARED3FREE",-0.002601425939803153,43308537 +132038,49258,"escort",1,false,1,110,107,5413567,"home",,22,"SHARED3FREE",-0.002601425939803153,43308541 +136983,50912,"work",1,true,2,123,112,5616342,"eatout",9.353397383302754,31,"DRIVEALONEFREE",-0.5493329919815063,44930737 +136983,50912,"work",2,true,2,104,123,5616342,"work",,32,"DRIVEALONEFREE",-0.6666110144615174,44930738 +136983,50912,"work",1,false,2,112,104,5616342,"social",11.149774183428809,32,"DRIVEALONEFREE",-0.5499035404682159,44930741 +136983,50912,"work",2,false,2,112,112,5616342,"home",,34,"DRIVEALONEFREE",-0.18331599397659298,44930742 +136984,50912,"work",1,true,2,101,112,5616383,"shopping",9.520915574738705,11,"DRIVEALONEFREE",-0.6129478299617769,44931065 +136984,50912,"work",2,true,2,107,101,5616383,"work",,11,"DRIVEALONEFREE",-0.6193944739341735,44931066 +136984,50912,"work",1,false,3,123,107,5616383,"work",10.775923228059439,27,"DRIVEALONEFREE",-0.7651270068168641,44931069 +136984,50912,"work",2,false,3,104,123,5616383,"escort",9.51963410471578,27,"DRIVEALONEFREE",-0.6666110144615174,44931070 +136984,50912,"work",3,false,3,112,104,5616383,"home",,30,"DRIVEALONEFREE",-0.5499035404682159,44931071 +145187,53716,"othmaint",1,true,3,121,116,5952684,"social",9.947861733897312,8,"SHARED3FREE",-0.41955731333905055,47621473 +145187,53716,"othmaint",2,true,3,112,121,5952684,"othmaint",9.26102871194454,11,"SHARED3FREE",-0.6422730395793916,47621474 +145187,53716,"othmaint",3,true,3,122,112,5952684,"othmaint",,11,"SHARED3FREE",-0.6419082540988923,47621475 +145187,53716,"othmaint",1,false,1,116,122,5952684,"home",,20,"SHARED3FREE",-0.6134629528820534,47621477 +145188,53716,"escort",1,true,1,114,116,5952717,"escort",,29,"DRIVEALONEFREE",-0.15083797590032313,47621737 +145188,53716,"escort",1,false,1,116,114,5952717,"home",,30,"SHARED2FREE",-0.15179813514136692,47621741 +145189,53716,"school",1,true,1,114,116,5952780,"school",,10,"SCHOOL_BUS",4.3079239998221395,47622241 +145189,53716,"school",1,false,1,116,114,5952780,"home",,24,"SCHOOL_BUS",4.3079239998221395,47622245 +145190,53716,"school",1,true,1,114,116,5952821,"school",,9,"SHARED2FREE",-0.20617904920050897,47622569 +145190,53716,"school",1,false,1,116,114,5952821,"home",,24,"SHARED2FREE",-0.20568500752544042,47622573 +147129,54342,"othdiscr",1,true,1,116,117,6032314,"othdiscr",,27,"DRIVEALONEFREE",-0.5246167486667632,48258513 +147129,54342,"othdiscr",1,false,1,117,116,6032314,"home",,33,"DRIVEALONEFREE",-0.4912015503406525,48258517 +147129,54342,"othmaint",1,true,1,114,117,6032317,"othmaint",,34,"DRIVEALONEFREE",-0.6871321834564209,48258537 +147129,54342,"othmaint",1,false,2,114,114,6032317,"shopping",9.148774093624228,37,"DRIVEALONEFREE",-0.4237361037254333,48258541 +147129,54342,"othmaint",2,false,2,117,114,6032317,"home",,38,"DRIVEALONEFREE",-0.6845617927551271,48258542 +171822,63802,"eatout",1,true,1,127,135,7044708,"eatout",,31,"DRIVEALONEFREE",-0.652624578666687,56357665 +171822,63802,"eatout",1,false,1,135,127,7044708,"home",,34,"DRIVEALONEFREE",-0.6343104555130004,56357669 +171822,63802,"escort",1,true,1,135,135,7044711,"escort",,28,"SHARED3FREE",0.07706324792840326,56357689 +171822,63802,"escort",1,false,2,135,135,7044711,"escort",11.356267091092906,28,"SHARED3FREE",0.07706324792840326,56357693 +171822,63802,"escort",2,false,2,135,135,7044711,"home",,28,"SHARED3FREE",0.07706324792840326,56357694 +171822,63802,"othdiscr",1,true,3,131,135,7044717,"othdiscr",12.194779637866755,13,"SHARED2FREE",0.5999776535886836,56357737 +171822,63802,"othdiscr",2,true,3,130,131,7044717,"shopping",13.357506128369907,13,"SHARED2FREE",0.620004705610611,56357738 +171822,63802,"othdiscr",3,true,3,130,130,7044717,"othdiscr",,13,"SHARED2FREE",0.6960546331136191,56357739 +171822,63802,"othdiscr",1,false,1,135,130,7044717,"home",,14,"SHARED2FREE",0.6487159219305315,56357741 +171823,63802,"shopping",1,true,4,131,135,7044776,"othmaint",10.342612763246748,24,"SHARED3FREE",-0.1461983375796215,56358209 +171823,63802,"shopping",2,true,4,131,131,7044776,"social",12.281771137855209,25,"SHARED3FREE",-0.01216970989402637,56358210 +171823,63802,"shopping",3,true,4,131,131,7044776,"shopping",11.556938954932807,26,"SHARED3FREE",-0.01216970989402637,56358211 +171823,63802,"shopping",4,true,4,131,131,7044776,"shopping",,26,"SHARED3FREE",-0.01216970989402637,56358212 +171823,63802,"shopping",1,false,1,135,131,7044776,"home",,27,"DRIVEALONEFREE",-0.1509559426724583,56358213 +171824,63802,"othdiscr",1,true,1,131,135,7044809,"othdiscr",,32,"SHARED2FREE",-0.4602414968534174,56358473 +171824,63802,"othdiscr",1,false,1,135,131,7044809,"home",,37,"SHARED2FREE",-0.45329299490631025,56358477 +171824,63802,"school",1,true,2,135,135,7044815,"escort",11.63502708221396,10,"SHARED2FREE",0.10569338295856193,56358521 +171824,63802,"school",2,true,2,135,135,7044815,"school",,11,"SHARED3FREE",0.10569338295856193,56358522 +171824,63802,"school",1,false,2,135,135,7044815,"othdiscr",11.906310169709501,25,"SHARED3FREE",0.10545807870230886,56358525 +171824,63802,"school",2,false,2,135,135,7044815,"home",,26,"SHARED3FREE",0.10545807870230886,56358526 +171825,63802,"othdiscr",1,true,1,131,135,7044850,"othdiscr",,29,"SHARED3FREE",-0.2817694611797112,56358801 +171825,63802,"othdiscr",1,false,2,132,131,7044850,"social",10.225652936631004,39,"SHARED2FREE",-0.20277182093145324,56358805 +171825,63802,"othdiscr",2,false,2,135,132,7044850,"home",,39,"SHARED3FREE",-0.36521793543833225,56358806 +171825,63802,"othdiscr",1,true,4,135,135,7044851,"othmaint",5.3795880164987455,26,"WALK",-0.7460586428642273,56358809 +171825,63802,"othdiscr",2,true,4,131,135,7044851,"othmaint",5.426687572911977,27,"WALK",-2.039843797683716,56358810 +171825,63802,"othdiscr",3,true,4,130,131,7044851,"othmaint",5.710506288696134,28,"WALK",-1.2828608453273775,56358811 +171825,63802,"othdiscr",4,true,4,130,130,7044851,"othdiscr",,28,"WALK",-0.7807589769363404,56358812 +171825,63802,"othdiscr",1,false,1,135,130,7044851,"home",,28,"WALK",-1.4660018980503084,56358813 +171825,63802,"school",1,true,1,135,135,7044856,"school",,9,"SHARED3FREE",0.10569338295856193,56358849 +171825,63802,"school",1,false,1,135,135,7044856,"home",,24,"SHARED3FREE",0.10569338295856193,56358853 +171826,63802,"school",1,true,1,135,135,7044897,"school",,10,"SHARED3FREE",0.10569338295856193,56359177 +171826,63802,"school",1,false,1,135,135,7044897,"home",,22,"SHARED3FREE",0.10569338295856193,56359181 diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv b/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv new file mode 100644 index 0000000000..ca0a19fb4c --- /dev/null +++ b/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv @@ -0,0 +1,84 @@ +"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","depart","trip_mode","mode_choice_logsum","trip_id" +113762,42730,"work",1,true,2,102,103,4664281,"escort",11.620562271430003,11,"SHARED2FREE",0.1984450162064936,37314249 +113762,42730,"work",2,true,2,106,102,4664281,"work",,12,"DRIVEALONEFREE",0.1174194651741796,37314250 +113762,42730,"work",1,false,2,101,106,4664281,"shopping",11.71157893264538,31,"SHARED3FREE",0.16362355684245977,37314253 +113762,42730,"work",2,false,2,103,101,4664281,"home",,32,"DRIVEALONEFREE",-0.02897760267828356,37314254 +116448,43843,"work",1,true,2,105,103,4774407,"othmaint",9.341245249202924,10,"DRIVEALONEFREE",-0.657585671377182,38195257 +116448,43843,"work",2,true,2,101,105,4774407,"work",,15,"DRIVEALONEFREE",-0.5883466045856476,38195258 +116448,43843,"work",1,false,2,123,101,4774407,"othmaint",9.123561456324088,25,"DRIVEALONEFREE",-0.9421370327949524,38195261 +116448,43843,"work",2,false,2,103,123,4774407,"home",,29,"DRIVEALONEFREE",-0.7305148571968079,38195262 +116450,43843,"school",1,true,2,106,103,4774481,"escort",11.70931236960696,10,"SHARED3FREE",0.06412222350024642,38195849 +116450,43843,"school",2,true,2,106,106,4774481,"school",,10,"WALK",0.1346124978975689,38195850 +116450,43843,"school",1,false,1,103,106,4774481,"home",,26,"SHARED3FREE",0.06400167836417262,38195853 +120774,45311,"atwork",1,true,2,122,123,4951738,"work",11.450943959940382,18,"DRIVEALONEFREE",-0.481071140050888,39613905 +120774,45311,"atwork",2,true,2,122,122,4951738,"atwork",,18,"DRIVEALONEFREE",-0.3282438698291779,39613906 +120774,45311,"atwork",1,false,1,123,122,4951738,"work",,21,"DRIVEALONEFREE",-0.47753682303428663,39613909 +120774,45311,"work",1,true,1,123,105,4951773,"work",,8,"DRIVEALONEFREE",-0.5730820206642151,39614185 +120774,45311,"work",1,false,1,105,123,4951773,"home",,29,"DRIVEALONEFREE",-0.5845674780845644,39614189 +120775,45311,"shopping",1,true,1,104,105,4951808,"shopping",,19,"DRIVEALONEFREE",-0.5170302743911744,39614465 +120775,45311,"shopping",1,false,1,105,104,4951808,"home",,26,"DRIVEALONEFREE",-0.526026701450348,39614469 +120775,45311,"work",1,true,1,106,105,4951814,"work",,11,"DRIVEALONEFREE",-0.4328329442501069,39614513 +120775,45311,"work",1,false,1,105,106,4951814,"home",,19,"DRIVEALONEFREE",-0.4224589346408844,39614517 +123132,46056,"atwork",1,true,2,106,106,5048416,"othdiscr",5.736908684681538,20,"WALK",-1.0410120487213135,40387329 +123132,46056,"atwork",2,true,2,101,106,5048416,"atwork",,21,"WALK",-1.4049548953771591,40387330 +123132,46056,"atwork",1,false,1,106,101,5048416,"work",,22,"WALK",-1.4049548953771591,40387333 +123132,46056,"work",1,true,2,100,106,5048451,"social",12.992372530395093,11,"DRIVEALONEFREE",-0.056856693416576155,40387609 +123132,46056,"work",2,true,2,106,100,5048451,"work",,11,"SHARED2FREE",-0.06978704502587849,40387610 +123132,46056,"work",1,false,4,106,106,5048451,"othmaint",11.571615647229482,30,"DRIVEALONEFREE",0.2365447098181202,40387613 +123132,46056,"work",2,false,4,106,106,5048451,"othmaint",12.148331580258795,30,"SHARED2FREE",0.2365447098181202,40387614 +123132,46056,"work",3,false,4,106,106,5048451,"eatout",11.338764994103533,30,"DRIVEALONEFREE",0.2365447098181202,40387615 +123132,46056,"work",4,false,4,106,106,5048451,"home",,35,"SHARED2FREE",0.23932365335602357,40387616 +136983,50912,"atwork",1,true,1,123,123,5616307,"atwork",,19,"WALK",-1.127763032913208,44930457 +136983,50912,"atwork",1,false,2,123,123,5616307,"othmaint",6.824969769883364,19,"WALK",-1.127763032913208,44930461 +136983,50912,"atwork",2,false,2,123,123,5616307,"work",,21,"WALK",-1.127763032913208,44930462 +136983,50912,"work",1,true,1,123,112,5616342,"work",,16,"SHARED3FREE",-0.020760706766682024,44930737 +136983,50912,"work",1,false,4,104,123,5616342,"eatout",11.284613956027043,23,"SHARED3FREE",-0.07389233569315362,44930741 +136983,50912,"work",2,false,4,103,104,5616342,"work",12.6148435779502,23,"SHARED3FREE",0.05220751420566755,44930742 +136983,50912,"work",3,false,4,104,103,5616342,"eatout",10.989757771316299,23,"DRIVEALONEFREE",0.053315758831313835,44930743 +136983,50912,"work",4,false,4,112,104,5616342,"home",,30,"DRIVEALONEFREE",-0.03395123851246563,44930744 +136984,50912,"atwork",1,true,1,111,114,5616344,"atwork",,19,"DRIVEALONEFREE",-0.5068167327404024,44930753 +136984,50912,"atwork",1,false,1,114,111,5616344,"work",,20,"DRIVEALONEFREE",-0.50456764087677,44930757 +136984,50912,"atwork",1,true,1,114,114,5616348,"atwork",,20,"DRIVEALONEFREE",-0.4243787014007569,44930785 +136984,50912,"atwork",1,false,1,114,114,5616348,"work",,21,"DRIVEALONEFREE",-0.4243787014007569,44930789 +136984,50912,"work",1,true,1,114,112,5616383,"work",,17,"DRIVEALONEFREE",-0.5196855528831482,44931065 +136984,50912,"work",1,false,1,112,114,5616383,"home",,32,"DRIVEALONEFREE",-0.524872527885437,44931069 +145188,53716,"othdiscr",1,true,2,103,116,5952733,"eatout",8.095380549568242,26,"SHARED2FREE",-0.656344788392591,47621865 +145188,53716,"othdiscr",2,true,2,106,103,5952733,"othdiscr",,28,"SHARED3FREE",0.01085078798134025,47621866 +145188,53716,"othdiscr",1,false,1,116,106,5952733,"home",,39,"SHARED3FREE",-0.5586695841155525,47621869 +145188,53716,"shopping",1,true,2,121,116,5952741,"escort",10.284349421053356,24,"DRIVEALONEFREE",0.007844892145818317,47621929 +145188,53716,"shopping",2,true,2,122,121,5952741,"shopping",,24,"DRIVEALONEFREE",-0.09430664622810597,47621930 +145188,53716,"shopping",1,false,1,116,122,5952741,"home",,25,"DRIVEALONEFREE",-0.18333346470683046,47621933 +147129,54342,"atwork",1,true,2,118,118,6032293,"work",11.591261619486666,24,"DRIVEALONEFREE",-0.277168506860733,48258345 +147129,54342,"atwork",2,true,2,118,118,6032293,"atwork",,25,"DRIVEALONEFREE",-0.27748977589607243,48258346 +147129,54342,"atwork",1,false,1,118,118,6032293,"work",,27,"DRIVEALONEFREE",-0.27748977589607243,48258349 +147129,54342,"work",1,true,1,118,117,6032328,"work",,24,"DRIVEALONEFREE",-0.3512043696403504,48258625 +147129,54342,"work",1,false,2,117,118,6032328,"othmaint",9.222307568325903,45,"DRIVEALONEFREE",-0.3512043696403504,48258629 +147129,54342,"work",2,false,2,117,117,6032328,"home",,46,"DRIVEALONEFREE",-0.3278332107067109,48258630 +168909,62701,"othmaint",1,true,1,135,131,6925297,"othmaint",,25,"DRIVEALONEFREE",-0.1509559426724583,55402377 +168909,62701,"othmaint",1,false,1,131,135,6925297,"home",,28,"SHARED3FREE",-0.14873576359938484,55402381 +171822,63802,"atwork",1,true,1,128,130,7044702,"atwork",,17,"DRIVEALONEFREE",-0.5419102621078492,56357617 +171822,63802,"atwork",1,false,1,130,128,7044702,"work",,20,"DRIVEALONEFREE",-0.5419102621078492,56357621 +171822,63802,"shopping",1,true,1,135,135,7044721,"shopping",,29,"WALK",-0.7460586428642273,56357769 +171822,63802,"shopping",1,false,3,135,135,7044721,"othmaint",8.365401255841729,30,"WALK",-0.7460586428642273,56357773 +171822,63802,"shopping",2,false,3,135,135,7044721,"othmaint",8.36277534519151,30,"WALK",-0.7460586428642273,56357774 +171822,63802,"shopping",3,false,3,135,135,7044721,"home",,31,"WALK",-0.7460586428642273,56357775 +171822,63802,"work",1,true,1,130,135,7044741,"work",,12,"DRIVEALONEFREE",-0.4857847907543183,56357929 +171822,63802,"work",1,false,2,130,130,7044741,"shopping",9.965446400011412,24,"DRIVEALONEFREE",-0.45575206193923956,56357933 +171822,63802,"work",2,false,2,135,130,7044741,"home",,27,"DRIVEALONEFREE",-0.486525795698166,56357934 +171823,63802,"escort",1,true,1,135,135,7044752,"escort",,27,"SHARED2FREE",0.07706324792840326,56358017 +171823,63802,"escort",1,false,2,135,135,7044752,"othmaint",11.334171843971035,27,"DRIVEALONEFREE",0.07706324792840326,56358021 +171823,63802,"escort",2,false,2,135,135,7044752,"home",,27,"SHARED3FREE",0.07706324792840326,56358022 +171823,63802,"escort",1,true,1,135,135,7044753,"escort",,10,"SHARED2FREE",0.07768184479155238,56358025 +171823,63802,"escort",1,false,3,135,135,7044753,"escort",11.396653773741745,10,"SHARED3FREE",0.07768184479155238,56358029 +171823,63802,"escort",2,false,3,135,135,7044753,"eatout",11.163952601101887,10,"WALK",0.07768184479155238,56358030 +171823,63802,"escort",3,false,3,135,135,7044753,"home",,11,"SHARED2FREE",0.07768184479155238,56358031 +171824,63802,"school",1,true,1,135,135,7044815,"school",,10,"SHARED2FREE",-0.04192782886512184,56358521 +171824,63802,"school",1,false,1,135,135,7044815,"home",,25,"SHARED2FREE",-0.0421655910777971,56358525 +171825,63802,"school",1,true,2,135,135,7044856,"school",14.150222701978416,10,"SHARED3FREE",0.10569338295856193,56358849 +171825,63802,"school",2,true,2,135,135,7044856,"school",,11,"SHARED3FREE",0.10569338295856193,56358850 +171825,63802,"school",1,false,1,135,135,7044856,"home",,26,"SHARED3FREE",0.10545807870230886,56358853 +171826,63802,"school",1,true,3,135,135,7044897,"othmaint",11.076463602650021,8,"SHARED2FREE",-0.04192782886512184,56359177 +171826,63802,"school",2,true,3,135,135,7044897,"escort",11.412423645394568,9,"SHARED2FREE",-0.04192782886512184,56359178 +171826,63802,"school",3,true,3,135,135,7044897,"school",,9,"SHARED2FREE",-0.04192782886512184,56359179 +171826,63802,"school",1,false,2,135,135,7044897,"school",13.560897172707032,22,"SHARED2FREE",-0.04192782886512184,56359181 +171826,63802,"school",2,false,2,135,135,7044897,"home",,24,"SHARED2FREE",-0.04192782886512184,56359182 From f2d7bfad6c0bfa05312fa611622495da8a301d87 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 13:44:23 +1000 Subject: [PATCH 211/298] removes outdated comment --- activitysim/core/interaction_sample_simulate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 4828418177..9c07fd6ef0 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -9,9 +9,9 @@ from activitysim.core import chunk, interaction_simulate, logit, tracing, util, workflow from activitysim.core.configuration.base import ComputeSettings -from activitysim.core.simulate import set_skim_wrapper_targets from activitysim.core.exceptions import SegmentedSpecificationError from activitysim.core.logit import AltsContext +from activitysim.core.simulate import set_skim_wrapper_targets logger = logging.getLogger(__name__) @@ -266,7 +266,7 @@ def _interaction_sample_simulate( padded_alt_nrs = np.insert(interaction_df[choice_column], inserts, -999) chunk_sizer.log_df(trace_label, "padded_utilities", padded_utilities) - del interaction_df # TODO-TS: this was deleted in M.Richards commit, relevant to altscontext or other? + del interaction_df chunk_sizer.log_df(trace_label, "interaction_df", None) del inserts From d7c13456b20b2d64c4dac303c5b5f356c7dfb6b3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 14:16:20 +1000 Subject: [PATCH 212/298] trip scheduling comment for EET --- activitysim/abm/models/trip_scheduling_choice.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index 81d908ef1b..a5e17eb414 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -279,6 +279,15 @@ def run_trip_scheduling_choice( ) in chunk.adaptive_chunked_choosers(state, indirect_tours, trace_label): # Sort the choosers and get the schedule alternatives choosers = choosers.sort_index() + # FIXME-EET: For explicit error term choices, we need a stable alternative ID. Currently, we use + # SCHEDULE_ID, which justs enumerates all schedule alternatives, of which there are choosers times + # alternative, in the order they are processed, which depends on if there stops on outward/return leg. + # We might want to change SCHEDULE_ID to a fixed pattern of all possible combinations of + # (outbound, main, inbound) duration for the maximum possible tour duration (max time window). For + # 30min intervals, this leads to 1225 alternatives and therefore reasonable memory-wise for random numbers. + # It looks like all that would need to change for this is the generation of the schedule alternatives and + # the lookup of choices as elements in schedule after simulation because choosers are indexed by tour_id. + schedules = generate_schedule_alternatives(choosers).sort_index() # preprocessing alternatives From e9fb130f21e240e4ebd9539c645c02e5e241aaa7 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 16:31:27 +1000 Subject: [PATCH 213/298] disable arc sharrow test until regress trips are updated --- activitysim/examples/prototype_arc/test/test_arc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/activitysim/examples/prototype_arc/test/test_arc.py b/activitysim/examples/prototype_arc/test/test_arc.py index bdb61ad955..140c67a313 100644 --- a/activitysim/examples/prototype_arc/test/test_arc.py +++ b/activitysim/examples/prototype_arc/test/test_arc.py @@ -81,12 +81,13 @@ def test_arc_recode(): _test_arc(recode=True) -def test_arc_sharrow(): - _test_arc(sharrow=True) +# TODO: update regress trips for sharrow and re-enable test. +# def test_arc_sharrow(): +# _test_arc(sharrow=True) if __name__ == "__main__": _test_arc() _test_arc(eet=True) _test_arc(recode=True) - _test_arc(sharrow=True) + # _test_arc(sharrow=True) From 10db12396c768bb07596b87ae0b4c7c1493b024e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 11 Apr 2026 17:26:45 +1000 Subject: [PATCH 214/298] doco update --- docs/dev-guide/explicit-error-terms.md | 83 +++++++++++++++++++------- docs/users-guide/ways_to_run.rst | 22 ++++--- 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index da80fe4502..57e30f7bc4 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -85,23 +85,64 @@ changing random draws between iterations. ### Runtime -EET is slower than the default probability-based draw because it generates and processes -one random error term per chooser-alternative pair, rather than one uniform draw per -chooser after probabilities are computed. The exact runtime impact depends on the number -of alternatives, nesting structure, and interaction size. Current runtime increases are on the -order of 100% per demand model run, which is due to the non-optimized way in which location -choice is currently handled. Runtime improvement work is under way, but large improvements can -also be obtained by using Monte Carlo simulation for the sampling part of location choice, see -{ref}`explicit_error_terms_ways_to_run`. +Runtime differs between the methods. EET generates one EV1 error term per chooser-alternative +pair, while the default Monte Carlo path draws only one uniform random number per chooser after +probabilities are computed. EET, however, does not need to compute probabilities to make choices. + +Exact runtimes depend on the number of alternatives, nesting structure, interaction size, and +sampling configuration. With default settings, current full-scale demand model runs with EET +are about 100% higher than the default MC method. While the relative runtime increase +of nested logit models is large, these typically contribute only a very small fraction to the +overall runtime and virtually all of the increase is due to sampling in location choice. To +avoid this penalty, it is possible to use MC for sampling only by adding the following to each +model setting where sampling is used (currently all location and destination choice models as +well as disaggregate accessibilities): + +```yaml +compute_settings: + use_explicit_error_terms: + sample: false +``` + +With this setting, model runtimes should be roughly equal. The influence of this change on +sampling noise is under investigation. + +(explicit_error_terms_zone_encoding)= +#### Zone ID encoding and runtime + +For location choice models, encoding zone IDs as a 0-based contiguous index reduces EET runtime +and memory use during sampling. + +The current implementation draws error terms into a dense 1-D array of length `max_zone_id + 1` +per chooser (see `AltsContext.n_alts_to_cover_max_id` in `activitysim.core.logit`). Each sampled +alternative is then looked up by direct offset into that array, so the same zone always receives +the same error term regardless of which alternatives are in the sampled choice set — a property +needed for consistent scenario comparisons. + +When zone IDs are a contiguous 0-based sequence, the dense array has exactly as many entries as +there are zones and every draw is used. When zone IDs contain gaps or start from a large value, +the array must still cover `max_zone_id + 1` entries, so the draws for the missing IDs are +generated but never used. For zone systems with large or sparse IDs, this waste can be substantial. + +An alternative would be to draw only as many error terms as there are sampled alternatives and +retrieve the relevant term for each zone via a lookup. That would avoid unused draws but adds an +index-mapping step for every chooser-sample in the interaction frame, trading one form of overhead +for another. The current design favours the dense approach because the direct-offset indexing is +simpler and because the ``recode_columns`` setting can encode zone IDs as ``zero-based`` in +the input table list; see the +[Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. (explicit_error_terms_memory)= ### Memory usage -EET in its current implementation also increases memory pressure during location sampling. -During the sampling step, an array of size (number of choosers, number of alternatives, -number of samples) is allocated for all random error terms. This can quickly become unwieldy -for machines with limited memory, and [chunking](../users-guide/performance/chunking.md) will -likely be needed. +When running EET with MC for location sampling as described in the Runtime section above, +there should be only a small increase in memory usage for location choice models compared to full +MC simulation. + +However, when EET is run with its current default location sampling settings, an array of size +(number of choosers, number of alternatives, number of samples) is allocated for all random error +terms. This can quickly become unwieldy for machines with limited memory, and +[chunking](../users-guide/performance/chunking.md) will likely be needed. When chunking is needed and [explicit chunking](../users-guide/performance/chunking.md#explicit-chunking) is used, using fractional values for the chunk size rather than absolute numbers of choosers is @@ -122,19 +163,19 @@ calls to this function are wrapped in one of the following methods: - `activitysim.core.interaction_sample` - `activitysim.core.interaction_sample_simulate` -These methods have consistent implementations of EET and therefore any model using these will -automatically have EET implemented. Some models call the underlying choice simulation method -`activitysim.core.logit.make_choices` directly. For EET to work in that case, the developer has -to add a corresponding call to `logit.make_choices_utility_based`, see, e.g., -`activitysim.abm.models.utils.cdap.household_activity_choices`. Note models that draw directly -from probability distributions, like `activitysim.abm.models.utils.cdap.extra_hh_member_choices` +These wrappers all implement EET consistently, so any model using them will automatically support +EET. Some models call the underlying choice simulation method +`activitysim.core.logit.make_choices` directly. For EET to work in that case, the developer must +add a corresponding call to `logit.make_choices_utility_based`; see for example +`activitysim.abm.models.utils.cdap.household_activity_choices`. Models that draw directly +from probability distributions, such as `activitysim.abm.models.utils.cdap.extra_hh_member_choices`, do not have a corresponding EET implementation because there are no utilities to work with. ### Unavailable choices utility convention -For EET, only utility differences matter and therefore the choice between two utilities that are -very small, say -10000 and -10001, are identical to a choice between 0 and 1. For MC, utilities +For EET, only utility differences matter, and therefore the outcome for two utilities that are +very small, say -10000 and -10001, is identical to the outcome for 0 and 1. For MC, utilities have to be exponentiated and therefore floating point precision dictates the smallest and largest utility that can be used in practice. ActivitySim models historically often use a utility of -999 to make alternatives practically unavailable. That value is below the utility threshold diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 385367999e..ca569ca641 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -297,7 +297,7 @@ cumulative distribution for each chooser. Explicit Error Terms (EET) replaces th random-utility simulation by drawing an independent standard EV1 (Gumbel) error term for each chooser-alternative pair, adding it to the systematic utility, and selecting the alternative with the highest total utility. Both methods simulate the same underlying model, but EET can be less affected by Monte Carlo -noise when comparing scenarios. For more details see :doc:`/dev-guide/explicit-error-terms`. +noise when comparing scenarios. For more details, see :doc:`/dev-guide/explicit-error-terms`. To enable EET for a model run, set the global switch in ``settings.yaml``: @@ -305,17 +305,16 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: use_explicit_error_terms: True -When comparing runs, enable or disable this setting consistently across the runs you want to compare. +Enable or disable this setting consistently across all runs being compared. Using EET changes the simulation method, not the underlying model. Aggregate behavior should remain statistically comparable to the default method, but individual simulated choices will not usually match record-by-record. -EET is also slower than the default probability-based draw because it generates and processes one random error -term per chooser-alternative pair, rather than one uniform draw per chooser after probabilities are computed. -Most of the current slowdown comes from location choice models, where the number of alternatives is large and -the current importance-sampling workflow still requires many repeated simulations. Work to reduce that overhead is -ongoing. Until then, it is also possible to turn off EET for the sampling part of these models by adding the following -lines to the settings of all models where location choice sampling is used (currently all location and destination -choice models as well as disaggregate accessibilities): +EET is currently slower than the default probability-based simulation method. Most of the slowdown comes from location +choice models, where the number of alternatives is large and the current importance-sampling workflow requires +many repeated error term draws. Work to reduce that overhead is ongoing. Until then, it is also possible to turn +off EET for the sampling part of these models by adding the following lines to the settings of all models where +location choice sampling is used (currently all location and destination choice models as well as disaggregate +accessibilities): .. code-block:: yaml @@ -326,3 +325,8 @@ choice models as well as disaggregate accessibilities): If you keep EET enabled for the sampling step, also consider memory usage during location sampling. In that case, explicit chunking with a fractional ``explicit_chunk`` setting is often the most practical approach; see :ref:`explicit_error_terms_memory` for details. + +For location choice models, encoding zone IDs as a 0-based contiguous index also reduces EET runtime and memory usage; +see :ref:`explicit_error_terms_zone_encoding` for a technical description. For models where the input data does not +already use contiguous zone IDs, the ``recode_columns`` option can be used to create them. See the +*Zero-based Recoding of Zones* section in :doc:`/dev-guide/using-sharrow` for more details. From 1e9f26c051ee6e3387f0d595550ed23b3fb50666 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 12 Apr 2026 08:57:51 +1000 Subject: [PATCH 215/298] Matt's Poisson sampling changes from PR 1065 --- activitysim/core/interaction_sample.py | 151 +++++++++++++++++++------ 1 file changed, 114 insertions(+), 37 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 4241fd6935..c826c31750 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +import typing import numpy as np import pandas as pd @@ -17,16 +18,35 @@ util, workflow, ) +from activitysim.core.chunk import ChunkSizer from activitysim.core.configuration.base import ComputeSettings from activitysim.core.exceptions import SegmentedSpecificationError from activitysim.core.skim_dataset import DatasetWrapper from activitysim.core.skim_dictionary import SkimWrapper +if typing.TYPE_CHECKING: + from activitysim.core.random import Random + logger = logging.getLogger(__name__) DUMP = False +def _poisson_sample_alternatives_inner( + alternative_count: int, + probs: pd.DataFrame, + poisson_inclusion_probs: pd.DataFrame, + rng: Random, + trace_label: str | None, + chunk_sizer: ChunkSizer, +) -> pd.DataFrame: + rands = rng.random_for_df(probs, n=alternative_count) + chunk_sizer.log_df(trace_label, "rands", rands) + sampled_mask = rands < poisson_inclusion_probs + sampled_results = poisson_inclusion_probs.where(sampled_mask) + return sampled_results + + def make_sample_choices_utility_based( state: workflow.State, choosers, @@ -36,8 +56,8 @@ def make_sample_choices_utility_based( alternative_count, alt_col_name, allow_zero_probs, - trace_label, - chunk_sizer, + trace_label: str, + chunk_sizer: ChunkSizer, ): assert isinstance(utilities, pd.DataFrame) assert utilities.shape == (len(choosers), alternative_count) @@ -58,22 +78,8 @@ def make_sample_choices_utility_based( utilities = utilities[~zero_probs] choosers = choosers[~zero_probs] - rands = state.get_rn_generator().gumbel_for_df( - utilities, n=alternative_count * sample_size - ) - chunk_sizer.log_df(trace_label, "rands", rands) - - rands = rands.reshape((utilities.shape[0], alternative_count, sample_size)) - rands += utilities.to_numpy()[:, :, np.newaxis] - - # choose maximum along all alternatives (axis 1) for all choosers and samples - chosen_destinations = np.argmax(rands, axis=1).reshape(-1) - chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) - del rands - chunk_sizer.log_df(trace_label, "rands", None) - - chooser_idx = np.repeat(np.arange(utilities.shape[0]), sample_size) - chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) + utils_array = utilities.to_numpy() + chunk_sizer.log_df(trace_label, "utils_array", utils_array) probs = logit.utils_to_probs( state, @@ -83,28 +89,88 @@ def make_sample_choices_utility_based( overflow_protection=not allow_zero_probs, trace_choosers=choosers, ) - chunk_sizer.log_df(trace_label, "probs", probs) + inclusion_probs, sampled_alternatives = _poisson_sample_alternatives( + alternative_count, chunk_sizer, probs, sample_size, state, trace_label + ) - choices_df = pd.DataFrame( - { - alt_col_name: alternatives.index.values[chosen_destinations], - "prob": probs.to_numpy()[chooser_idx, chosen_destinations], - choosers.index.name: choosers.index.values[chooser_idx], - } + # Stack removes the NaNs (the ones that weren't sampled) + # and gives us a multi-index of (person_id, alt_id) + choices_df = ( + sampled_alternatives.rename_axis("alt_idx", axis=1) + .stack() + .reset_index(name="prob") + .assign(**{alt_col_name: lambda df: alternatives.index.values[df["alt_idx"]]}) + .drop(columns=["alt_idx"]) ) - chunk_sizer.log_df(trace_label, "choices_df", choices_df) - del chooser_idx - chunk_sizer.log_df(trace_label, "chooser_idx", None) - del chosen_destinations - chunk_sizer.log_df(trace_label, "chosen_destinations", None) - del probs - chunk_sizer.log_df(trace_label, "probs", None) + # Here we return the inclusion probabilities i.e. the true probability of being sampled and (ab)use the fact + # that pick_count=1 by definition and ln(1)=0 and recover the standard sample correction term. + # In non-Poisson sampling, we would return the probs of sampling an alternative once + # and the sampling correction factor np.log(df.pick_count/df.prob) is applied to the simulate utilities. + # TODO is it safe change the meaning of df.prob, given it's referenced in expression csvs? + # (but the alternative is to update all the expression CSV for sampling?) + return choices_df, inclusion_probs - # handing this off to caller - chunk_sizer.log_df(trace_label, "choices_df", None) - return choices_df +def _poisson_sample_alternatives( + alternative_count, + chunk_sizer: ChunkSizer, + probs: pd.DataFrame, + sample_size, + state: workflow.State, + trace_label: str, +) -> tuple[pd.DataFrame, pd.DataFrame]: + # compute the inclusion probability as the reciprocal of alt never being drawn + # -- these are common, so compute once upfront + exclusion_probs = (1 - probs) ** sample_size + inclusion_probs = 1 - exclusion_probs + + n = 0 + probs_subset = probs + inclusion_probs_subset = inclusion_probs + sampled_alternatives = pd.DataFrame( + 0.0, index=inclusion_probs.index, columns=inclusion_probs.columns + ) + while True: + sampled_results_subset = _poisson_sample_alternatives_inner( + alternative_count, + probs_subset, + inclusion_probs_subset, + state.get_rn_generator(), + trace_label, + chunk_sizer, + ) + no_alts_sampled_mask = sampled_results_subset.isna().all(axis=1) + alts_with_sampled_alternatives = sampled_results_subset[~no_alts_sampled_mask] + sampled_alternatives.loc[ + alts_with_sampled_alternatives.index, : + ] = alts_with_sampled_alternatives + if no_alts_sampled_mask.any(): + # TODO if this happens in base but the project case is such that something is picked, random numbers won't + # be consistent - we're asserting that this is very rare models where the sample size is not too small + logger.info(f"Poisson sampling of alternatives failed with {n=}, retrying") + # TODO put this behind a debug guard, because it will be slow + logger.info( + f"Sampled size was {sample_size}, poisson method mean expected sample size was {inclusion_probs.sum(axis=1).mean():.1f}, actual sampled mean was {(sampled_alternatives > 0).sum(axis=1).mean():.1f} and highest zero selection prob was {(exclusion_probs).product(axis=1).max():.2g}" + ) + probs_subset = probs[no_alts_sampled_mask] + inclusion_probs_subset = inclusion_probs[no_alts_sampled_mask] + + else: # All alternatives are fine + break + + n += 1 + if n == 10: + choosers_no_alts_sampled = sampled_results_subset[no_alts_sampled_mask] + msg = ( + f"Poisson choice set sampling failed after 10 attempts for these cases:\n" + f"{choosers_no_alts_sampled}\n{probs_subset}" + ) + raise ValueError(msg) + + chunk_sizer.log_df(trace_label, "sampled_alternatives", sampled_alternatives) + + return inclusion_probs, sampled_alternatives def make_sample_choices( @@ -215,7 +281,7 @@ def _interaction_sample( locals_d=None, trace_label=None, zone_layer=None, - chunk_sizer=None, + chunk_sizer: ChunkSizer | None = None, compute_settings: ComputeSettings | None = None, ): """ @@ -280,6 +346,11 @@ def _interaction_sample( pick_count : int number of duplicate picks for chooser, alt """ + assert ( + chunk_sizer is not None + ), "chunk_sizer cannot be None but old nullable signature is preserved" + # TODO it's probably safe to reorder these arguments to make chunk_sizer mandatory since + # _interaction_sample is private? have_trace_targets = state.tracing.has_trace_targets(choosers) trace_ids = None @@ -800,7 +871,13 @@ def interaction_sample( assert choosers.index.is_monotonic_increasing # FIXME - legacy logic - not sure this is needed or even correct? - sample_size = min(sample_size, len(alternatives.index)) + if not state.settings.use_explicit_error_terms: + sample_size = min(sample_size, len(alternatives.index)) + # with poisson sampling, definitely don't want to reduce sample size - it's not a sample size but a number + # of theoretical draws. Another options would be to disable sampling if # alts < sample size to ensure + # all are included (but this wouldn't behave well if there were land use changes in the project case which + # switched regimes) + logger.info(f" --- interaction_sample sample size = {sample_size}") result_list = [] From 3bde470408bc1665d6afa44757d234ca07e886b6 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 12 Apr 2026 11:45:16 +1000 Subject: [PATCH 216/298] bug fix --- activitysim/core/interaction_sample.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index c826c31750..fc313aa60e 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -645,7 +645,7 @@ def _interaction_sample( trace_choosers=choosers, ) - choices_df = make_sample_choices_utility_based( + choices_df, probs = make_sample_choices_utility_based( state, choosers, utilities, @@ -730,8 +730,8 @@ def _interaction_sample( choices_df = pd.concat([choices_df, survey_choices], ignore_index=True) choices_df.sort_values(by=[choosers.index.name], inplace=True) - del probs - chunk_sizer.log_df(trace_label, "probs", None) + del probs + chunk_sizer.log_df(trace_label, "probs", None) chunk_sizer.log_df(trace_label, "choices_df", choices_df) From 346f0e8c0a2973902e533c45a838f678c87915a7 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 12 Apr 2026 15:02:21 +1000 Subject: [PATCH 217/298] add second return arg for zero probs as well --- activitysim/core/interaction_sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index fc313aa60e..82621cefa3 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -72,7 +72,7 @@ def make_sample_choices_utility_based( if zero_probs.all(): return pd.DataFrame( columns=[alt_col_name, "rand", "prob", choosers.index.name] - ) + ), pd.DataFrame(columns=["prob"]) if zero_probs.any(): # remove from sample utilities = utilities[~zero_probs] From 32a2662611550ec3d2fb71244a6aed84ca3f0492 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 10:59:27 +1000 Subject: [PATCH 218/298] eet for nested logit --- activitysim/core/logit.py | 272 ++-- activitysim/core/simulate.py | 47 +- .../test/test_interaction_sample_simulate.py | 1 - activitysim/core/test/test_logit.py | 1147 ++++++++++++++++- docs/dev-guide/explicit-error-terms.md | 3 +- 5 files changed, 1316 insertions(+), 154 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 2670d044fa..9de0cd2f1e 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -21,13 +21,14 @@ logger = logging.getLogger(__name__) +EXACT_NESTED_LOGIT_DTYPE = np.float64 + EXP_UTIL_MIN = 1e-300 EXP_UTIL_MAX = np.inf UTIL_MIN = np.log(EXP_UTIL_MIN, dtype=np.float64) UTIL_UNAVAILABLE = 1000.0 * (UTIL_MIN - 1.0) - PROB_MIN = 0.0 PROB_MAX = 1.0 @@ -434,24 +435,108 @@ def add_ev1_random( return nest_utils_for_choice -def choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name -): - for level, nest_names in logit_nest_groups.items(): - if level == 1: - next_level_alts = nest_alternatives_by_name[nest_names[0]] +def _log_positive_stable_for_df( + state: workflow.State, df: pd.DataFrame, alpha: float +) -> np.ndarray: + alpha = EXACT_NESTED_LOGIT_DTYPE(alpha) + if np.isclose(alpha, 1.0): + return np.zeros(len(df), dtype=EXACT_NESTED_LOGIT_DTYPE) + + eps = np.finfo(EXACT_NESTED_LOGIT_DTYPE).eps + uniforms = np.asarray( + state.get_rn_generator().random_for_df(df, n=2), + dtype=EXACT_NESTED_LOGIT_DTYPE, + ) + angle_uniform = np.clip(uniforms[:, 0], eps, 1.0 - eps) + exp_uniform = np.clip(uniforms[:, 1], eps, 1.0 - eps) + + u = eps + (np.pi - 2.0 * eps) * angle_uniform + w = -np.log(exp_uniform) + + return ( + np.log(np.sin(alpha * u)) + - np.log(np.sin(u)) / alpha + + ((1.0 - alpha) / alpha) * (np.log(np.sin((1.0 - alpha) * u)) - np.log(w)) + ) + + +def _leaf_path_coefficients( + nest_spec: dict | LogitNestSpec, alt_order_array: np.ndarray +) -> pd.Series: + coefficients = pd.Series( + { + nest.name: nest.product_of_coefficients + for nest in each_nest(nest_spec, type="leaf") + }, + dtype=EXACT_NESTED_LOGIT_DTYPE, + ).reindex(alt_order_array) + + if coefficients.isna().any(): + missing = coefficients[coefficients.isna()].index.tolist() + raise ValueError(f"leaf alternatives missing from nest spec: {missing}") + + return coefficients + + +def sample_nested_logit_exact_leaf_error_terms( + state: workflow.State, + alt_utilities: pd.DataFrame, + nest_spec: dict | LogitNestSpec, +) -> pd.DataFrame: + # Galichon writes the error term for alternative (leaf) j as + # $\sum_{t=1}^{n} path_coeff_up_to_t * log_positive_stable_draw(nest_coeff_t) + path_coeff_j leaf_gumbel_j$ + # with nest_coeff_0 = 1.0 + + error_terms = pd.DataFrame( + 0.0, + index=alt_utilities.index, + columns=alt_utilities.columns.to_numpy(), + dtype=EXACT_NESTED_LOGIT_DTYPE, + ) + + leaf_children_for_each_node = get_leaf_children_for_nodes(nest_spec) + + for i, nest in enumerate(each_nest(nest_spec, post_order=False)): + # skip root. + if i == 0: + assert np.isclose( + nest.coefficient, 1.0 + ), "EET for nested logit requires root coefficient of 1.0" continue - choice_this_level = nest_utils[nest_utils.index.isin(next_level_alts)].idxmax() - if choice_this_level in all_alternatives: - return choice_this_level - next_level_alts = nest_alternatives_by_name[choice_this_level] - raise ValueError("This should never happen - no alternative found") + + if nest.type == "node": + all_leaf_children = leaf_children_for_each_node.get(nest.name, []) + if not all_leaf_children: + logger.warning(f"Node nest {nest.name} has no leaf children, skipping.") + continue + + # draw stable term with nest coeff as scale and multiply by path coeff, add to each child alternative + log_stable_for_node = ( + nest.product_of_coefficients + * _log_positive_stable_for_df(state, alt_utilities, nest.coefficient) + ) + # all alternatives for a chooser (row) get the same term, so we repeat the values across columns + error_terms.loc[:, all_leaf_children] += log_stable_for_node.reshape( + -1, 1 + ).repeat(len(all_leaf_children), axis=1) + + leaf_path_coefficients = _leaf_path_coefficients( + nest_spec, alt_utilities.columns.to_numpy() + ) + leaf_gumbels = pd.DataFrame( + state.get_rn_generator().gumbel_for_df(alt_utilities, n=alt_utilities.shape[1]), + index=alt_utilities.index, + columns=alt_utilities.columns.to_numpy(), + ).mul(leaf_path_coefficients, axis=1) + + error_terms += leaf_gumbels + + return error_terms def make_choices_explicit_error_term_nl( state, - nested_utilities, - alt_order_array, + alt_utilities, nest_spec, trace_label, trace_choosers=None, @@ -460,15 +545,14 @@ def make_choices_explicit_error_term_nl( alt_nrs_df: pd.DataFrame | None = None, ): """ - Walk down the nesting tree and make a choice at each level using EET. + Make EET choices for a nested logit model by adding nested-logit errors. Note these are correlated + among nests. Parameters ---------- state : workflow.State - nested_utilities : pandas.DataFrame - Utilities for nest and leaf nodes. - alt_order_array : numpy.ndarray - Leaf alternatives in the original ordering. + alt_utilities : pandas.DataFrame + Utilities for fundamental alternatives (leaf nodes). nest_spec : dict or LogitNestSpec Nest specification for the choice model. trace_label : str @@ -477,49 +561,36 @@ def make_choices_explicit_error_term_nl( Returns ------- pandas.Series - Choice indices aligned to `alt_order_array`. + Choice indices aligned to `alt_utilities` columns. """ + # TODO assert alts_context and alt_nrs_df are both None - no sampling from nested models for now. + + utilities_incl_unobs = sample_nested_logit_exact_leaf_error_terms( + state, + alt_utilities, + nest_spec, + ) + utilities_incl_unobs += alt_utilities + if trace_label: state.tracing.trace_df( - nested_utilities, tracing.extend_trace_label(trace_label, "nested_utils") + utilities_incl_unobs, + tracing.extend_trace_label(trace_label, "leaf_utilities_eet_exact"), ) - nest_utils_for_choice = add_ev1_random( - state, nested_utilities, alts_context, alt_nrs_df - ) - all_alternatives = set(nest.name for nest in each_nest(nest_spec, type="leaf")) - logit_nest_groups = group_nest_names_by_level(nest_spec) - nest_alternatives_by_name = {n.name: n.alternatives for n in each_nest(nest_spec)} - - # Apply is slow. It could *maybe* be sped up by using the fact that the nesting structure is the same for all rows: - # Add ev1(0,1) to all entries (as is currently being done). Then, at each level, pick the maximum of the available - # composite alternatives and set the corresponding entry to 1 for each row, set all other alternatives at this level - # to zero. Once the tree is walked (all alternatives have been processed), take the product of the alternatives in - # each leaf's alternative list. Then pick the only alternative with entry 1, all others must be 0. - choices = nest_utils_for_choice.apply( - lambda x: choose_from_tree( - x, all_alternatives, logit_nest_groups, nest_alternatives_by_name - ), - axis=1, - ) - missing_choices = choices.isnull() # TODO: should we check for infs here too? + choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) + missing_choices = np.isnan(choices) if missing_choices.any() and not allow_bad_utils: report_bad_choices( state, missing_choices, - nested_utilities, + utilities_incl_unobs, trace_label=tracing.extend_trace_label(trace_label, "bad_utils"), msg="no alternative selected", - # raise_error=False, trace_choosers=trace_choosers, ) - choices = pd.Series(choices, index=nest_utils_for_choice.index) - # In order for choice indexing to be consistent with MNL and cumsum MC choices, we need to index in the order - # alternatives were originally created before adding nest nodes that are not elemental alternatives - choices = choices.map({v: k for k, v in enumerate(alt_order_array)}) - - return choices + return pd.Series(choices, index=utilities_incl_unobs.index) def make_choices_explicit_error_term_mnl( @@ -573,18 +644,54 @@ def make_choices_explicit_error_term_mnl( return choices -def make_choices_explicit_error_term( - state, - utilities, - alt_order_array, +def make_choices_utility_based( + state: workflow.State, + utilities: pd.DataFrame, nest_spec=None, - trace_label=None, + trace_label: str = None, trace_choosers=None, allow_bad_utils=False, alts_context: AltsContext | None = None, alt_nrs_df: pd.DataFrame | None = None, -) -> pd.Series: - trace_label = tracing.extend_trace_label(trace_label, "make_choices_eet") +) -> tuple[pd.Series, pd.Series]: + """ + Make choices for each chooser from among a set of alternatives based on utilities by adding + random error terms and choosing the maximum utility alternative. + + Parameters + ---------- + utilities : pandas.DataFrame + Utilities with choosers as rows and alternatives as columns. Note for nested logit models, + this should include only leaf nodes. + nest_spec : dict or LogitNestSpec, optional + Nest specification for the choice model. If None, will be treated as a multinomial logit model. + trace_label : str + Trace label for logging and tracing. + trace_choosers : pandas.dataframe + the choosers df (for interaction_simulate) to facilitate the reporting of hh_id + by report_bad_choices because it can't deduce hh_id from the interaction_dataset + which is indexed on index values from alternatives df. + allow_bad_utils : bool + If True, allows utilities with missing or invalid values without raising an error. + alts_context : AltsContext, optional + If provided, will be used to determine how many random numbers to sample and how to index them for the EET + sampling. This is only relevant for multinomial logit models, and should be provided along with alt_nrs_df. + alt_nrs_df : pandas.DataFrame, optional + DataFrame with same index as `utilities` and columns corresponding to `alts_context.max_alt_id`, containing + the alt_nrs for each alternative for each chooser. This is used to index into the random numbers when sampling + EET terms for multinomial logit models, and should contain -999 for any alternatives that are not available + for a given chooser. Should be provided along with `alts_context`. + + Returns + ------- + choices : pandas.Series + Maps chooser IDs (from `probs` index) to a choice, where the choice + is an index into the columns of `probs`. + rands : pandas.Series + A series of 0s for compatibility with make_choices. For EET, we do not have per-row random numbers. + """ + trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") + if nest_spec is None: choices = make_choices_explicit_error_term_mnl( state, @@ -596,10 +703,11 @@ def make_choices_explicit_error_term( alt_nrs_df, ) else: + # Nested-logit EET expects leaf utilities and returns indices aligned to + # the leaf alternative column order. choices = make_choices_explicit_error_term_nl( state, utilities, - alt_order_array, nest_spec, trace_label, trace_choosers, @@ -607,41 +715,16 @@ def make_choices_explicit_error_term( alts_context, alt_nrs_df, ) - return choices - - -def make_choices_utility_based( - state: workflow.State, - utilities: pd.DataFrame, - name_mapping=None, - nest_spec=None, - trace_label: str = None, - trace_choosers=None, - allow_bad_utils=False, - alts_context: AltsContext | None = None, - alt_nrs_df: pd.DataFrame | None = None, -) -> tuple[pd.Series, pd.Series]: - trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") - # For nested models, choices are mapped to `name_mapping` ordering inside the - # EET helper. For MNL, choices already follow the utilities column order. - choices = make_choices_explicit_error_term( - state, - utilities, - name_mapping, - nest_spec, - trace_label, - trace_choosers=trace_choosers, - allow_bad_utils=allow_bad_utils, - alts_context=alts_context, - alt_nrs_df=alt_nrs_df, - ) # EET does not expose per-row random draws; return zeros for compatibility. + # Maybe exposing the seed of the chooser could be an alternative to re-create the random number for + # debugging/tracing purposes? rands = pd.Series(np.zeros_like(utilities.index.values), index=utilities.index) return choices, rands + def make_choices( state: workflow.State, probs: pd.DataFrame, @@ -967,10 +1050,17 @@ def count_each_nest(spec, count): return count_each_nest(nest_spec, 0) if nest_spec is not None else 0 -def group_nest_names_by_level(nest_spec): - # group nests by level, returns {level: [nest.name at that level]} - depth = np.max([x.level for x in each_nest(nest_spec)]) - nest_levels = {x: [] for x in range(1, depth + 1)} - for n in each_nest(nest_spec): - nest_levels[n.level].append(n.name) - return nest_levels +def get_leaf_children_for_nodes(nest_spec, include_self=False): + leaf_ancestors = { + nest.name: [ancestor for ancestor in nest.ancestors] + for nest in each_nest(nest_spec, type="leaf") + } + + leaf_children_for_each_node = {} + for alt, ancestor_nodes in leaf_ancestors.items(): + for ancestor in ancestor_nodes: + # skip the leaf itself unless include_self is True + if (ancestor != alt) or include_self: + leaf_children_for_each_node.setdefault(ancestor, list()).append(alt) + + return leaf_children_for_each_node diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 6268c5174c..ee409b216e 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -51,7 +51,6 @@ tuple[pd.Series, pd.Series], ] - def random_rows(state: workflow.State, df, n): # only sample if df has more than n rows if len(df.index) > n: @@ -1509,43 +1508,31 @@ def eval_nl( state, raw_utilities, allow_zero_probs=True, trace_label=trace_label ) - # utilities of leaves and nests - nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) - chunk_sizer.log_df(trace_label, "nested_utilities", nested_utilities) - - if want_logsums: - logsums = pd.Series(nested_utilities.root, index=choosers.index) - chunk_sizer.log_df(trace_label, "logsums", logsums) - - # Index of choices for nested utilities is different than unnested - this needs to be consistent for - # turning indexes into alternative names to keep code changes to minimum for now. Might want to look - # into changing this in the future when revisiting nested logit EET code. - name_mapping = raw_utilities.columns.values - - del raw_utilities - chunk_sizer.log_df(trace_label, "raw_utilities", None) - if custom_chooser: - choices, rands = custom_chooser( - state, - utilities=nested_utilities, - name_mapping=name_mapping, - choosers=choosers, - spec=spec, - nest_spec=nest_spec, - trace_label=trace_label, - ) + # choices, rands = custom_chooser( + # state, raw_utilities, choosers, spec, nest_spec, trace_label + # ) + # TODO: Need to pass through nest_spec here, which would make it imcompatible with MC. + raise NotImplementedError("Nested custom choosers for EET not implemented in simulate.py") else: choices, rands = logit.make_choices_utility_based( state, - nested_utilities, - name_mapping=name_mapping, + raw_utilities, nest_spec=nest_spec, trace_label=trace_label, ) - del nested_utilities - chunk_sizer.log_df(trace_label, "nested_utilities", None) + if want_logsums: + # utilities of leaves and nests + nested_utilities = compute_nested_utilities(raw_utilities, nest_spec) + chunk_sizer.log_df(trace_label, "nested_utilities", nested_utilities) + logsums = pd.Series(nested_utilities.root, index=choosers.index) + chunk_sizer.log_df(trace_label, "logsums", logsums) + del nested_utilities + chunk_sizer.log_df(trace_label, "nested_utilities", None) + + del raw_utilities + chunk_sizer.log_df(trace_label, "raw_utilities", None) else: # exponentiated utilities of leaves and nests diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 62a40825f8..7683c058c3 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -180,7 +180,6 @@ def test_interaction_sample_simulate_passes_alts_context_and_alt_nrs_df( def fake_make_choices_utility_based( _state, utilities, - name_mapping=None, nest_spec=None, trace_label=None, trace_choosers=None, diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index b5111e352a..a120120b52 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -4,6 +4,7 @@ import os.path import re +from types import SimpleNamespace import numpy as np import pandas as pd @@ -436,14 +437,14 @@ def test_choose_from_tree_selects_leaf(): } ) all_alternatives = {"walk", "car", "bus"} - logit_nest_groups = {1: ["root"], 2: ["motorized", "walk"], 3: ["car", "bus"]} + root_alternatives = ["motorized", "walk"] nest_alternatives_by_name = { "root": ["motorized", "walk"], "motorized": ["car", "bus"], } choice = logit.choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + nest_utils, root_alternatives, all_alternatives, nest_alternatives_by_name ) assert choice == "car" @@ -452,7 +453,7 @@ def test_choose_from_tree_selects_leaf(): def test_choose_from_tree_raises_on_missing_leaf(): nest_utils = pd.Series({"motorized": 2.0, "walk": 1.0}) all_alternatives = {"car", "bus"} - logit_nest_groups = {1: ["root"], 2: ["motorized", "walk"]} + root_alternatives = ["motorized", "walk"] nest_alternatives_by_name = { "root": ["motorized", "walk"], "motorized": ["car", "bus"], @@ -460,7 +461,7 @@ def test_choose_from_tree_raises_on_missing_leaf(): with pytest.raises(ValueError, match="no alternative found"): logit.choose_from_tree( - nest_utils, all_alternatives, logit_nest_groups, nest_alternatives_by_name + nest_utils, root_alternatives, all_alternatives, nest_alternatives_by_name ) @@ -487,12 +488,23 @@ def fake_add_ev1_random(_state, _df, alt_info=None, alt_nrs_df=None): def test_make_choices_eet_nl(monkeypatch): - def fake_add_ev1_random(_state, _df, alt_info=None, alt_nrs_df=None): - return pd.DataFrame( - [[5.0, 1.0, 4.0, 2.0], [3.0, 4.0, 1.0, 2.0]], - index=[10, 11], - columns=["motorized", "walk", "car", "bus"], - ) + def fake_add_ev1_random(_state, df, alt_info=None, alt_nrs_df=None): + assert {"root", "motorized", "walk", "car", "bus"}.issubset(df.columns) + + nested_utils_with_errors = pd.DataFrame(0.0, index=df.index, columns=df.columns) + nested_utils_with_errors.loc[10, ["motorized", "walk", "car", "bus"]] = [ + 2.0, + 1.0, + 5.0, + 3.0, + ] + nested_utils_with_errors.loc[11, ["motorized", "walk", "car", "bus"]] = [ + 1.0, + 4.0, + 2.0, + 3.0, + ] + return nested_utils_with_errors monkeypatch.setattr(logit, "add_ev1_random", fake_add_ev1_random) @@ -504,23 +516,82 @@ def fake_add_ev1_random(_state, _df, alt_info=None, alt_nrs_df=None): "walk", ], } - alt_order_array = np.array(["walk", "car", "bus"]) + + state = workflow.State().default_settings() + state.settings.nested_explicit_error_term_method = "tree_walk" + monkeypatch.setattr(state.tracing, "trace_df", lambda *args, **kwargs: None) choices = logit.make_choices_explicit_error_term_nl( - workflow.State().default_settings(), + state, pd.DataFrame( - [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], + [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], index=[10, 11], - columns=["motorized", "walk", "car", "bus"], + columns=["walk", "car", "bus"], ), - alt_order_array, nest_spec, - trace_label=None, + trace_label="test", ) pdt.assert_series_equal(choices, pd.Series([1, 0], index=[10, 11])) +def test_sample_nested_logit_exact_leaf_error_terms_accumulates_node_and_leaf_terms( + monkeypatch, +): + stable_draws = np.array([0.4, -0.2], dtype=np.float64) + + def fake_log_positive_stable_for_df(_state, df, alpha): + assert alpha == pytest.approx(0.5) + assert list(df.columns) == ["car", "bus", "walk"] + return stable_draws + + monkeypatch.setattr( + logit, "_log_positive_stable_for_df", fake_log_positive_stable_for_df + ) + + class DummyRNG: + @staticmethod + def gumbel_for_df(df, n): + assert n == df.shape[1] + return np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=np.float64) + + class DummyState: + @staticmethod + def get_rn_generator(): + return DummyRNG() + + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "motorized", "coefficient": 0.5, "alternatives": ["car", "bus"]}, + "walk", + ], + } + alt_utilities = pd.DataFrame( + 0.0, + index=pd.Index([10, 11], name="chooser_id"), + columns=["car", "bus", "walk"], + dtype=np.float64, + ) + + error_terms = logit.sample_nested_logit_exact_leaf_error_terms( + DummyState(), alt_utilities, nest_spec + ) + + expected = pd.DataFrame( + { + "car": [0.7, 1.9], + "bus": [1.2, 2.4], + "walk": [3.0, 6.0], + }, + index=alt_utilities.index, + dtype=np.float64, + ) + + pdt.assert_frame_equal(error_terms, expected) + + def test_make_choices_utility_based_sets_zero_rands(monkeypatch): def fake_add_ev1_random(_state, df, alt_info=None, alt_nrs_df=None): return pd.DataFrame( @@ -535,7 +606,6 @@ def fake_add_ev1_random(_state, df, alt_info=None, alt_nrs_df=None): choices, rands = logit.make_choices_utility_based( workflow.State().default_settings(), utilities, - name_mapping=np.array(["a", "b"]), nest_spec=None, trace_label=None, ) @@ -586,6 +656,8 @@ def gumbel_for_df(self, df, n): return eet_rng.gumbel(size=(len(df), n)) class EETDummyState: + settings = SimpleNamespace(nested_explicit_error_term_method="tree_walk") + @staticmethod def get_rn_generator(): return EETDummyRNG() @@ -625,12 +697,11 @@ def test_make_choices_vs_eet_nl_same_distribution(): # but for probability-based choice we usually use the flattened/logsummed probabilities. # To compare them fairly, we use the same base utilities. # car=0.5, bus=0.2, walk=0.4 - utils_df = pd.DataFrame( + nested_utils_df = pd.DataFrame( [[0.5, 0.2, 0.4, 0.0, 0.0]], columns=["car", "bus", "walk", "motorized", "root"], ) - utils_df = pd.concat([utils_df] * n_draws, ignore_index=True) - alt_order_array = np.array(["car", "bus", "walk"]) + utils_df = pd.concat([nested_utils_df] * n_draws, ignore_index=True) # 1. Probability-based Nested Logit choices mc_rng = np.random.default_rng(42) @@ -649,13 +720,13 @@ def default_settings(self): # Compute probabilities for NL using simulation logic nested_exp_utilities = simulate.compute_nested_exp_utilities( - utils_df[["car", "bus", "walk"]], nest_spec + nested_utils_df[["car", "bus", "walk"]], nest_spec ) nested_probabilities = simulate.compute_nested_probabilities( MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None ) probs = simulate.compute_base_probabilities( - nested_probabilities, nest_spec, utils_df[["car", "bus", "walk"]] + nested_probabilities, nest_spec, nested_utils_df[["car", "bus", "walk"]] ) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) @@ -667,6 +738,8 @@ def gumbel_for_df(self, df, n): return eet_rng.gumbel(size=(len(df), n)) class EETDummyState: + settings = SimpleNamespace(nested_explicit_error_term_method="tree_walk") + @staticmethod def get_rn_generator(): return EETDummyRNG() @@ -680,17 +753,9 @@ def tracing(self): return tracing - # For EET NL, we provide the utilities for all nodes. - # compute_nested_utilities handles the division by nesting coefficients for leaves - # and the logsum * coefficient for internal nodes. - nested_utilities = simulate.compute_nested_utilities( - utils_df[["car", "bus", "walk"]], nest_spec - ) - choices_eet = logit.make_choices_explicit_error_term_nl( EETDummyState(), - nested_utilities, - alt_order_array, + nested_utils_df[["car", "bus", "walk"]], nest_spec, trace_label=None, ) @@ -702,6 +767,1026 @@ def tracing(self): np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol) +def _repeated_utility_df(raw_utilities: pd.Series, n_draws: int) -> pd.DataFrame: + raw_utilities = pd.Series(raw_utilities, dtype=float) + return pd.DataFrame( + np.repeat(raw_utilities.to_numpy()[None, :], n_draws, axis=0), + columns=raw_utilities.index, + index=pd.RangeIndex(n_draws, name="chooser_id"), + ) + + +def _make_rng_state( + df: pd.DataFrame, + seed: int, + step_name: str, + nested_method: str | None = None, +) -> workflow.State: + state = workflow.State().default_settings() + if nested_method is not None: + state.settings.nested_explicit_error_term_method = nested_method + rng = state.get_rn_generator() + rng.set_base_seed(seed) + rng.add_channel(df.index.name, df) + rng.begin_step(step_name) + return state + + +def _finish_rng_state(state: workflow.State, step_name: str) -> None: + state.get_rn_generator().end_step(step_name) + + +def _choice_shares(choices: pd.Series, alt_names) -> pd.Series: + alt_names = pd.Index(alt_names) + counts = np.bincount(choices.to_numpy(dtype=int), minlength=len(alt_names)) + return pd.Series(counts / counts.sum(), index=alt_names) + + +def _expected_nested_logit_shares( + raw_utilities: pd.Series, + nest_spec: dict, + seed: int = 42, +) -> pd.Series: + raw_df = _repeated_utility_df(raw_utilities, n_draws=1) + step_name = f"expected_nested_logit_{len(raw_utilities)}_seed_{seed}" + state = _make_rng_state(raw_df, seed=seed, step_name=step_name) + try: + nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_df, nest_spec) + nested_probabilities = simulate.compute_nested_probabilities( + state, nested_exp_utilities, nest_spec, trace_label=None + ) + base_probabilities = simulate.compute_base_probabilities( + nested_probabilities, nest_spec, raw_df + ) + finally: + _finish_rng_state(state, step_name) + + return base_probabilities.iloc[0] + + +def _nested_logit_eet_shares( + raw_utilities: pd.Series, + nest_spec: dict, + method: str, + n_draws: int, + seed: int = 42, +) -> pd.Series: + raw_df = _repeated_utility_df(raw_utilities, n_draws=n_draws) + step_name = f"nested_eet_{method}_{n_draws}_{len(raw_utilities)}" + state = _make_rng_state( + raw_df, seed=seed, step_name=step_name, nested_method=method + ) + try: + choices = logit.make_choices_explicit_error_term_nl( + state, + raw_df, + nest_spec, + trace_label=None, + ) + finally: + _finish_rng_state(state, step_name) + + return _choice_shares(choices, raw_df.columns) + + +def _nested_logit_mc_shares( + raw_utilities: pd.Series, + nest_spec: dict, + n_draws: int, + seed: int = 42, +) -> pd.Series: + raw_df = _repeated_utility_df(raw_utilities, n_draws=n_draws) + step_name = f"nested_mc_{n_draws}_{len(raw_utilities)}" + state = _make_rng_state(raw_df, seed=seed, step_name=step_name) + try: + nested_exp_utilities = simulate.compute_nested_exp_utilities(raw_df, nest_spec) + nested_probabilities = simulate.compute_nested_probabilities( + state, nested_exp_utilities, nest_spec, trace_label=None + ) + base_probabilities = simulate.compute_base_probabilities( + nested_probabilities, nest_spec, raw_df + ) + choices, _ = logit.make_choices(state, base_probabilities, trace_label=None) + finally: + _finish_rng_state(state, step_name) + + return _choice_shares(choices, raw_df.columns) + + +def _assert_empirical_shares_close( + observed: pd.Series, + expected: pd.Series, + n_draws: int, + sigma_multiplier: float = 6.0, + variance_floor: float = 0.02, +) -> None: + expected = expected.reindex(observed.index) + tolerances = sigma_multiplier * np.sqrt( + np.maximum(expected * (1.0 - expected), variance_floor) / n_draws + ) + differences = (observed - expected).abs() + assert (differences <= tolerances).all(), pd.DataFrame( + { + "observed": observed, + "expected": expected, + "abs_diff": differences, + "tolerance": tolerances, + } + ).to_string() + + +def _nested_logit_method_share_matrix( + raw_utilities: pd.Series, + nest_spec: dict, + method: str, + n_draws: int, + seeds: list[int], +) -> np.ndarray: + share_samples = [] + for seed in seeds: + if method == "mc": + shares = _nested_logit_mc_shares( + raw_utilities, + nest_spec, + n_draws=n_draws, + seed=seed, + ) + else: + shares = _nested_logit_eet_shares( + raw_utilities, + nest_spec, + method=method, + n_draws=n_draws, + seed=seed, + ) + share_samples.append(shares.to_numpy()) + + return np.vstack(share_samples) + + +def _assert_average_empirical_shares_close( + observed_matrix: np.ndarray, + expected: pd.Series, + n_draws: int, + sigma_multiplier: float = 6.0, + variance_floor: float = 0.02, +) -> None: + expected = expected.astype(float) + mean_observed = pd.Series(observed_matrix.mean(axis=0), index=expected.index) + effective_draws = n_draws * observed_matrix.shape[0] + tolerances = sigma_multiplier * np.sqrt( + np.maximum(expected * (1.0 - expected), variance_floor) / effective_draws + ) + differences = (mean_observed - expected).abs() + assert (differences <= tolerances).all(), pd.DataFrame( + { + "mean_observed": mean_observed, + "expected": expected, + "abs_diff": differences, + "tolerance": tolerances, + } + ).to_string() + + +def _assert_average_share_deltas_close( + baseline_matrix: np.ndarray, + perturbed_matrix: np.ndarray, + baseline_expected: pd.Series, + perturbed_expected: pd.Series, + n_draws: int, + sigma_multiplier: float = 6.0, + variance_floor: float = 0.02, +) -> None: + observed_delta = pd.Series( + perturbed_matrix.mean(axis=0) - baseline_matrix.mean(axis=0), + index=baseline_expected.index, + ) + expected_delta = perturbed_expected - baseline_expected + effective_draws = n_draws * baseline_matrix.shape[0] + variances = ( + np.maximum(baseline_expected * (1.0 - baseline_expected), variance_floor) + + np.maximum(perturbed_expected * (1.0 - perturbed_expected), variance_floor) + ) / effective_draws + tolerances = sigma_multiplier * np.sqrt(variances) + differences = (observed_delta - expected_delta).abs() + assert (differences <= tolerances).all(), pd.DataFrame( + { + "observed_delta": observed_delta, + "expected_delta": expected_delta, + "abs_diff": differences, + "tolerance": tolerances, + } + ).to_string() + + +def _assert_nested_logit_methods_match_expected_across_seeds( + raw_utilities: pd.Series, + nest_spec: dict, + n_draws: int, + seeds: list[int], + methods: tuple[str, ...] = ("mc", "tree_walk", "exact_leaf"), +) -> dict[str, np.ndarray]: + expected = _expected_nested_logit_shares(raw_utilities, nest_spec) + share_matrices: dict[str, np.ndarray] = {} + for method in methods: + share_matrix = _nested_logit_method_share_matrix( + raw_utilities, + nest_spec, + method=method, + n_draws=n_draws, + seeds=seeds, + ) + _assert_average_empirical_shares_close(share_matrix, expected, n_draws=n_draws) + share_matrices[method] = share_matrix + + for i, left_method in enumerate(methods): + for right_method in methods[i + 1 :]: + left_mean = pd.Series( + share_matrices[left_method].mean(axis=0), + index=raw_utilities.columns.to_numpy(), + ) + right_mean = pd.Series( + share_matrices[right_method].mean(axis=0), + index=raw_utilities.columns.to_numpy(), + ) + tolerances = 8.0 * np.sqrt( + 2.0 + * np.maximum(expected * (1.0 - expected), 0.02) + / (n_draws * len(seeds)) + ) + differences = (left_mean - right_mean).abs() + assert (differences <= tolerances).all(), pd.DataFrame( + { + "left_method": left_method, + "right_method": right_method, + "left_mean": left_mean, + "right_mean": right_mean, + "abs_diff": differences, + "tolerance": tolerances, + } + ).to_string() + + return share_matrices + + +def _rmse(values: np.ndarray) -> float: + return float(np.sqrt(np.mean(np.square(values)))) + + +def _estimate_power_law_slope(draw_counts: np.ndarray, errors: np.ndarray) -> float: + clipped_errors = np.clip(errors.astype(float), np.finfo(float).eps, None) + slope, _intercept = np.polyfit( + np.log(draw_counts.astype(float)), np.log(clipped_errors), deg=1 + ) + return float(slope) + + +def _assert_three_level_nested_logit_methods_follow_power_law( + draw_counts: np.ndarray, + seeds: list[int], + slope_lower: float = -0.8, + slope_upper: float = -0.2, + pair_slope_lower: float | None = None, + pair_slope_upper: float | None = None, + max_final_method_error: float | None = None, + max_final_pair_error: float | None = None, +) -> None: + if pair_slope_lower is None: + pair_slope_lower = slope_lower + if pair_slope_upper is None: + pair_slope_upper = slope_upper + + method_names = ["mc", "tree_walk", "exact_leaf"] + pair_names = [ + ("mc", "tree_walk"), + ("mc", "exact_leaf"), + ("tree_walk", "exact_leaf"), + ] + + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.72, + "alternatives": [ + { + "name": "DRIVEALONE", + "coefficient": 0.35, + "alternatives": ["DA_FREE", "DA_PAY"], + } + ], + }, + { + "name": "TRANSIT", + "coefficient": 0.72, + "alternatives": [ + { + "name": "WALKACCESS", + "coefficient": 0.50, + "alternatives": ["WALK_LOC", "WALK_EXP"], + } + ], + }, + { + "name": "NONMOTORIZED", + "coefficient": 0.72, + "alternatives": ["WALK"], + }, + ], + } + raw_utilities = pd.Series( + { + "DA_FREE": 1.4, + "DA_PAY": 0.9, + "WALK_LOC": 0.5, + "WALK_EXP": 0.2, + "WALK": 0.0, + } + ) + + expected = _expected_nested_logit_shares(raw_utilities, nest_spec) + method_errors = {name: [] for name in method_names} + pair_errors = {pair: [] for pair in pair_names} + + for n_draws in draw_counts: + shares_by_method = {name: [] for name in method_names} + + for seed in seeds: + shares_by_method["mc"].append( + _nested_logit_mc_shares( + raw_utilities, + nest_spec, + n_draws=int(n_draws), + seed=seed, + ) + ) + shares_by_method["tree_walk"].append( + _nested_logit_eet_shares( + raw_utilities, + nest_spec, + method="tree_walk", + n_draws=int(n_draws), + seed=seed, + ) + ) + shares_by_method["exact_leaf"].append( + _nested_logit_eet_shares( + raw_utilities, + nest_spec, + method="exact_leaf", + n_draws=int(n_draws), + seed=seed, + ) + ) + + for method_name, share_samples in shares_by_method.items(): + share_matrix = np.vstack([share.to_numpy() for share in share_samples]) + centered = share_matrix - expected.to_numpy() + method_errors[method_name].append(_rmse(centered)) + + for left_name, right_name in pair_names: + left_matrix = np.vstack( + [share.to_numpy() for share in shares_by_method[left_name]] + ) + right_matrix = np.vstack( + [share.to_numpy() for share in shares_by_method[right_name]] + ) + pair_errors[(left_name, right_name)].append( + _rmse(left_matrix - right_matrix) + ) + + for method_name, errors in method_errors.items(): + errors = np.asarray(errors, dtype=float) + slope = _estimate_power_law_slope(draw_counts, errors) + assert ( + slope_lower <= slope <= slope_upper + ), f"{method_name} slope {slope:.3f} outside [{slope_lower}, {slope_upper}]" + assert ( + errors[-1] < errors[0] + ), f"{method_name} errors did not decrease: {errors}" + if max_final_method_error is not None: + assert ( + errors[-1] <= max_final_method_error + ), f"{method_name} final error {errors[-1]:.6f} exceeds {max_final_method_error:.6f}" + + for left_name, right_name in pair_names: + errors = np.asarray(pair_errors[(left_name, right_name)], dtype=float) + slope = _estimate_power_law_slope(draw_counts, errors) + assert ( + pair_slope_lower <= slope <= pair_slope_upper + ), f"{left_name} vs {right_name} slope {slope:.3f} outside [{pair_slope_lower}, {pair_slope_upper}]" + assert ( + errors[-1] < errors[0] + ), f"{left_name} vs {right_name} errors did not decrease: {errors}" + if max_final_pair_error is not None: + assert ( + errors[-1] <= max_final_pair_error + ), f"{left_name} vs {right_name} final error {errors[-1]:.6f} exceeds {max_final_pair_error:.6f}" + + +NESTED_LOGIT_EXACT_PARITY_CASES = [ + pytest.param( + { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.72, + "alternatives": ["DA_FREE", "DA_PAY"], + }, + {"name": "NONMOTORIZED", "coefficient": 0.80, "alternatives": ["WALK"]}, + ], + }, + pd.Series({"DA_FREE": 1.2, "DA_PAY": 0.7, "WALK": 0.1}), + np.array(["DA_FREE", "DA_PAY", "WALK"]), + id="two_level_single_leaf_nest", + ), + pytest.param( + { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.72, + "alternatives": [ + { + "name": "DRIVEALONE", + "coefficient": 0.35, + "alternatives": ["DA_FREE", "DA_PAY"], + } + ], + }, + { + "name": "TRANSIT", + "coefficient": 0.72, + "alternatives": [ + { + "name": "WALKACCESS", + "coefficient": 0.50, + "alternatives": ["WALK_LOC", "WALK_EXP"], + } + ], + }, + { + "name": "NONMOTORIZED", + "coefficient": 0.72, + "alternatives": ["WALK"], + }, + ], + }, + pd.Series( + { + "DA_FREE": 1.4, + "DA_PAY": 0.9, + "WALK_LOC": 0.5, + "WALK_EXP": 0.2, + "WALK": 0.0, + } + ), + np.array(["DA_FREE", "DA_PAY", "WALK_LOC", "WALK_EXP", "WALK"]), + id="three_level_single_leaf_chains", + ), + pytest.param( + { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "MOTORIZED", + "coefficient": 0.78, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.62, + "alternatives": ["DA_FREE", "DA_PAY"], + }, + { + "name": "RIDEHAIL", + "coefficient": 0.58, + "alternatives": ["RH_SHARED", "RH_SOLO"], + }, + ], + }, + { + "name": "ACTIVE", + "coefficient": 0.85, + "alternatives": ["BIKE", "WALK"], + }, + ], + }, + pd.Series( + { + "DA_FREE": 1.1, + "DA_PAY": 0.8, + "RH_SHARED": 0.7, + "RH_SOLO": 0.9, + "BIKE": 0.2, + "WALK": 0.0, + } + ), + np.array(["DA_FREE", "DA_PAY", "RH_SHARED", "RH_SOLO", "BIKE", "WALK"]), + id="three_level_balanced", + ), + pytest.param( + { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.72, + "alternatives": [ + { + "name": "DRIVE", + "coefficient": 0.60, + "alternatives": [ + { + "name": "SOLO", + "coefficient": 0.45, + "alternatives": ["DA_FREE", "DA_PAY"], + } + ], + } + ], + }, + { + "name": "TRANSIT", + "coefficient": 0.75, + "alternatives": [ + { + "name": "ACCESS", + "coefficient": 0.55, + "alternatives": [ + { + "name": "LOCAL", + "coefficient": 0.50, + "alternatives": ["WALK_LOC", "WALK_EXP"], + } + ], + } + ], + }, + {"name": "ACTIVE", "coefficient": 0.82, "alternatives": ["WALK"]}, + ], + }, + pd.Series( + { + "DA_FREE": 1.5, + "DA_PAY": 1.0, + "WALK_LOC": 0.7, + "WALK_EXP": 0.4, + "WALK": 0.1, + } + ), + np.array(["DA_FREE", "DA_PAY", "WALK_LOC", "WALK_EXP", "WALK"]), + id="four_level_single_leaf_chains", + ), + pytest.param( + { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "MOTORIZED", + "coefficient": 0.80, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.68, + "alternatives": [ + { + "name": "SOLO", + "coefficient": 0.48, + "alternatives": ["DA_FREE", "DA_PAY"], + }, + { + "name": "SHARED", + "coefficient": 0.52, + "alternatives": ["SR2", "SR3"], + }, + ], + }, + { + "name": "TRANSIT", + "coefficient": 0.72, + "alternatives": [ + { + "name": "WALKACCESS", + "coefficient": 0.55, + "alternatives": ["WALK_LOC", "WALK_EXP"], + } + ], + }, + ], + }, + {"name": "ACTIVE", "coefficient": 0.88, "alternatives": ["BIKE"]}, + ], + }, + pd.Series( + { + "DA_FREE": 1.4, + "DA_PAY": 1.0, + "SR2": 0.8, + "SR3": 0.6, + "WALK_LOC": 0.7, + "WALK_EXP": 0.3, + "BIKE": 0.1, + } + ), + np.array(["DA_FREE", "DA_PAY", "SR2", "SR3", "WALK_LOC", "WALK_EXP", "BIKE"]), + id="four_level_mixed_structure", + ), +] + + +REALISTIC_NESTED_LOGIT_FAST_CASES = [ + { + "id": "mtc_extended_tour_mode_choice_style", + "nest_spec": { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.72, + "alternatives": [ + { + "name": "DRIVEALONE", + "coefficient": 0.35, + "alternatives": ["DRIVEALONEFREE", "DRIVEALONEPAY"], + }, + { + "name": "SHAREDRIDE2", + "coefficient": 0.35, + "alternatives": ["SHARED2FREE", "SHARED2PAY"], + }, + { + "name": "SHAREDRIDE3", + "coefficient": 0.40, + "alternatives": ["SHARED3FREE", "SHARED3PAY"], + }, + ], + }, + { + "name": "NONMOTORIZED", + "coefficient": 0.80, + "alternatives": ["WALK", "BIKE"], + }, + { + "name": "TRANSIT", + "coefficient": 0.60, + "alternatives": [ + { + "name": "WALKACCESS", + "coefficient": 0.50, + "alternatives": [ + "WALK_LOC", + "WALK_LRF", + "WALK_EXP", + "WALK_HVY", + "WALK_COM", + ], + }, + { + "name": "DRIVEACCESS", + "coefficient": 0.45, + "alternatives": [ + "DRIVE_LOC", + "DRIVE_LRF", + "DRIVE_EXP", + "DRIVE_HVY", + "DRIVE_COM", + ], + }, + ], + }, + { + "name": "RIDEHAIL", + "coefficient": 0.65, + "alternatives": ["TAXI", "TNC_SINGLE", "TNC_SHARED"], + }, + ], + }, + "raw_utilities": pd.Series( + { + "DRIVEALONEFREE": 1.60, + "DRIVEALONEPAY": 1.10, + "SHARED2FREE": 1.05, + "SHARED2PAY": 0.82, + "SHARED3FREE": 0.70, + "SHARED3PAY": 0.48, + "WALK": -0.20, + "BIKE": 0.05, + "WALK_LOC": 0.15, + "WALK_LRF": 0.05, + "WALK_EXP": 0.22, + "WALK_HVY": 0.10, + "WALK_COM": -0.03, + "DRIVE_LOC": 0.42, + "DRIVE_LRF": 0.34, + "DRIVE_EXP": 0.54, + "DRIVE_HVY": 0.38, + "DRIVE_COM": 0.26, + "TAXI": 0.30, + "TNC_SINGLE": 0.45, + "TNC_SHARED": 0.18, + } + ), + }, + { + "id": "semcog_tour_mode_choice_style", + "nest_spec": { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.78, + "alternatives": ["DRIVEALONE", "SHARED2", "SHARED3"], + }, + { + "name": "NONMOTORIZED", + "coefficient": 0.85, + "alternatives": ["WALK", "BIKE"], + }, + { + "name": "TRANSIT", + "coefficient": 0.64, + "alternatives": [ + { + "name": "WALKACCESS", + "coefficient": 0.56, + "alternatives": ["WALK_LOC", "WALK_PRM", "WALK_MIX"], + }, + { + "name": "PNRACCESS", + "coefficient": 0.52, + "alternatives": ["PNR_LOC", "PNR_PRM", "PNR_MIX"], + }, + { + "name": "KNRACCESS", + "coefficient": 0.50, + "alternatives": ["KNR_LOC", "KNR_PRM", "KNR_MIX"], + }, + ], + }, + { + "name": "SCHOOL_BUS", + "coefficient": 0.92, + "alternatives": ["SCHOOLBUS"], + }, + { + "name": "RIDEHAIL", + "coefficient": 0.68, + "alternatives": ["TAXI", "TNC_SINGLE", "TNC_SHARED"], + }, + ], + }, + "raw_utilities": pd.Series( + { + "DRIVEALONE": 1.45, + "SHARED2": 1.08, + "SHARED3": 0.76, + "WALK": -0.10, + "BIKE": 0.12, + "WALK_LOC": 0.10, + "WALK_PRM": 0.18, + "WALK_MIX": 0.06, + "PNR_LOC": 0.30, + "PNR_PRM": 0.36, + "PNR_MIX": 0.26, + "KNR_LOC": 0.27, + "KNR_PRM": 0.32, + "KNR_MIX": 0.21, + "SCHOOLBUS": 0.24, + "TAXI": 0.22, + "TNC_SINGLE": 0.40, + "TNC_SHARED": 0.16, + } + ), + }, +] + + +@pytest.mark.parametrize( + "nest_spec,raw_utilities,_alt_order_array", + NESTED_LOGIT_EXACT_PARITY_CASES, +) +def test_make_choices_vs_eet_nl_exact_leaf_parity_across_structures( + nest_spec, raw_utilities, _alt_order_array +): + n_draws = 100_000 + expected = _expected_nested_logit_shares(raw_utilities, nest_spec) + observed = _nested_logit_eet_shares( + raw_utilities, + nest_spec, + method="exact_leaf", + n_draws=n_draws, + ) + + _assert_empirical_shares_close(observed, expected, n_draws=n_draws) + + +# def test_exact_leaf_error_terms_use_float64_with_float32_nested_utilities(): +# nest_spec = { +# "name": "root", +# "coefficient": 1.0, +# "alternatives": [ +# {"name": "motorized", "coefficient": 0.5, "alternatives": ["car", "bus"]}, +# "walk", +# ], +# } +# raw_utilities = pd.DataFrame( +# np.array([[0.5, 0.2, 0.4]], dtype=np.float32), +# index=pd.RangeIndex(1, name="chooser_id"), +# columns=["car", "bus", "walk"], +# ) +# # nested_utilities = simulate.compute_nested_utilities( +# # raw_utilities, nest_spec +# # ).astype(np.float32) +# # alt_order_array = np.array(["car", "bus", "walk"]) +# state = _make_rng_state( +# raw_utilities, +# seed=17, +# step_name="exact_leaf_float64_dtype", +# nested_method="exact_leaf", +# ) + +# try: +# error_terms = logit.sample_nested_logit_exact_leaf_error_terms( +# state, +# raw_utilities, +# nest_spec, +# ) +# finally: +# _finish_rng_state(state, "exact_leaf_float64_dtype") + +# assert all(dtype == np.float64 for dtype in error_terms.dtypes) + + +@pytest.mark.parametrize( + "nest_spec,raw_utilities,_alt_order_array", + [ + NESTED_LOGIT_EXACT_PARITY_CASES[1], + NESTED_LOGIT_EXACT_PARITY_CASES[3], + ], +) +def test_make_choices_vs_eet_nl_tree_walk_parity_deeper_structures( + nest_spec, raw_utilities, _alt_order_array +): + n_draws = 20_000 + expected = _expected_nested_logit_shares(raw_utilities, nest_spec) + observed = _nested_logit_eet_shares( + raw_utilities, + nest_spec, + method="tree_walk", + n_draws=n_draws, + ) + + _assert_empirical_shares_close(observed, expected, n_draws=n_draws) + + +def test_make_choices_utility_based_uses_exact_leaf_setting(monkeypatch): + sentinel = pd.Series([1, 0], index=pd.Index([100, 101], name="chooser_id")) + + def fake_exact_leaf( + state, + alt_utilities, + nest_spec, + trace_label, + trace_choosers=None, + allow_bad_utils=False, + ): + assert list(alt_utilities.columns) == ["car", "walk"] + return sentinel + + monkeypatch.setattr( + logit, + "make_choices_explicit_error_term_nl_exact_leaf", + fake_exact_leaf, + ) + + state = workflow.State().default_settings() + state.settings.nested_explicit_error_term_method = "exact_leaf" + utilities = pd.DataFrame( + [[0.0, 0.0], [0.0, 0.0]], + index=pd.Index([100, 101], name="chooser_id"), + columns=["car", "walk"], + ) + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + {"name": "motorized", "coefficient": 0.7, "alternatives": ["car"]}, + "walk", + ], + } + + choices, rands = logit.make_choices_utility_based( + state, + utilities, + nest_spec=nest_spec, + trace_label=None, + ) + + pdt.assert_series_equal(choices, sentinel) + pdt.assert_series_equal( + rands, + pd.Series([0, 0], index=pd.Index([100, 101], name="chooser_id")), + ) + + +@pytest.mark.parametrize( + "case", + REALISTIC_NESTED_LOGIT_FAST_CASES, + ids=[case["id"] for case in REALISTIC_NESTED_LOGIT_FAST_CASES], +) +def test_nested_logit_methods_match_expected_shares_for_realistic_tour_mode_choice_nests( + case, +): + _assert_nested_logit_methods_match_expected_across_seeds( + case["raw_utilities"], + case["nest_spec"], + n_draws=6_000, + seeds=[11, 23, 37], + ) + + +def test_nested_logit_share_response_tracks_utility_perturbations(): + case = REALISTIC_NESTED_LOGIT_FAST_CASES[0] + base_utilities = case["raw_utilities"] + perturbed_utilities = base_utilities.copy() + perturbed_utilities["DRIVE_EXP"] += 0.60 + perturbed_utilities["TNC_SHARED"] -= 0.45 + + baseline_expected = _expected_nested_logit_shares(base_utilities, case["nest_spec"]) + perturbed_expected = _expected_nested_logit_shares( + perturbed_utilities, case["nest_spec"] + ) + + expected_delta = perturbed_expected - baseline_expected + assert expected_delta["DRIVE_EXP"] > 0 + assert expected_delta["TNC_SHARED"] < 0 + + for method in ("mc", "tree_walk", "exact_leaf"): + baseline_matrix = _nested_logit_method_share_matrix( + base_utilities, + case["nest_spec"], + method=method, + n_draws=8_000, + seeds=[11, 23, 37], + ) + perturbed_matrix = _nested_logit_method_share_matrix( + perturbed_utilities, + case["nest_spec"], + method=method, + n_draws=8_000, + seeds=[11, 23, 37], + ) + _assert_average_empirical_shares_close( + baseline_matrix, + baseline_expected, + n_draws=8_000, + ) + _assert_average_empirical_shares_close( + perturbed_matrix, + perturbed_expected, + n_draws=8_000, + ) + _assert_average_share_deltas_close( + baseline_matrix, + perturbed_matrix, + baseline_expected, + perturbed_expected, + n_draws=8_000, + ) + + +def test_three_level_nested_logit_methods_follow_monte_carlo_power_law(): + _assert_three_level_nested_logit_methods_follow_power_law( + draw_counts=np.array([2_000, 8_000, 32_000]), + seeds=[17, 29, 43], + ) + + +# # @pytest.mark.slow +# def test_three_level_nested_logit_methods_follow_monte_carlo_power_law_large_draws(): +# _assert_three_level_nested_logit_methods_follow_power_law( +# draw_counts=np.array([8_000, 32_000, 128_000]), +# seeds=[17, 29, 43], +# slope_lower=-0.7, +# slope_upper=-0.3, +# pair_slope_lower=-1.0, +# pair_slope_upper=-0.2, +# max_final_method_error=0.0015, +# max_final_pair_error=0.0020, +# ) + + # # Interaction Dataset Tests # diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 57e30f7bc4..55add8a15e 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -39,7 +39,8 @@ With EET enabled, the final draw step changes: For multinomial logit, ActivitySim adds Gumbel draws to the utility table and takes the row-wise maximum. For nested logit, ActivitySim applies the same idea while walking the -nest tree, preserving the configured nesting structure. For details, see +nest tree (TODO: UPDATE DOCO, now exact sampler for error terms), preserving the configured +nesting structure. For details, see [this ATRF paper](https://australasiantransportresearchforum.org.au/frozen-randomness-at-the-individual-utility-level/). The model being simulated does not change. EET changes how the random utility model is From e0dbad8e084c4b411f371d4bed92e7a6e5a80d8c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 12:34:20 +1000 Subject: [PATCH 219/298] towards a unified choice interface --- .../abm/models/joint_tour_participation.py | 10 +-- activitysim/core/logit.py | 63 +++++++------------ activitysim/core/simulate.py | 17 ++--- 3 files changed, 35 insertions(+), 55 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index e6dbee8b64..2b848857d0 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -131,6 +131,7 @@ def participants_chooser( choosers: pd.DataFrame, spec: pd.DataFrame, trace_label: str, + nest_spec: Optional[dict, LogitNestSpec] = None, ) -> tuple[pd.Series, pd.Series]: """ custom alternative to logit.make_choices for simulate.simple_simulate @@ -256,7 +257,7 @@ def participants_chooser( else logit.make_choices ) choices, rands = choice_function( - state, probs_or_utils, trace_label=trace_label, trace_choosers=choosers + state, probs_or_utils, trace_label=trace_label, trace_choosers=choosers, nest_spec=nest_spec, ) participate = choices == PARTICIPATE_CHOICE @@ -429,13 +430,6 @@ def joint_tour_participation( if i not in model_settings.compute_settings.protect_columns: model_settings.compute_settings.protect_columns.append(i) - # This is related to the difference in nested logit and logit choice. As soon as alt_order_array - # is removed from arguments to make_choices_explicit_error_term_nl this guard can be removed. - if state.settings.use_explicit_error_terms: - assert ( - nest_spec is None - ), "Nested logit model custom chooser for EET requires name_mapping, currently not implemented in jtp" - custom_chooser = participants_chooser choices = simulate.simple_simulate_by_chunk_id( diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 9de0cd2f1e..d3e7b68cd9 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -540,7 +540,6 @@ def make_choices_explicit_error_term_nl( nest_spec, trace_label, trace_choosers=None, - allow_bad_utils=False, alts_context: AltsContext | None = None, alt_nrs_df: pd.DataFrame | None = None, ): @@ -579,17 +578,6 @@ def make_choices_explicit_error_term_nl( ) choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) - missing_choices = np.isnan(choices) - if missing_choices.any() and not allow_bad_utils: - report_bad_choices( - state, - missing_choices, - utilities_incl_unobs, - trace_label=tracing.extend_trace_label(trace_label, "bad_utils"), - msg="no alternative selected", - trace_choosers=trace_choosers, - ) - return pd.Series(choices, index=utilities_incl_unobs.index) @@ -598,7 +586,6 @@ def make_choices_explicit_error_term_mnl( utilities, trace_label, trace_choosers=None, - allow_bad_utils=False, alts_context: AltsContext | None = None, alt_nrs_df: pd.DataFrame | None = None, ) -> pd.Series: @@ -618,39 +605,25 @@ def make_choices_explicit_error_term_mnl( pandas.Series Choice indices aligned to the utilities columns order. """ - if trace_label: - state.tracing.trace_df( - utilities, tracing.extend_trace_label(trace_label, "utilities") - ) utilities_incl_unobs = add_ev1_random(state, utilities, alts_context, alt_nrs_df) - if trace_label: - state.tracing.trace_df( - utilities_incl_unobs, - tracing.extend_trace_label(trace_label, "utilities_eet"), - ) + + # if trace_label: + # state.tracing.trace_df( + # utilities_incl_unobs, + # tracing.extend_trace_label(trace_label, "utilities_eet"), + # ) + choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) - missing_choices = np.isnan(choices) # TODO: should we check for infs here too? - if missing_choices.any() and not allow_bad_utils: - report_bad_choices( - state, - missing_choices, - utilities, - trace_label=tracing.extend_trace_label(trace_label, "bad_utils"), - msg="no alternative selected", - # raise_error=False, - trace_choosers=trace_choosers, - ) - choices = pd.Series(choices, index=utilities_incl_unobs.index) - return choices + return pd.Series(choices, index=utilities_incl_unobs.index) def make_choices_utility_based( state: workflow.State, utilities: pd.DataFrame, - nest_spec=None, trace_label: str = None, trace_choosers=None, allow_bad_utils=False, + nest_spec=None, # Make consistent with make_choices for generalizability of custom chooser. alts_context: AltsContext | None = None, alt_nrs_df: pd.DataFrame | None = None, ) -> tuple[pd.Series, pd.Series]: @@ -663,8 +636,6 @@ def make_choices_utility_based( utilities : pandas.DataFrame Utilities with choosers as rows and alternatives as columns. Note for nested logit models, this should include only leaf nodes. - nest_spec : dict or LogitNestSpec, optional - Nest specification for the choice model. If None, will be treated as a multinomial logit model. trace_label : str Trace label for logging and tracing. trace_choosers : pandas.dataframe @@ -673,6 +644,8 @@ def make_choices_utility_based( which is indexed on index values from alternatives df. allow_bad_utils : bool If True, allows utilities with missing or invalid values without raising an error. + nest_spec : dict or LogitNestSpec, optional + Nest specification for the choice model. If None, will be treated as a multinomial logit model. alts_context : AltsContext, optional If provided, will be used to determine how many random numbers to sample and how to index them for the EET sampling. This is only relevant for multinomial logit models, and should be provided along with alt_nrs_df. @@ -698,7 +671,6 @@ def make_choices_utility_based( utilities, trace_label, trace_choosers, - allow_bad_utils, alts_context, alt_nrs_df, ) @@ -711,11 +683,22 @@ def make_choices_utility_based( nest_spec, trace_label, trace_choosers, - allow_bad_utils, alts_context, alt_nrs_df, ) + missing_choices = np.isnan(choices) # TODO: should we check for infs here too? + if missing_choices.any() and not allow_bad_utils: + report_bad_choices( + state, + missing_choices, + utilities, + trace_label=tracing.extend_trace_label(trace_label, "bad_utils"), + msg="no alternative selected", + # raise_error=False, + trace_choosers=trace_choosers, + ) + # EET does not expose per-row random draws; return zeros for compatibility. # Maybe exposing the seed of the chooser could be an alternative to re-create the random number for # debugging/tracing purposes? diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index ee409b216e..2c88dbdb2e 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -47,7 +47,7 @@ logger = logging.getLogger(__name__) CustomChooser_T = Callable[ - [workflow.State, pd.DataFrame, pd.DataFrame, pd.DataFrame, str], + [workflow.State, pd.DataFrame, pd.DataFrame, pd.DataFrame, str, dict | LogitNestSpec | None], tuple[pd.Series, pd.Series], ] @@ -1509,17 +1509,20 @@ def eval_nl( ) if custom_chooser: - # choices, rands = custom_chooser( - # state, raw_utilities, choosers, spec, nest_spec, trace_label - # ) - # TODO: Need to pass through nest_spec here, which would make it imcompatible with MC. - raise NotImplementedError("Nested custom choosers for EET not implemented in simulate.py") + choices, rands = custom_chooser( + state, + raw_utilities, + choosers, + spec, + trace_label, + nest_spec=nest_spec, + ) else: choices, rands = logit.make_choices_utility_based( state, raw_utilities, - nest_spec=nest_spec, trace_label=trace_label, + nest_spec=nest_spec, ) if want_logsums: From e944d1eb2f1d283e533ad9c32dcbaf6f40d6b69b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 13:10:31 +1000 Subject: [PATCH 220/298] test and doco updates --- activitysim/core/test/test_logit.py | 200 ++++++------------------- docs/dev-guide/explicit-error-terms.md | 22 +-- 2 files changed, 59 insertions(+), 163 deletions(-) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index a120120b52..af294849ec 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -4,7 +4,6 @@ import os.path import re -from types import SimpleNamespace import numpy as np import pandas as pd @@ -408,63 +407,6 @@ def get_rn_generator(): alt_nrs_df=None, ) - -# -# Nested Logit Structure Tests -# -def test_group_nest_names_by_level(): - nest_spec = { - "name": "root", - "coefficient": 1.0, - "alternatives": [ - {"name": "motorized", "coefficient": 0.7, "alternatives": ["car", "bus"]}, - "walk", - ], - } - - grouped = logit.group_nest_names_by_level(nest_spec) - - assert grouped == {1: ["root"], 2: ["motorized", "walk"], 3: ["car", "bus"]} - - -def test_choose_from_tree_selects_leaf(): - nest_utils = pd.Series( - { - "motorized": 2.0, - "walk": 1.0, - "car": 5.0, - "bus": 3.0, - } - ) - all_alternatives = {"walk", "car", "bus"} - root_alternatives = ["motorized", "walk"] - nest_alternatives_by_name = { - "root": ["motorized", "walk"], - "motorized": ["car", "bus"], - } - - choice = logit.choose_from_tree( - nest_utils, root_alternatives, all_alternatives, nest_alternatives_by_name - ) - - assert choice == "car" - - -def test_choose_from_tree_raises_on_missing_leaf(): - nest_utils = pd.Series({"motorized": 2.0, "walk": 1.0}) - all_alternatives = {"car", "bus"} - root_alternatives = ["motorized", "walk"] - nest_alternatives_by_name = { - "root": ["motorized", "walk"], - "motorized": ["car", "bus"], - } - - with pytest.raises(ValueError, match="no alternative found"): - logit.choose_from_tree( - nest_utils, root_alternatives, all_alternatives, nest_alternatives_by_name - ) - - # # EET Choice Behavior Tests # @@ -488,25 +430,20 @@ def fake_add_ev1_random(_state, _df, alt_info=None, alt_nrs_df=None): def test_make_choices_eet_nl(monkeypatch): - def fake_add_ev1_random(_state, df, alt_info=None, alt_nrs_df=None): - assert {"root", "motorized", "walk", "car", "bus"}.issubset(df.columns) - - nested_utils_with_errors = pd.DataFrame(0.0, index=df.index, columns=df.columns) - nested_utils_with_errors.loc[10, ["motorized", "walk", "car", "bus"]] = [ - 2.0, - 1.0, - 5.0, - 3.0, - ] - nested_utils_with_errors.loc[11, ["motorized", "walk", "car", "bus"]] = [ - 1.0, - 4.0, - 2.0, - 3.0, - ] - return nested_utils_with_errors + def fake_sample_nested_logit_exact_leaf_error_terms(_state, df, nest_spec): + assert nest_spec["name"] == "root" + assert list(df.columns) == ["walk", "car", "bus"] - monkeypatch.setattr(logit, "add_ev1_random", fake_add_ev1_random) + error_terms = pd.DataFrame(0.0, index=df.index, columns=df.columns) + error_terms.loc[10, ["walk", "car", "bus"]] = [1.0, 5.0, 3.0] + error_terms.loc[11, ["walk", "car", "bus"]] = [4.0, 2.0, 3.0] + return error_terms + + monkeypatch.setattr( + logit, + "sample_nested_logit_exact_leaf_error_terms", + fake_sample_nested_logit_exact_leaf_error_terms, + ) nest_spec = { "name": "root", @@ -518,7 +455,6 @@ def fake_add_ev1_random(_state, df, alt_info=None, alt_nrs_df=None): } state = workflow.State().default_settings() - state.settings.nested_explicit_error_term_method = "tree_walk" monkeypatch.setattr(state.tracing, "trace_df", lambda *args, **kwargs: None) choices = logit.make_choices_explicit_error_term_nl( @@ -652,12 +588,13 @@ def get_rn_generator(): eet_rng = np.random.default_rng(123) class EETDummyRNG: + def random_for_df(self, df, n=1): + return eet_rng.random((len(df), n)) + def gumbel_for_df(self, df, n): return eet_rng.gumbel(size=(len(df), n)) class EETDummyState: - settings = SimpleNamespace(nested_explicit_error_term_method="tree_walk") - @staticmethod def get_rn_generator(): return EETDummyRNG() @@ -697,11 +634,11 @@ def test_make_choices_vs_eet_nl_same_distribution(): # but for probability-based choice we usually use the flattened/logsummed probabilities. # To compare them fairly, we use the same base utilities. # car=0.5, bus=0.2, walk=0.4 - nested_utils_df = pd.DataFrame( - [[0.5, 0.2, 0.4, 0.0, 0.0]], - columns=["car", "bus", "walk", "motorized", "root"], + leaf_utilities = pd.DataFrame( + [[0.5, 0.2, 0.4]], + columns=["car", "bus", "walk"], ) - utils_df = pd.concat([nested_utils_df] * n_draws, ignore_index=True) + utils_df = pd.concat([leaf_utilities] * n_draws, ignore_index=True) # 1. Probability-based Nested Logit choices mc_rng = np.random.default_rng(42) @@ -719,14 +656,12 @@ def default_settings(self): return self # Compute probabilities for NL using simulation logic - nested_exp_utilities = simulate.compute_nested_exp_utilities( - nested_utils_df[["car", "bus", "walk"]], nest_spec - ) + nested_exp_utilities = simulate.compute_nested_exp_utilities(utils_df, nest_spec) nested_probabilities = simulate.compute_nested_probabilities( MCDummyState(), nested_exp_utilities, nest_spec, trace_label=None ) probs = simulate.compute_base_probabilities( - nested_probabilities, nest_spec, nested_utils_df[["car", "bus", "walk"]] + nested_probabilities, nest_spec, utils_df ) choices_mc, _ = logit.make_choices(MCDummyState(), probs, trace_label=None) @@ -734,12 +669,13 @@ def default_settings(self): eet_rng = np.random.default_rng(123) class EETDummyRNG: + def random_for_df(self, df, n=1): + return eet_rng.random((len(df), n)) + def gumbel_for_df(self, df, n): return eet_rng.gumbel(size=(len(df), n)) class EETDummyState: - settings = SimpleNamespace(nested_explicit_error_term_method="tree_walk") - @staticmethod def get_rn_generator(): return EETDummyRNG() @@ -755,7 +691,7 @@ def tracing(self): choices_eet = logit.make_choices_explicit_error_term_nl( EETDummyState(), - nested_utils_df[["car", "bus", "walk"]], + utils_df, nest_spec, trace_label=None, ) @@ -780,11 +716,8 @@ def _make_rng_state( df: pd.DataFrame, seed: int, step_name: str, - nested_method: str | None = None, ) -> workflow.State: state = workflow.State().default_settings() - if nested_method is not None: - state.settings.nested_explicit_error_term_method = nested_method rng = state.get_rn_generator() rng.set_base_seed(seed) rng.add_channel(df.index.name, df) @@ -827,15 +760,12 @@ def _expected_nested_logit_shares( def _nested_logit_eet_shares( raw_utilities: pd.Series, nest_spec: dict, - method: str, n_draws: int, seed: int = 42, ) -> pd.Series: raw_df = _repeated_utility_df(raw_utilities, n_draws=n_draws) - step_name = f"nested_eet_{method}_{n_draws}_{len(raw_utilities)}" - state = _make_rng_state( - raw_df, seed=seed, step_name=step_name, nested_method=method - ) + step_name = f"nested_eet_exact_leaf_{n_draws}_{len(raw_utilities)}" + state = _make_rng_state(raw_df, seed=seed, step_name=step_name) try: choices = logit.make_choices_explicit_error_term_nl( state, @@ -911,14 +841,15 @@ def _nested_logit_method_share_matrix( n_draws=n_draws, seed=seed, ) - else: + elif method == "exact_leaf": shares = _nested_logit_eet_shares( raw_utilities, nest_spec, - method=method, n_draws=n_draws, seed=seed, ) + else: + raise ValueError(f"unknown nested-logit share method: {method}") share_samples.append(shares.to_numpy()) return np.vstack(share_samples) @@ -984,7 +915,7 @@ def _assert_nested_logit_methods_match_expected_across_seeds( nest_spec: dict, n_draws: int, seeds: list[int], - methods: tuple[str, ...] = ("mc", "tree_walk", "exact_leaf"), + methods: tuple[str, ...] = ("mc", "exact_leaf"), ) -> dict[str, np.ndarray]: expected = _expected_nested_logit_shares(raw_utilities, nest_spec) share_matrices: dict[str, np.ndarray] = {} @@ -1003,11 +934,11 @@ def _assert_nested_logit_methods_match_expected_across_seeds( for right_method in methods[i + 1 :]: left_mean = pd.Series( share_matrices[left_method].mean(axis=0), - index=raw_utilities.columns.to_numpy(), + index=raw_utilities.index.to_numpy(), ) right_mean = pd.Series( share_matrices[right_method].mean(axis=0), - index=raw_utilities.columns.to_numpy(), + index=raw_utilities.index.to_numpy(), ) tolerances = 8.0 * np.sqrt( 2.0 @@ -1056,12 +987,8 @@ def _assert_three_level_nested_logit_methods_follow_power_law( if pair_slope_upper is None: pair_slope_upper = slope_upper - method_names = ["mc", "tree_walk", "exact_leaf"] - pair_names = [ - ("mc", "tree_walk"), - ("mc", "exact_leaf"), - ("tree_walk", "exact_leaf"), - ] + method_names = ["mc", "exact_leaf"] + pair_names = [("mc", "exact_leaf")] nest_spec = { "name": "root", @@ -1122,20 +1049,10 @@ def _assert_three_level_nested_logit_methods_follow_power_law( seed=seed, ) ) - shares_by_method["tree_walk"].append( - _nested_logit_eet_shares( - raw_utilities, - nest_spec, - method="tree_walk", - n_draws=int(n_draws), - seed=seed, - ) - ) shares_by_method["exact_leaf"].append( _nested_logit_eet_shares( raw_utilities, nest_spec, - method="exact_leaf", n_draws=int(n_draws), seed=seed, ) @@ -1584,7 +1501,6 @@ def test_make_choices_vs_eet_nl_exact_leaf_parity_across_structures( observed = _nested_logit_eet_shares( raw_utilities, nest_spec, - method="exact_leaf", n_draws=n_draws, ) @@ -1613,7 +1529,6 @@ def test_make_choices_vs_eet_nl_exact_leaf_parity_across_structures( # raw_utilities, # seed=17, # step_name="exact_leaf_float64_dtype", -# nested_method="exact_leaf", # ) # try: @@ -1627,51 +1542,32 @@ def test_make_choices_vs_eet_nl_exact_leaf_parity_across_structures( # assert all(dtype == np.float64 for dtype in error_terms.dtypes) - -@pytest.mark.parametrize( - "nest_spec,raw_utilities,_alt_order_array", - [ - NESTED_LOGIT_EXACT_PARITY_CASES[1], - NESTED_LOGIT_EXACT_PARITY_CASES[3], - ], -) -def test_make_choices_vs_eet_nl_tree_walk_parity_deeper_structures( - nest_spec, raw_utilities, _alt_order_array -): - n_draws = 20_000 - expected = _expected_nested_logit_shares(raw_utilities, nest_spec) - observed = _nested_logit_eet_shares( - raw_utilities, - nest_spec, - method="tree_walk", - n_draws=n_draws, - ) - - _assert_empirical_shares_close(observed, expected, n_draws=n_draws) - - -def test_make_choices_utility_based_uses_exact_leaf_setting(monkeypatch): +def test_make_choices_utility_based_routes_nested_logit_to_nl_eet(monkeypatch): sentinel = pd.Series([1, 0], index=pd.Index([100, 101], name="chooser_id")) - def fake_exact_leaf( + def fake_make_choices_explicit_error_term_nl( state, alt_utilities, nest_spec, trace_label, trace_choosers=None, - allow_bad_utils=False, + alts_context=None, + alt_nrs_df=None, ): assert list(alt_utilities.columns) == ["car", "walk"] + assert trace_label == "test.make_choices_utility_based" + assert trace_choosers is None + assert alts_context is None + assert alt_nrs_df is None return sentinel monkeypatch.setattr( logit, - "make_choices_explicit_error_term_nl_exact_leaf", - fake_exact_leaf, + "make_choices_explicit_error_term_nl", + fake_make_choices_explicit_error_term_nl, ) state = workflow.State().default_settings() - state.settings.nested_explicit_error_term_method = "exact_leaf" utilities = pd.DataFrame( [[0.0, 0.0], [0.0, 0.0]], index=pd.Index([100, 101], name="chooser_id"), @@ -1690,7 +1586,7 @@ def fake_exact_leaf( state, utilities, nest_spec=nest_spec, - trace_label=None, + trace_label="test", ) pdt.assert_series_equal(choices, sentinel) @@ -1732,7 +1628,7 @@ def test_nested_logit_share_response_tracks_utility_perturbations(): assert expected_delta["DRIVE_EXP"] > 0 assert expected_delta["TNC_SHARED"] < 0 - for method in ("mc", "tree_walk", "exact_leaf"): + for method in ("mc", "exact_leaf"): baseline_matrix = _nested_logit_method_share_matrix( base_utilities, case["nest_spec"], diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 55add8a15e..e338c82613 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -4,7 +4,8 @@ Explicit Error Terms (EET) is an alternative way to simulate choices from ActivitySim's logit models. It keeps the same systematic utilities and the same random-utility interpretation as the standard method, but changes how the final simulated choice is -drawn. +drawn. For details, see +[this ATRF paper](https://australasiantransportresearchforum.org.au/frozen-randomness-at-the-individual-utility-level/). For user-facing guidance on when to use EET, see {ref}`explicit_error_terms_ways_to_run`. @@ -33,18 +34,17 @@ Under the default ActivitySim simulation path, choice drawing works like this: With EET enabled, the final draw step changes: 1. Compute systematic utilities. -2. Draw one iid EV1 error term for each chooser-alternative pair. -3. Add that error term to the systematic utility. +2. Draw error terms for each chooser-alternative pair. +3. Add those error terms to the systematic utilities. 4. Choose the alternative with the highest total utility. -For multinomial logit, ActivitySim adds Gumbel draws to the utility table and takes the -row-wise maximum. For nested logit, ActivitySim applies the same idea while walking the -nest tree (TODO: UPDATE DOCO, now exact sampler for error terms), preserving the configured -nesting structure. For details, see -[this ATRF paper](https://australasiantransportresearchforum.org.au/frozen-randomness-at-the-individual-utility-level/). - -The model being simulated does not change. EET changes how the random utility model is -sampled, not the underlying utility specification. +For multinomial logit, the error term distribution is i.i.d. Gumbel and draws are generated +by inverting the cumulative density function. For nested logit, this method is not available +due to correlations between error terms. Instead, ActivitySim makes use of recent advances +regarding the [representation of nested logit models](https://doi.org/10.1017/S026646662000047X) +and combines this with +[exact numerical sampling methods](https://doi.org/10.1007/978-3-030-52915-4) +to draw error terms of all fundamental alternatives (leafs). ## Practical Effects From 968da5727291cf14a862ce343dc87ffa3887a373 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 13:32:15 +1000 Subject: [PATCH 221/298] jtp fix Co-authored-by: Copilot --- .../abm/models/joint_tour_participation.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 2b848857d0..2b441d82a4 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -251,14 +251,21 @@ def participants_chooser( f"{num_tours_remaining} tours could not be satisfied after {iter} iterations" ) - choice_function = ( - logit.make_choices_utility_based - if state.settings.use_explicit_error_terms - else logit.make_choices - ) - choices, rands = choice_function( - state, probs_or_utils, trace_label=trace_label, trace_choosers=choosers, nest_spec=nest_spec, - ) + if state.settings.use_explicit_error_terms: + choices, rands = logit.make_choices_utility_based( + state, + probs_or_utils, + trace_label=trace_label, + trace_choosers=choosers, + nest_spec=nest_spec, + ) + else: + choices, rands = logit.make_choices( + state, + probs_or_utils, + trace_label=trace_label, + trace_choosers=choosers, + ) participate = choices == PARTICIPATE_CHOICE # satisfaction indexed by tour_id From 04b2a28be7189a738f4f84eb4f786345d3106d86 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 14:11:35 +1000 Subject: [PATCH 222/298] fused location sampling Co-authored-by: Copilot --- activitysim/core/interaction_sample.py | 15 +--- activitysim/core/random.py | 64 +++++++++++++++++ .../core/test/test_interaction_sample.py | 68 ++++++++++++++++++- activitysim/core/test/test_random.py | 39 +++++++++++ 4 files changed, 171 insertions(+), 15 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 4241fd6935..51414930e4 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -58,19 +58,10 @@ def make_sample_choices_utility_based( utilities = utilities[~zero_probs] choosers = choosers[~zero_probs] - rands = state.get_rn_generator().gumbel_for_df( - utilities, n=alternative_count * sample_size - ) - chunk_sizer.log_df(trace_label, "rands", rands) - - rands = rands.reshape((utilities.shape[0], alternative_count, sample_size)) - rands += utilities.to_numpy()[:, :, np.newaxis] - - # choose maximum along all alternatives (axis 1) for all choosers and samples - chosen_destinations = np.argmax(rands, axis=1).reshape(-1) + chosen_destinations = state.get_rn_generator().gumbel_max_positions_for_df( + utilities, sample_size + ).reshape(-1) chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) - del rands - chunk_sizer.log_df(trace_label, "rands", None) chooser_idx = np.repeat(np.arange(utilities.shape[0]), sample_size) chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index ea42b24118..e63aa6c8ac 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -291,6 +291,47 @@ def gumbel_for_df(self, df, step_name, n=1): self.row_states.loc[df.index, "offset"] += n return rands + def gumbel_max_positions_for_df(self, utilities, step_name, sample_size): + """ + Return the winning alternative position for each chooser/sample pair + without materializing the full chooser-by-alternative-by-sample Gumbel array. + + Parameters + ---------- + utilities : pandas.DataFrame + DataFrame with one row per chooser and one column per alternative. + sample_size : int + Number of repeated sampled choices to make per chooser. + + Returns + ------- + positions : 2-D ndarray of int32 + Array with shape (len(utilities), sample_size) containing the column + position of the winning alternative for each chooser/sample pair. + """ + + assert self.step_name + assert self.step_name == step_name + + utility_values = utilities.to_numpy() + n_rows, n_alts = utility_values.shape + positions = np.empty((n_rows, sample_size), dtype=np.int32) + + generators = self._generators_for_df(utilities) + + for row_num, prng in enumerate(generators): + utility_row = utility_values[row_num] + row_gumbels = -np.log(-np.log(prng.rand(n_alts * sample_size))).reshape( + (sample_size, n_alts), order="F" + ) + positions[row_num, :] = np.argmax( + row_gumbels + utility_row[np.newaxis, :], + axis=1, + ) + + self.row_states.loc[utilities.index, "offset"] += n_alts * sample_size + return positions + def normal_for_df(self, df, step_name, mu, sigma, lognormal=False, size=None): """ Return a floating point random number in normal (or lognormal) distribution @@ -730,6 +771,29 @@ def gumbel_for_df(self, df, n=1): rands = channel.gumbel_for_df(df, self.step_name, n) return rands + def gumbel_max_positions_for_df(self, utilities, sample_size): + """ + Return the winning alternative position for each chooser/sample pair + using the appropriate channel for each chooser row. + + Parameters + ---------- + utilities : pandas.DataFrame + DataFrame with one row per chooser and one column per alternative. + sample_size : int + Number of repeated sampled choices to make per chooser. + + Returns + ------- + positions : 2-D ndarray of int32 + Array with shape (len(utilities), sample_size) containing the column + position of the winning alternative for each chooser/sample pair. + """ + channel = self.get_channel_for_df(utilities) + return channel.gumbel_max_positions_for_df( + utilities, self.step_name, sample_size + ) + def normal_for_df(self, df, mu=0, sigma=1, broadcast=False, size=None): """ Return a single floating point normal random number in range (-inf, inf) for each row in df diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 623b1622fb..34872941d2 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -228,9 +228,12 @@ class _DummyRngUtilityBased: def __init__(self, rands_3d): self.rands_3d = rands_3d - def gumbel_for_df(self, _utilities, n): - assert n == self.rands_3d.shape[1] * self.rands_3d.shape[2] - return self.rands_3d.reshape(-1) + def gumbel_max_positions_for_df(self, utilities, sample_size): + assert sample_size == self.rands_3d.shape[2] + return np.argmax( + self.rands_3d + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_heterogeneity(): @@ -295,3 +298,62 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het assert np.array_equal(out["prob"].to_numpy(), expected_prob_repeat) assert not np.array_equal(out["prob"].to_numpy(), wrong_prob_tile) + + +def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path(): + chooser_index = pd.Index([201, 202, 203], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([10, 11, 12, 13], name="alt_id")) + utilities = pd.DataFrame( + [[0.0, 0.3, -0.2, 0.1], [1.0, 0.2, 0.4, -0.5], [-0.1, 0.0, 0.8, 0.7]], + index=chooser_index, + ) + sample_size = 2 + n_alts = len(alternatives) + rands_3d = np.array( + [ + [[0.1, -0.3], [0.2, 0.4], [0.5, -0.1], [0.0, 0.2]], + [[-0.2, 0.3], [0.6, -0.5], [0.1, 0.7], [0.4, 0.2]], + [[0.0, 0.1], [0.3, -0.4], [0.2, 0.5], [-0.3, 0.2]], + ], + dtype=np.float64, + ) + state = _DummyState(_DummyRngUtilityBased(rands_3d)) + + out = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=n_alts, + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_fused_rng_matches_materialized", + chunk_sizer=_DummyChunkSizer(), + ) + + chosen_positions = np.argmax( + rands_3d + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) + chosen_flat = chosen_positions.reshape(-1) + chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_fused_rng_matches_materialized", + overflow_protection=True, + trace_choosers=choosers, + ).to_numpy() + + expected = pd.DataFrame( + { + "alt_id": alternatives.index.values[chosen_flat], + "prob": probs[chooser_idx, chosen_flat], + "person_id": choosers.index.values[chooser_idx], + } + ) + + pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) diff --git a/activitysim/core/test/test_random.py b/activitysim/core/test/test_random.py index bcbc602685..385fc87522 100644 --- a/activitysim/core/test/test_random.py +++ b/activitysim/core/test/test_random.py @@ -126,3 +126,42 @@ def test_channel(): npt.assert_almost_equal(np.asanyarray(rands).flatten(), test1_expected_rands2) rng.end_step("test_step") + + +def test_gumbel_max_positions_for_df_matches_materialized_path_and_offsets(): + persons = pd.DataFrame( + {"household_id": [1, 1, 2]}, + index=pd.Index([11, 12, 13], name="person_id"), + ) + utilities = pd.DataFrame( + [[0.5, -0.2, 1.1], [0.1, 0.2, -0.3], [2.0, 1.0, 0.0]], + index=persons.index, + ) + sample_size = 4 + n_alts = utilities.shape[1] + + baseline_rng = random.Random() + baseline_rng.set_base_seed(0) + baseline_rng.begin_step("test_step") + baseline_rng.add_channel("persons", persons) + + materialized = baseline_rng.gumbel_for_df(utilities, n=n_alts * sample_size) + expected_positions = np.argmax( + materialized.reshape((len(utilities), n_alts, sample_size)) + + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) + next_random_after_materialized = baseline_rng.random_for_df(persons) + baseline_rng.end_step("test_step") + + fused_rng = random.Random() + fused_rng.set_base_seed(0) + fused_rng.begin_step("test_step") + fused_rng.add_channel("persons", persons) + + observed_positions = fused_rng.gumbel_max_positions_for_df(utilities, sample_size) + next_random_after_fused = fused_rng.random_for_df(persons) + fused_rng.end_step("test_step") + + npt.assert_array_equal(observed_positions, expected_positions) + npt.assert_allclose(next_random_after_fused, next_random_after_materialized) From 8f0ae70c79a68814cbf495909945c7c753a183e9 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 14:23:13 +1000 Subject: [PATCH 223/298] c order to map the first n_alt rands to all alternatives for the first sample, etc Co-authored-by: Copilot --- activitysim/core/random.py | 5 ++++- activitysim/core/test/test_random.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index e63aa6c8ac..7ab6063255 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -319,10 +319,13 @@ def gumbel_max_positions_for_df(self, utilities, step_name, sample_size): generators = self._generators_for_df(utilities) + # for each chooser, generate the error terms for all samples at once. reshaping this + # in (default) C order means that the the first n_alts values are the gumbels for the + # first sample, the next n_alts values are the gumbels for the second sample, etc. for row_num, prng in enumerate(generators): utility_row = utility_values[row_num] row_gumbels = -np.log(-np.log(prng.rand(n_alts * sample_size))).reshape( - (sample_size, n_alts), order="F" + (sample_size, n_alts) ) positions[row_num, :] = np.argmax( row_gumbels + utility_row[np.newaxis, :], diff --git a/activitysim/core/test/test_random.py b/activitysim/core/test/test_random.py index 385fc87522..b56b55da73 100644 --- a/activitysim/core/test/test_random.py +++ b/activitysim/core/test/test_random.py @@ -147,9 +147,9 @@ def test_gumbel_max_positions_for_df_matches_materialized_path_and_offsets(): materialized = baseline_rng.gumbel_for_df(utilities, n=n_alts * sample_size) expected_positions = np.argmax( - materialized.reshape((len(utilities), n_alts, sample_size)) - + utilities.to_numpy()[:, :, np.newaxis], - axis=1, + materialized.reshape((len(utilities), sample_size, n_alts)) + + utilities.to_numpy()[:, np.newaxis, :], + axis=2, ) next_random_after_materialized = baseline_rng.random_for_df(persons) baseline_rng.end_step("test_step") From 510c6abc2e3c6332880161ad84f5b45f14660050 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 14:36:44 +1000 Subject: [PATCH 224/298] fused mnl ev1 argmax with alt_info Co-authored-by: Copilot --- activitysim/core/logit.py | 18 ++--- activitysim/core/random.py | 117 +++++++++++++++++++++++++++ activitysim/core/test/test_logit.py | 57 +++++++++---- activitysim/core/test/test_random.py | 78 ++++++++++++++++++ 4 files changed, 245 insertions(+), 25 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index d3e7b68cd9..0972ed8ff2 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -605,16 +605,16 @@ def make_choices_explicit_error_term_mnl( pandas.Series Choice indices aligned to the utilities columns order. """ - utilities_incl_unobs = add_ev1_random(state, utilities, alts_context, alt_nrs_df) - - # if trace_label: - # state.tracing.trace_df( - # utilities_incl_unobs, - # tracing.extend_trace_label(trace_label, "utilities_eet"), - # ) + if alts_context is None: + choices = state.get_rn_generator().gumbel_choice_positions_for_df(utilities) + else: + choices = state.get_rn_generator().gumbel_choice_positions_for_df( + utilities, + alt_nrs_df=alt_nrs_df, + n_rands=alts_context.n_alts_to_cover_max_id, + ) - choices = np.argmax(utilities_incl_unobs.to_numpy(), axis=1) - return pd.Series(choices, index=utilities_incl_unobs.index) + return pd.Series(choices, index=utilities.index) def make_choices_utility_based( diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 7ab6063255..67c07c4056 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -335,6 +335,72 @@ def gumbel_max_positions_for_df(self, utilities, step_name, sample_size): self.row_states.loc[utilities.index, "offset"] += n_alts * sample_size return positions + def gumbel_choice_positions_for_df( + self, + utilities, + step_name, + alt_nrs_df=None, + n_rands=None, + ): + """ + Return the winning alternative position for each chooser row without + materializing the utility-plus-error table. + + Parameters + ---------- + utilities : pandas.DataFrame + DataFrame with one row per chooser and one column per available alternative. + alt_nrs_df : pandas.DataFrame, optional + DataFrame aligned to `utilities` whose values identify which dense alternative + each utility column corresponds to. Use -999 for masked or unavailable positions. + n_rands : int, optional + Number of EV1 draws to generate per chooser row. Required when `alt_nrs_df` + is provided and may exceed the visible number of utility columns. + + Returns + ------- + positions : 1-D ndarray of int32 + Array with shape (len(utilities),) containing the winning column position + for each chooser row. + """ + + assert self.step_name + assert self.step_name == step_name + + utility_values = utilities.to_numpy() + n_rows, n_alts = utility_values.shape + positions = np.empty(n_rows, dtype=np.int32) + + if alt_nrs_df is not None: + assert alt_nrs_df.shape == utilities.shape + if n_rands is None: + raise ValueError("n_rands is required when alt_nrs_df is provided") + alt_nr_values = alt_nrs_df.to_numpy() + masked = alt_nr_values == -999 + safe_alt_nrs = np.where(masked, 0, alt_nr_values) + else: + if n_rands is None: + n_rands = n_alts + elif n_rands != n_alts: + raise ValueError("n_rands must equal utilities.shape[1] when alt_nrs_df is omitted") + alt_nr_values = masked = safe_alt_nrs = None + + generators = self._generators_for_df(utilities) + + for row_num, prng in enumerate(generators): + utility_row = utility_values[row_num] + row_gumbels = -np.log(-np.log(prng.rand(n_rands))) + + if alt_nrs_df is None: + positions[row_num] = np.argmax(row_gumbels + utility_row) + else: + candidate_values = utility_row + row_gumbels[safe_alt_nrs[row_num]] + candidate_values[masked[row_num]] = utility_row[masked[row_num]] + positions[row_num] = np.argmax(candidate_values) + + self.row_states.loc[utilities.index, "offset"] += n_rands + return positions + def normal_for_df(self, df, step_name, mu, sigma, lognormal=False, size=None): """ Return a floating point random number in normal (or lognormal) distribution @@ -797,6 +863,57 @@ def gumbel_max_positions_for_df(self, utilities, sample_size): utilities, self.step_name, sample_size ) + def gumbel_choice_positions_for_df(self, utilities, alt_nrs_df=None, n_rands=None): + """ + Return the winning alternative position for each chooser row. + + Parameters + ---------- + utilities : pandas.DataFrame + DataFrame with one row per chooser and one column per available alternative. + alt_nrs_df : pandas.DataFrame, optional + Dense-alternative mapping aligned to `utilities`. + n_rands : int, optional + Number of EV1 draws to generate per chooser row. + + Returns + ------- + positions : 1-D ndarray of int32 + """ + if not self.channels: + rng = np.random.RandomState(0) + utility_values = utilities.to_numpy() + positions = np.empty(len(utilities), dtype=np.int32) + + if alt_nrs_df is not None: + if n_rands is None: + raise ValueError("n_rands is required when alt_nrs_df is provided") + alt_nr_values = alt_nrs_df.to_numpy() + masked = alt_nr_values == -999 + safe_alt_nrs = np.where(masked, 0, alt_nr_values) + for row_num, utility_row in enumerate(utility_values): + row_gumbels = -np.log(-np.log(rng.rand(n_rands))) + candidate_values = utility_row + row_gumbels[safe_alt_nrs[row_num]] + candidate_values[masked[row_num]] = utility_row[masked[row_num]] + positions[row_num] = np.argmax(candidate_values) + else: + if n_rands is None: + n_rands = utility_values.shape[1] + for row_num, utility_row in enumerate(utility_values): + positions[row_num] = np.argmax( + -np.log(-np.log(rng.rand(n_rands))) + utility_row + ) + + return positions + + channel = self.get_channel_for_df(utilities) + return channel.gumbel_choice_positions_for_df( + utilities, + self.step_name, + alt_nrs_df=alt_nrs_df, + n_rands=n_rands, + ) + def normal_for_df(self, df, mu=0, sigma=1, broadcast=False, size=None): """ Return a single floating point normal random number in range (-inf, inf) for each row in df diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index af294849ec..45adcaa21c 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -411,22 +411,28 @@ def get_rn_generator(): # EET Choice Behavior Tests # def test_make_choices_eet_mnl(monkeypatch): - def fake_add_ev1_random(_state, _df, alt_info=None, alt_nrs_df=None): - return pd.DataFrame( - [[1.0, 3.0], [4.0, 2.0]], - index=[100, 101], - columns=["a", "b"], - ) + class DummyRNG: + def gumbel_choice_positions_for_df(self, utilities, alt_nrs_df=None, n_rands=None): + assert alt_nrs_df is None + assert n_rands is None + assert list(utilities.columns) == ["a", "b"] + return np.array([1, 0], dtype=np.int32) - monkeypatch.setattr(logit, "add_ev1_random", fake_add_ev1_random) + class DummyState: + @staticmethod + def get_rn_generator(): + return DummyRNG() choices = logit.make_choices_explicit_error_term_mnl( - workflow.State().default_settings(), + DummyState(), pd.DataFrame([[0.0, 0.0], [0.0, 0.0]], index=[100, 101], columns=["a", "b"]), trace_label=None, ) - pdt.assert_series_equal(choices, pd.Series([1, 0], index=[100, 101])) + pdt.assert_series_equal( + choices, + pd.Series([1, 0], index=[100, 101], dtype=np.int32), + ) def test_make_choices_eet_nl(monkeypatch): @@ -529,14 +535,24 @@ def get_rn_generator(): def test_make_choices_utility_based_sets_zero_rands(monkeypatch): - def fake_add_ev1_random(_state, df, alt_info=None, alt_nrs_df=None): - return pd.DataFrame( - [[2.0, 1.0], [0.5, 2.5]], - index=df.index, - columns=df.columns, - ) + def fake_make_choices_explicit_error_term_mnl( + _state, + utilities, + trace_label, + trace_choosers=None, + alts_context=None, + alt_nrs_df=None, + ): + assert trace_choosers is None + assert alts_context is None + assert alt_nrs_df is None + return pd.Series([0, 1], index=utilities.index) - monkeypatch.setattr(logit, "add_ev1_random", fake_add_ev1_random) + monkeypatch.setattr( + logit, + "make_choices_explicit_error_term_mnl", + fake_make_choices_explicit_error_term_mnl, + ) utilities = pd.DataFrame([[3.0, 2.0], [1.0, 4.0]], index=[11, 12]) choices, rands = logit.make_choices_utility_based( @@ -594,6 +610,15 @@ def random_for_df(self, df, n=1): def gumbel_for_df(self, df, n): return eet_rng.gumbel(size=(len(df), n)) + def gumbel_choice_positions_for_df(self, utilities, alt_nrs_df=None, n_rands=None): + assert alt_nrs_df is None + assert n_rands is None + return np.argmax( + eet_rng.gumbel(size=(len(utilities), utilities.shape[1])) + + utilities.to_numpy(), + axis=1, + ) + class EETDummyState: @staticmethod def get_rn_generator(): diff --git a/activitysim/core/test/test_random.py b/activitysim/core/test/test_random.py index b56b55da73..a15e8dfe9d 100644 --- a/activitysim/core/test/test_random.py +++ b/activitysim/core/test/test_random.py @@ -165,3 +165,81 @@ def test_gumbel_max_positions_for_df_matches_materialized_path_and_offsets(): npt.assert_array_equal(observed_positions, expected_positions) npt.assert_allclose(next_random_after_fused, next_random_after_materialized) + + +def test_gumbel_choice_positions_for_df_matches_materialized_path_and_offsets(): + persons = pd.DataFrame( + {"household_id": [1, 1, 2]}, + index=pd.Index([21, 22, 23], name="person_id"), + ) + utilities = pd.DataFrame( + [[0.5, -0.2, 1.1], [0.1, 0.2, -0.3], [2.0, 1.0, 0.0]], + index=persons.index, + ) + + baseline_rng = random.Random() + baseline_rng.set_base_seed(0) + baseline_rng.begin_step("test_step") + baseline_rng.add_channel("persons", persons) + + materialized = baseline_rng.gumbel_for_df(utilities, n=utilities.shape[1]) + expected_positions = np.argmax(materialized + utilities.to_numpy(), axis=1) + next_random_after_materialized = baseline_rng.random_for_df(persons) + baseline_rng.end_step("test_step") + + fused_rng = random.Random() + fused_rng.set_base_seed(0) + fused_rng.begin_step("test_step") + fused_rng.add_channel("persons", persons) + + observed_positions = fused_rng.gumbel_choice_positions_for_df(utilities) + next_random_after_fused = fused_rng.random_for_df(persons) + fused_rng.end_step("test_step") + + npt.assert_array_equal(observed_positions, expected_positions) + npt.assert_allclose(next_random_after_fused, next_random_after_materialized) + + +def test_gumbel_choice_positions_for_df_matches_dense_alt_mapping(): + persons = pd.DataFrame( + {"household_id": [1, 1]}, + index=pd.Index([31, 32], name="person_id"), + ) + utilities = pd.DataFrame( + [[2.0, 1.0], [0.3, 1.2]], + index=persons.index, + ) + alt_nrs_df = pd.DataFrame( + [[0, 2], [1, 2]], + index=persons.index, + ) + n_rands = 3 + + baseline_rng = random.Random() + baseline_rng.set_base_seed(0) + baseline_rng.begin_step("test_step") + baseline_rng.add_channel("persons", persons) + + dense = baseline_rng.gumbel_for_df(utilities, n=n_rands) + expected_positions = np.argmax( + utilities.to_numpy() + np.take_along_axis(dense, alt_nrs_df.to_numpy(), axis=1), + axis=1, + ) + next_random_after_materialized = baseline_rng.random_for_df(persons) + baseline_rng.end_step("test_step") + + fused_rng = random.Random() + fused_rng.set_base_seed(0) + fused_rng.begin_step("test_step") + fused_rng.add_channel("persons", persons) + + observed_positions = fused_rng.gumbel_choice_positions_for_df( + utilities, + alt_nrs_df=alt_nrs_df, + n_rands=n_rands, + ) + next_random_after_fused = fused_rng.random_for_df(persons) + fused_rng.end_step("test_step") + + npt.assert_array_equal(observed_positions, expected_positions) + npt.assert_allclose(next_random_after_fused, next_random_after_materialized) From 3ae81fa754e1ae8f303fc822d5b0260e14b98f26 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 15:36:23 +1000 Subject: [PATCH 225/298] stable sampling Co-authored-by: Copilot --- activitysim/abm/models/location_choice.py | 23 ++ .../abm/models/util/tour_destination.py | 25 +++ .../test_location_choice_sampling.py | 140 ++++++++++++ .../test_tour_destination_sampling.py | 202 ++++++++++++++++++ .../test_trip_destination_sampling.py | 187 ++++++++++++++++ activitysim/core/interaction_sample.py | 15 +- activitysim/core/random.py | 96 ++++++++- .../core/test/test_interaction_sample.py | 77 ++++++- activitysim/core/test/test_random.py | 48 +++++ 9 files changed, 804 insertions(+), 9 deletions(-) create mode 100644 activitysim/abm/test/test_misc/test_location_choice_sampling.py create mode 100644 activitysim/abm/test/test_misc/test_tour_destination_sampling.py create mode 100644 activitysim/abm/test/test_misc/test_trip_destination_sampling.py diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index dfe1108783..9b9b9a6431 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -117,6 +117,8 @@ def _location_sample( chunk_tag, trace_label, zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, ): """ select a sample of alternative locations. @@ -216,6 +218,8 @@ def _location_sample( compute_settings=model_settings.compute_settings.subcomponent_settings( "sample" ), + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) return choices @@ -232,6 +236,8 @@ def location_sample( chunk_size, chunk_tag, trace_label, + stable_alt_positions=None, + n_total_alts=None, ): # FIXME - MEMORY HACK - only include columns actually used in spec chooser_columns = model_settings.SIMULATE_CHOOSER_COLUMNS @@ -258,6 +264,8 @@ def location_sample( chunk_size, chunk_tag, trace_label, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) return choices @@ -367,6 +375,7 @@ def location_presample( chunk_size, chunk_tag, trace_label, + full_dest_size_terms=None, ): trace_label = tracing.extend_trace_label(trace_label, "presample") @@ -378,6 +387,12 @@ def location_presample( MAZ_size_terms, TAZ_size_terms = aggregate_size_terms( state, dest_size_terms, network_los, model_settings ) + if full_dest_size_terms is None: + full_dest_size_terms = dest_size_terms + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_dest_size_terms.index), name=DEST_TAZ + ).unique().sort_values() + stable_taz_positions = full_taz_index.get_indexer(TAZ_size_terms.index) # convert MAZ zone_id to 'TAZ' in choosers (persons_merged) # persons_merged[HOME_TAZ] = persons_merged[HOME_MAZ].map(maz_to_taz) @@ -412,6 +427,8 @@ def location_presample( chunk_tag, trace_label, zone_layer="taz", + stable_alt_positions=stable_taz_positions, + n_total_alts=len(full_taz_index), ) # print(f"taz_sample\n{taz_sample}") @@ -463,6 +480,8 @@ def run_location_sample( 23751, 14, 0.972732479292, 2 """ + full_dest_size_terms = dest_size_terms + logger.debug( f"dropping {(~(dest_size_terms.size_term > 0)).sum()} " f"of {len(dest_size_terms)} rows where size_term is zero" @@ -497,9 +516,11 @@ def run_location_sample( chunk_size, chunk_tag=f"{chunk_tag}.presample", trace_label=trace_label, + full_dest_size_terms=full_dest_size_terms, ) else: + stable_maz_positions = full_dest_size_terms.index.get_indexer(dest_size_terms.index) choices = location_sample( state, segment_name, @@ -511,6 +532,8 @@ def run_location_sample( chunk_size, chunk_tag=f"{chunk_tag}.sample", trace_label=trace_label, + stable_alt_positions=stable_maz_positions, + n_total_alts=len(full_dest_size_terms.index), ) return choices diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 0531a2caeb..5c6f704c30 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -82,6 +82,8 @@ def _destination_sample( chunk_tag, trace_label: str, zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, ): model_spec = simulate.spec_for_segment( state, @@ -159,6 +161,8 @@ def _destination_sample( compute_settings=model_settings.compute_settings.subcomponent_settings( "sample" ), + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) # if special person id is passed @@ -178,6 +182,7 @@ def destination_sample( model_settings: TourLocationComponentSettings, network_los, destination_size_terms, + full_destination_size_terms, estimator, chunk_size, trace_label, @@ -197,6 +202,9 @@ def destination_sample( # the name of the dest column to be returned in choices alt_dest_col_name = model_settings.ALT_DEST_COL_NAME + stable_maz_positions = full_destination_size_terms.index.get_indexer( + destination_size_terms.index + ) choices = _destination_sample( state, @@ -209,6 +217,8 @@ def destination_sample( alt_dest_col_name, chunk_tag=chunk_tag, trace_label=trace_label, + stable_alt_positions=stable_maz_positions, + n_total_alts=len(full_destination_size_terms.index), ) return choices @@ -557,6 +567,7 @@ def destination_presample( model_settings: TourLocationComponentSettings, network_los, destination_size_terms, + full_destination_size_terms, estimator, trace_label, ): @@ -571,6 +582,10 @@ def destination_presample( MAZ_size_terms, TAZ_size_terms = aggregate_size_terms( destination_size_terms, network_los ) + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_destination_size_terms.index), name=DEST_TAZ + ).unique().sort_values() + stable_taz_positions = full_taz_index.get_indexer(TAZ_size_terms.index) orig_maz = model_settings.CHOOSER_ORIG_COL_NAME assert orig_maz in choosers @@ -595,6 +610,8 @@ def destination_presample( chunk_tag=chunk_tag, trace_label=trace_label, zone_layer="taz", + stable_alt_positions=stable_taz_positions, + n_total_alts=len(full_taz_index), ) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total @@ -616,6 +633,7 @@ def run_destination_sample( model_settings: TourLocationComponentSettings, network_los, destination_size_terms, + full_destination_size_terms, estimator, chunk_size, trace_label, @@ -664,6 +682,7 @@ def run_destination_sample( model_settings, network_los, destination_size_terms, + full_destination_size_terms, estimator, trace_label, ) @@ -676,6 +695,7 @@ def run_destination_sample( model_settings, network_los, destination_size_terms, + full_destination_size_terms, estimator, chunk_size, trace_label, @@ -944,6 +964,10 @@ def run_tour_destination( segment_destination_size_terms = size_term_calculator.dest_size_terms_df( segment_name, segment_trace_label ) + full_segment_destination_size_terms = size_term_calculator.destination_size_terms[ + [segment_name] + ].copy() + full_segment_destination_size_terms.columns = ["size_term"] if choosers.shape[0] == 0: logger.info( @@ -961,6 +985,7 @@ def run_tour_destination( model_settings, network_los, segment_destination_size_terms, + full_segment_destination_size_terms, estimator, chunk_size=state.settings.chunk_size, trace_label=tracing.extend_trace_label(segment_trace_label, "sample"), diff --git a/activitysim/abm/test/test_misc/test_location_choice_sampling.py b/activitysim/abm/test/test_misc/test_location_choice_sampling.py new file mode 100644 index 0000000000..f5e5846059 --- /dev/null +++ b/activitysim/abm/test/test_misc/test_location_choice_sampling.py @@ -0,0 +1,140 @@ +import pandas as pd + +from activitysim.abm.models import location_choice +from activitysim.core import workflow + + +class _DummySkimDict: + def wrap(self, orig_key, dest_key): + return type("WrappedSkims", (), {"orig_key": orig_key, "dest_key": dest_key})() + + +class _DummyNetworkLos: + def __init__(self, maz_to_taz): + self._maz_to_taz = maz_to_taz + + def map_maz_to_taz(self, maz_index): + return pd.Index([self._maz_to_taz[maz] for maz in maz_index], name="TAZ") + + def get_skim_dict(self, layer): + assert layer == "taz" + return _DummySkimDict() + + +def test_location_presample_uses_taz_stable_mapping(monkeypatch): + captured = {} + + def fake_load_shadow_price_calculator(_state, _model_settings): + return type( + "ShadowPriceCalculator", + (), + { + "use_shadow_pricing": False, + }, + )() + + def fake_location_sample( + _state, + _segment_name, + _choosers, + alternatives, + _skims, + _estimator, + _model_settings, + alt_dest_col_name, + _chunk_size, + _chunk_tag, + _trace_label, + zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, + ): + captured["alt_dest_col_name"] = alt_dest_col_name + captured["zone_layer"] = zone_layer + captured["active_taz_index"] = alternatives.index.copy() + captured["stable_alt_positions"] = stable_alt_positions.copy() + captured["n_total_alts"] = n_total_alts + return pd.DataFrame( + {"dest_TAZ": [1]}, + index=pd.Index([1001], name="person_id"), + ) + + def fake_choose_maz_for_taz(_state, _taz_sample, _maz_size_terms, _trace_label, _model_settings): + return pd.DataFrame( + {"dest_MAZ": [101]}, + index=pd.Index([1001], name="person_id"), + ) + + monkeypatch.setattr( + location_choice.shadow_pricing, + "load_shadow_price_calculator", + fake_load_shadow_price_calculator, + ) + monkeypatch.setattr(location_choice, "_location_sample", fake_location_sample) + monkeypatch.setattr( + location_choice.tour_destination, + "choose_MAZ_for_TAZ", + fake_choose_maz_for_taz, + ) + + state = workflow.State().default_settings() + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "zone_id", + "SIMULATE_CHOOSER_COLUMNS": [location_choice.HOME_MAZ], + }, + )() + persons_merged = pd.DataFrame( + { + location_choice.HOME_MAZ: [9001], + location_choice.HOME_TAZ: [90], + }, + index=pd.Index([1001], name="person_id"), + ) + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + active_dest_size_terms = pd.DataFrame( + { + "size_term": [1.0, 2.0], + "shadow_price_size_term_adjustment": [1.0, 1.0], + "shadow_price_utility_adjustment": [0.0, 0.0], + }, + index=pd.Index([101, 103], name="zone_id"), + ) + full_dest_size_terms = pd.DataFrame( + { + "size_term": [1.0, 0.0, 2.0], + "shadow_price_size_term_adjustment": [1.0, 1.0, 1.0], + "shadow_price_utility_adjustment": [0.0, 0.0, 0.0], + }, + index=pd.Index([101, 102, 103], name="zone_id"), + ) + + out = location_choice.location_presample( + state, + "segment", + persons_merged, + network_los, + active_dest_size_terms, + estimator=None, + model_settings=model_settings, + chunk_size=0, + chunk_tag="test_chunk", + trace_label="test_trace", + full_dest_size_terms=full_dest_size_terms, + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame({"zone_id": [101]}, index=pd.Index([1001], name="person_id")), + ) + pd.testing.assert_index_equal( + captured["active_taz_index"], + pd.Index([1, 3], name=location_choice.DEST_TAZ), + ) + assert captured["alt_dest_col_name"] == location_choice.DEST_TAZ + assert captured["zone_layer"] == "taz" + assert captured["n_total_alts"] == 3 + assert list(captured["stable_alt_positions"]) == [0, 2] \ No newline at end of file diff --git a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py new file mode 100644 index 0000000000..74efd450af --- /dev/null +++ b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py @@ -0,0 +1,202 @@ +import pandas as pd + +from activitysim.abm.models.util import tour_destination +from activitysim.core import workflow + + +class _DummySkimDict: + def wrap(self, orig_key, dest_key): + return type("WrappedSkims", (), {"orig_key": orig_key, "dest_key": dest_key})() + + +class _DummyNetworkLos: + zone_system = 2 + + def __init__(self, maz_to_taz): + self._maz_to_taz = maz_to_taz + + def map_maz_to_taz(self, maz_index): + return pd.Index([self._maz_to_taz[maz] for maz in maz_index], name="TAZ") + + def get_default_skim_dict(self): + return _DummySkimDict() + + def get_skim_dict(self, layer): + assert layer == "taz" + return _DummySkimDict() + + +def test_destination_presample_uses_taz_stable_mapping(monkeypatch): + captured = {} + + def fake_destination_sample( + _state, + _spec_segment_name, + _choosers, + destination_size_terms, + _skims, + _estimator, + _model_settings, + alt_dest_col_name, + chunk_tag, + trace_label, + zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, + ): + captured["alt_dest_col_name"] = alt_dest_col_name + captured["zone_layer"] = zone_layer + captured["active_taz_index"] = destination_size_terms.index.copy() + captured["stable_alt_positions"] = stable_alt_positions.copy() + captured["n_total_alts"] = n_total_alts + captured["chunk_tag"] = chunk_tag + captured["trace_label"] = trace_label + return pd.DataFrame( + {tour_destination.DEST_TAZ: [1]}, + index=pd.Index([7001], name="tour_id"), + ) + + def fake_choose_maz_for_taz( + _state, _taz_sample, _maz_size_terms, _trace_label, _model_settings + ): + return pd.DataFrame( + {tour_destination.DEST_MAZ: [101]}, + index=pd.Index([7001], name="tour_id"), + ) + + monkeypatch.setattr(tour_destination, "_destination_sample", fake_destination_sample) + monkeypatch.setattr(tour_destination, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) + + state = workflow.State().default_settings() + choosers = pd.DataFrame( + {"origin": [101]}, + index=pd.Index([7001], name="tour_id"), + ) + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "zone_id", + "CHOOSER_ORIG_COL_NAME": "origin", + }, + )() + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + active_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 2.0]}, + index=pd.Index([101, 103], name="zone_id"), + ) + full_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 0.0, 2.0]}, + index=pd.Index([101, 102, 103], name="zone_id"), + ) + + out = tour_destination.destination_presample( + state, + "segment", + choosers, + model_settings, + network_los, + active_destination_size_terms, + full_destination_size_terms, + estimator=None, + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame({"zone_id": [101]}, index=pd.Index([7001], name="tour_id")), + ) + pd.testing.assert_index_equal( + captured["active_taz_index"], + pd.Index([1, 3], name=tour_destination.DEST_TAZ), + ) + assert captured["alt_dest_col_name"] == tour_destination.DEST_TAZ + assert captured["zone_layer"] == "taz" + assert captured["n_total_alts"] == 3 + assert list(captured["stable_alt_positions"]) == [0, 2] + + +def test_destination_sample_uses_maz_stable_mapping(monkeypatch): + captured = {} + + def fake_destination_sample( + _state, + _spec_segment_name, + _choosers, + destination_size_terms, + _skims, + _estimator, + _model_settings, + alt_dest_col_name, + chunk_tag, + trace_label, + zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, + ): + captured["active_maz_index"] = destination_size_terms.index.copy() + captured["stable_alt_positions"] = stable_alt_positions.copy() + captured["n_total_alts"] = n_total_alts + captured["alt_dest_col_name"] = alt_dest_col_name + captured["zone_layer"] = zone_layer + return pd.DataFrame( + {"zone_id": [101], "person_id": [55]}, + index=pd.Index([7001], name="tour_id"), + ) + + monkeypatch.setattr(tour_destination, "_destination_sample", fake_destination_sample) + + state = workflow.State().default_settings() + choosers = pd.DataFrame( + {"origin": [101], "person_id": [55]}, + index=pd.Index([7001], name="tour_id"), + ) + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "zone_id", + "CHOOSER_ORIG_COL_NAME": "origin", + "CHOOSER_ID_COLUMN": "person_id", + }, + )() + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + active_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 2.0]}, + index=pd.Index([101, 103], name="zone_id"), + ) + full_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 0.0, 2.0]}, + index=pd.Index([101, 102, 103], name="zone_id"), + ) + + out = tour_destination.destination_sample( + state, + "segment", + choosers, + model_settings, + network_los, + active_destination_size_terms, + full_destination_size_terms, + estimator=None, + chunk_size=0, + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + {"zone_id": [101], "person_id": [55]}, + index=pd.Index([7001], name="tour_id"), + ), + ) + pd.testing.assert_index_equal( + captured["active_maz_index"], + pd.Index([101, 103], name="zone_id"), + ) + assert list(captured["stable_alt_positions"]) == [0, 2] + assert captured["n_total_alts"] == 3 + assert captured["alt_dest_col_name"] == "zone_id" + assert captured["zone_layer"] is None \ No newline at end of file diff --git a/activitysim/abm/test/test_misc/test_trip_destination_sampling.py b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py new file mode 100644 index 0000000000..d4d9903841 --- /dev/null +++ b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py @@ -0,0 +1,187 @@ +import pandas as pd + +from activitysim.abm.models import trip_destination +from activitysim.core import workflow +from activitysim.core.skim_dictionary import DataFrameMatrix + + +class _DummySkimHotel: + def sample_skims(self, presample): + return {"presample": presample} + + +class _DummyNetworkLos: + zone_system = 2 + + def __init__(self, maz_to_taz): + self._maz_to_taz = maz_to_taz + + def map_maz_to_taz(self, maz_index): + return pd.Index([self._maz_to_taz[maz] for maz in maz_index], name="zone_id") + + +def test_destination_sample_retains_full_maz_universe(monkeypatch): + captured = {} + + def fake_destination_sample( + _state, + _primary_purpose, + _trips, + alternatives, + _model_settings, + _size_term_matrix, + skims, + alt_dest_col_name, + _estimator, + chunk_tag, + trace_label, + zone_layer=None, + ): + captured["alternatives_index"] = alternatives.index.copy() + captured["alt_dest_col_name"] = alt_dest_col_name + captured["chunk_tag"] = chunk_tag + captured["trace_label"] = trace_label + captured["zone_layer"] = zone_layer + captured["presample"] = skims["presample"] + return pd.DataFrame( + {"dest_taz": [101]}, + index=pd.Index([7001], name="trip_id"), + ) + + monkeypatch.setattr(trip_destination, "_destination_sample", fake_destination_sample) + + state = workflow.State().default_settings() + trips = pd.DataFrame(index=pd.Index([7001], name="trip_id")) + model_settings = type("ModelSettings", (), {"ALT_DEST_COL_NAME": "dest_taz"})() + + alternatives = pd.DataFrame( + {"eatout": [1.0, 0.0, 2.0]}, + index=pd.Index([101, 102, 103], name="dest_taz"), + ) + size_term_matrix = DataFrameMatrix(alternatives) + + out = trip_destination.destination_sample( + state, + "eatout", + trips, + alternatives, + model_settings, + size_term_matrix, + _DummySkimHotel(), + estimator=None, + chunk_size=0, + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame({"dest_taz": [101]}, index=pd.Index([7001], name="trip_id")), + ) + pd.testing.assert_index_equal( + captured["alternatives_index"], + pd.Index([101, 102, 103], name="dest_taz"), + ) + assert captured["alt_dest_col_name"] == "dest_taz" + assert captured["chunk_tag"] == "trip_destination.sample" + assert captured["zone_layer"] is None + assert captured["presample"] is False + + +def test_destination_presample_retains_full_taz_universe(monkeypatch): + captured = {} + + def fake_destination_sample( + _state, + _primary_purpose, + _trips, + alternatives, + _model_settings, + size_term_matrix, + skims, + alt_dest_col_name, + _estimator, + chunk_tag, + trace_label, + zone_layer=None, + ): + captured["alternatives_index"] = alternatives.index.copy() + captured["size_term_index"] = size_term_matrix.df.index.copy() + captured["alt_dest_col_name"] = alt_dest_col_name + captured["chunk_tag"] = chunk_tag + captured["trace_label"] = trace_label + captured["zone_layer"] = zone_layer + captured["presample"] = skims["presample"] + return pd.DataFrame( + {"dest_taz": [1]}, + index=pd.Index([7001], name="trip_id"), + ) + + def fake_choose_maz_for_taz( + _state, + _taz_sample, + _maz_size_terms, + _trips, + _network_los, + _alt_dest_col_name, + _trace_label, + _model_settings, + ): + return pd.DataFrame( + {"dest_taz": [101]}, + index=pd.Index([7001], name="trip_id"), + ) + + monkeypatch.setattr(trip_destination, "_destination_sample", fake_destination_sample) + monkeypatch.setattr(trip_destination, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) + + state = workflow.State().default_settings() + trips = pd.DataFrame( + {"origin": [101], "tour_leg_dest": [103]}, + index=pd.Index([7001], name="trip_id"), + ) + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "dest_taz", + "TRIP_ORIGIN": "origin", + "PRIMARY_DEST": "tour_leg_dest", + }, + )() + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + alternatives = pd.DataFrame( + {"eatout": [1.0, 0.0, 2.0]}, + index=pd.Index([101, 102, 103], name="dest_taz"), + ) + size_term_matrix = DataFrameMatrix(alternatives) + + out = trip_destination.destination_presample( + state, + "eatout", + trips, + alternatives, + model_settings, + size_term_matrix, + _DummySkimHotel(), + network_los, + estimator=None, + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame({"dest_taz": [101]}, index=pd.Index([7001], name="trip_id")), + ) + pd.testing.assert_index_equal( + captured["alternatives_index"], + pd.Index([1, 2, 3], name="zone_id"), + ) + pd.testing.assert_index_equal( + captured["size_term_index"], + pd.Index([1, 2, 3], name="zone_id"), + ) + assert captured["alt_dest_col_name"] == "dest_taz" + assert captured["chunk_tag"] == "trip_destination.presample" + assert captured["zone_layer"] == "taz" + assert captured["presample"] is True \ No newline at end of file diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 51414930e4..20cc5c60b3 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -38,6 +38,8 @@ def make_sample_choices_utility_based( allow_zero_probs, trace_label, chunk_sizer, + stable_alt_positions=None, + n_total_alts=None, ): assert isinstance(utilities, pd.DataFrame) assert utilities.shape == (len(choosers), alternative_count) @@ -59,7 +61,10 @@ def make_sample_choices_utility_based( choosers = choosers[~zero_probs] chosen_destinations = state.get_rn_generator().gumbel_max_positions_for_df( - utilities, sample_size + utilities, + sample_size, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ).reshape(-1) chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) @@ -208,6 +213,8 @@ def _interaction_sample( zone_layer=None, chunk_sizer=None, compute_settings: ComputeSettings | None = None, + stable_alt_positions=None, + n_total_alts=None, ): """ Run a MNL simulation in the situation in which alternatives must @@ -576,6 +583,8 @@ def _interaction_sample( allow_zero_probs=allow_zero_probs, trace_label=trace_label, chunk_sizer=chunk_sizer, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) del utilities chunk_sizer.log_df(trace_label, "utilities", None) @@ -716,6 +725,8 @@ def interaction_sample( zone_layer: str | None = None, explicit_chunk_size: float = 0, compute_settings: ComputeSettings | None = None, + stable_alt_positions=None, + n_total_alts=None, ): """ Run a simulation in the situation in which alternatives must @@ -818,6 +829,8 @@ def interaction_sample( zone_layer=zone_layer, chunk_sizer=chunk_sizer, compute_settings=compute_settings, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) if choices.shape[0] > 0: diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 67c07c4056..398c9dac99 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -291,7 +291,14 @@ def gumbel_for_df(self, df, step_name, n=1): self.row_states.loc[df.index, "offset"] += n return rands - def gumbel_max_positions_for_df(self, utilities, step_name, sample_size): + def gumbel_max_positions_for_df( + self, + utilities, + step_name, + sample_size, + stable_alt_positions=None, + n_total_alts=None, + ): """ Return the winning alternative position for each chooser/sample pair without materializing the full chooser-by-alternative-by-sample Gumbel array. @@ -302,6 +309,11 @@ def gumbel_max_positions_for_df(self, utilities, step_name, sample_size): DataFrame with one row per chooser and one column per alternative. sample_size : int Number of repeated sampled choices to make per chooser. + stable_alt_positions : 1-D ndarray, optional + Mapping from active utility columns to positions in a larger stable + alternative universe. + n_total_alts : int, optional + Number of alternatives in the larger stable universe. Returns ------- @@ -317,6 +329,24 @@ def gumbel_max_positions_for_df(self, utilities, step_name, sample_size): n_rows, n_alts = utility_values.shape positions = np.empty((n_rows, sample_size), dtype=np.int32) + if stable_alt_positions is not None or n_total_alts is not None: + if stable_alt_positions is None or n_total_alts is None: + raise ValueError( + "stable_alt_positions and n_total_alts must both be provided or omitted together" + ) + stable_alt_positions = np.asarray(stable_alt_positions) + if stable_alt_positions.shape != (n_alts,): + raise ValueError( + "stable_alt_positions must be a 1-D array aligned to utilities columns" + ) + if stable_alt_positions.min() < 0 or stable_alt_positions.max() >= n_total_alts: + raise ValueError( + "stable_alt_positions values must be within [0, n_total_alts)" + ) + n_gumbels = n_total_alts + else: + n_gumbels = n_alts + generators = self._generators_for_df(utilities) # for each chooser, generate the error terms for all samples at once. reshaping this @@ -324,15 +354,17 @@ def gumbel_max_positions_for_df(self, utilities, step_name, sample_size): # first sample, the next n_alts values are the gumbels for the second sample, etc. for row_num, prng in enumerate(generators): utility_row = utility_values[row_num] - row_gumbels = -np.log(-np.log(prng.rand(n_alts * sample_size))).reshape( - (sample_size, n_alts) + row_gumbels = -np.log(-np.log(prng.rand(n_gumbels * sample_size))).reshape( + (sample_size, n_gumbels) ) + if stable_alt_positions is not None: + row_gumbels = row_gumbels[:, stable_alt_positions] positions[row_num, :] = np.argmax( row_gumbels + utility_row[np.newaxis, :], axis=1, ) - self.row_states.loc[utilities.index, "offset"] += n_alts * sample_size + self.row_states.loc[utilities.index, "offset"] += n_gumbels * sample_size return positions def gumbel_choice_positions_for_df( @@ -840,7 +872,13 @@ def gumbel_for_df(self, df, n=1): rands = channel.gumbel_for_df(df, self.step_name, n) return rands - def gumbel_max_positions_for_df(self, utilities, sample_size): + def gumbel_max_positions_for_df( + self, + utilities, + sample_size, + stable_alt_positions=None, + n_total_alts=None, + ): """ Return the winning alternative position for each chooser/sample pair using the appropriate channel for each chooser row. @@ -851,6 +889,11 @@ def gumbel_max_positions_for_df(self, utilities, sample_size): DataFrame with one row per chooser and one column per alternative. sample_size : int Number of repeated sampled choices to make per chooser. + stable_alt_positions : 1-D ndarray, optional + Mapping from active utility columns to positions in a larger stable + alternative universe. + n_total_alts : int, optional + Number of alternatives in the larger stable universe. Returns ------- @@ -858,9 +901,50 @@ def gumbel_max_positions_for_df(self, utilities, sample_size): Array with shape (len(utilities), sample_size) containing the column position of the winning alternative for each chooser/sample pair. """ + if not self.channels: + utility_values = utilities.to_numpy() + n_rows, n_alts = utility_values.shape + positions = np.empty((n_rows, sample_size), dtype=np.int32) + rng = np.random.RandomState(0) + + if stable_alt_positions is not None or n_total_alts is not None: + if stable_alt_positions is None or n_total_alts is None: + raise ValueError( + "stable_alt_positions and n_total_alts must both be provided or omitted together" + ) + stable_alt_positions = np.asarray(stable_alt_positions) + if stable_alt_positions.shape != (n_alts,): + raise ValueError( + "stable_alt_positions must be a 1-D array aligned to utilities columns" + ) + if stable_alt_positions.min() < 0 or stable_alt_positions.max() >= n_total_alts: + raise ValueError( + "stable_alt_positions values must be within [0, n_total_alts)" + ) + n_gumbels = n_total_alts + else: + n_gumbels = n_alts + + for row_num, utility_row in enumerate(utility_values): + row_gumbels = -np.log(-np.log(rng.rand(n_gumbels * sample_size))).reshape( + (sample_size, n_gumbels) + ) + if stable_alt_positions is not None: + row_gumbels = row_gumbels[:, stable_alt_positions] + positions[row_num, :] = np.argmax( + row_gumbels + utility_row[np.newaxis, :], + axis=1, + ) + + return positions + channel = self.get_channel_for_df(utilities) return channel.gumbel_max_positions_for_df( - utilities, self.step_name, sample_size + utilities, + self.step_name, + sample_size, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) def gumbel_choice_positions_for_df(self, utilities, alt_nrs_df=None, n_rands=None): diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 34872941d2..f03984e6d7 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -228,10 +228,21 @@ class _DummyRngUtilityBased: def __init__(self, rands_3d): self.rands_3d = rands_3d - def gumbel_max_positions_for_df(self, utilities, sample_size): + def gumbel_max_positions_for_df( + self, + utilities, + sample_size, + stable_alt_positions=None, + n_total_alts=None, + ): assert sample_size == self.rands_3d.shape[2] + if stable_alt_positions is None: + active_rands = self.rands_3d + else: + assert n_total_alts == self.rands_3d.shape[1] + active_rands = self.rands_3d[:, stable_alt_positions, :] return np.argmax( - self.rands_3d + utilities.to_numpy()[:, :, np.newaxis], + active_rands + utilities.to_numpy()[:, :, np.newaxis], axis=1, ) @@ -357,3 +368,65 @@ def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path() ) pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) + + +def test_make_sample_choices_utility_based_stable_alt_mapping_matches_materialized_path(): + chooser_index = pd.Index([301, 302], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([10, 12, 14], name="alt_id")) + utilities = pd.DataFrame( + [[0.0, 0.3, -0.2], [1.0, 0.2, 0.4]], + index=chooser_index, + ) + sample_size = 2 + stable_alt_positions = np.array([0, 2, 4], dtype=np.int64) + n_total_alts = 5 + dense_rands_3d = np.array( + [ + [[0.1, -0.3], [0.4, 0.2], [0.2, 0.4], [0.3, -0.2], [0.5, -0.1]], + [[-0.2, 0.3], [0.0, 0.5], [0.6, -0.5], [0.2, 0.1], [0.1, 0.7]], + ], + dtype=np.float64, + ) + state = _DummyState(_DummyRngUtilityBased(dense_rands_3d)) + + out = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_stable_alt_mapping", + chunk_sizer=_DummyChunkSizer(), + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + + active_rands = dense_rands_3d[:, stable_alt_positions, :] + chosen_positions = np.argmax( + active_rands + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) + chosen_flat = chosen_positions.reshape(-1) + chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_stable_alt_mapping", + overflow_protection=True, + trace_choosers=choosers, + ).to_numpy() + + expected = pd.DataFrame( + { + "alt_id": alternatives.index.values[chosen_flat], + "prob": probs[chooser_idx, chosen_flat], + "person_id": choosers.index.values[chooser_idx], + } + ) + + pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) diff --git a/activitysim/core/test/test_random.py b/activitysim/core/test/test_random.py index a15e8dfe9d..fdf23213c9 100644 --- a/activitysim/core/test/test_random.py +++ b/activitysim/core/test/test_random.py @@ -167,6 +167,54 @@ def test_gumbel_max_positions_for_df_matches_materialized_path_and_offsets(): npt.assert_allclose(next_random_after_fused, next_random_after_materialized) +def test_gumbel_max_positions_for_df_matches_stable_alt_mapping_and_offsets(): + persons = pd.DataFrame( + {"household_id": [1, 1, 2]}, + index=pd.Index([41, 42, 43], name="person_id"), + ) + utilities = pd.DataFrame( + [[0.5, -0.2, 1.1], [0.1, 0.2, -0.3], [2.0, 1.0, 0.0]], + index=persons.index, + ) + sample_size = 3 + stable_alt_positions = np.array([0, 2, 4], dtype=np.int64) + n_total_alts = 5 + + baseline_rng = random.Random() + baseline_rng.set_base_seed(0) + baseline_rng.begin_step("test_step") + baseline_rng.add_channel("persons", persons) + + materialized = baseline_rng.gumbel_for_df( + utilities, + n=n_total_alts * sample_size, + ).reshape((len(utilities), sample_size, n_total_alts)) + expected_positions = np.argmax( + materialized[:, :, stable_alt_positions] + + utilities.to_numpy()[:, np.newaxis, :], + axis=2, + ) + next_random_after_materialized = baseline_rng.random_for_df(persons) + baseline_rng.end_step("test_step") + + fused_rng = random.Random() + fused_rng.set_base_seed(0) + fused_rng.begin_step("test_step") + fused_rng.add_channel("persons", persons) + + observed_positions = fused_rng.gumbel_max_positions_for_df( + utilities, + sample_size, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + next_random_after_fused = fused_rng.random_for_df(persons) + fused_rng.end_step("test_step") + + npt.assert_array_equal(observed_positions, expected_positions) + npt.assert_allclose(next_random_after_fused, next_random_after_materialized) + + def test_gumbel_choice_positions_for_df_matches_materialized_path_and_offsets(): persons = pd.DataFrame( {"household_id": [1, 1, 2]}, From cb0c5d4ca6c204695bf835f30d2ab9acf15a244f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 16:24:31 +1000 Subject: [PATCH 226/298] some doco updates --- docs/core.rst | 9 ++- docs/dev-guide/explicit-error-terms.md | 104 ++++++++++--------------- docs/users-guide/ways_to_run.rst | 33 +++----- 3 files changed, 58 insertions(+), 88 deletions(-) diff --git a/docs/core.rst b/docs/core.rst index a7a9ba59d6..4ceaa5c34f 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -328,11 +328,12 @@ Explicit Error Terms By default, ActivitySim makes choices by calculating analytical probabilities and then drawing once from the cumulative distribution for each chooser. With Explicit Error Terms (EET), enabled by setting -``use_explicit_error_terms: True`` in ``settings.yaml``, ActivitySim instead draws a standard EV1 (Gumbel) error -term for each chooser-alternative pair, adds it to the observed utility, and chooses the maximum total utility. +``use_explicit_error_terms: True`` in ``settings.yaml``, ActivitySim instead draws the unobserved portion of +utility (error term) for each chooser-alternative pair, adds it to the observed utility, and chooses the alternative +with the highest total utility. -EET changes the final simulation step, not the utility expressions, availability logic, or nesting -structure. In practice, it can reduce Monte Carlo noise in scenario comparisons. +EET changes the final simulation step, not the utility expressions, availability logic, or nesting structure. In +practice, it can reduce Monte Carlo noise in scenario comparisons and between demand and network assignment iterations. For configuration guidance see :ref:`explicit_error_terms_ways_to_run`. For detailed implementation notes see :doc:`/dev-guide/explicit-error-terms`. diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index e338c82613..523df90db8 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -7,7 +7,7 @@ interpretation as the standard method, but changes how the final simulated choic drawn. For details, see [this ATRF paper](https://australasiantransportresearchforum.org.au/frozen-randomness-at-the-individual-utility-level/). -For user-facing guidance on when to use EET, see {ref}`explicit_error_terms_ways_to_run`. +For user-facing guidance, see {ref}`explicit_error_terms_ways_to_run`. ## Enabling EET @@ -41,10 +41,10 @@ With EET enabled, the final draw step changes: For multinomial logit, the error term distribution is i.i.d. Gumbel and draws are generated by inverting the cumulative density function. For nested logit, this method is not available due to correlations between error terms. Instead, ActivitySim makes use of recent advances -regarding the [representation of nested logit models](https://doi.org/10.1017/S026646662000047X) +in the [representation of nested logit models](https://doi.org/10.1017/S026646662000047X) and combines this with [exact numerical sampling methods](https://doi.org/10.1007/978-3-030-52915-4) -to draw error terms of all fundamental alternatives (leafs). +to draw error terms of all fundamental alternatives. ## Practical Effects @@ -53,36 +53,27 @@ to draw error terms of all fundamental alternatives (leafs). For EET to reduce simulation noise, it is important that alternatives of a choice situation keep the same unobserved error term in different scenario runs. This is intimately tied to how random numbers are generated; see {ref}`random_in_detail` for the underlying -random-number stream design and the `activitysim.core.random` API. +random-number stream design and the `activitysim.core.random` API. In essence, keeping the +global random number generator seed constant for comparison runs is essential. This also means +that it is advisable to use the same setting in all runs. Comparing a baseline +run with EET to a scenario run without EET mixes two simulation methods and can make differences +harder to interpret. Aggregate choice patterns should remain statistically the same +as for the default probability-based method. + Because unchanged alternatives can keep the same unobserved draws, changes to choices between scenarios can only happen when the observed utility of an alternative increases. This is not the case for the Monte Carlo simulation method, where the draws are based on probabilities, -which necessarily change for all alternatives if any observed utility changes. +which necessarily change for all alternatives if any observed utility changes. This combined +with sensitivity to small differences in the final CDF draw when comparing nearby scenarios +means that EET is a good candidate to remove noise from scenario comparisons. -This also means that it is advisable to use the same setting in all runs. Comparing a baseline -run with EET to a scenario run without EET mixes two simulation methods and can make differences -harder to interpret. Aggregate choice patterns should remain statistically the same -as for the default probability-based method. The project test suite includes parity tests for -MNL, NL, and interaction-based simulations. - -### Numerical and Debugging Behavior -EET changes the final simulation step, not the utility calculation itself. Utility -expressions, availability logic, nesting structure, and utility validation still matter in -the same way as in the default method. +#### EET as a variance reduction method +TODO: expand on this here. -In practice, EET can make some comparisons easier to interpret because the selected -alternative is the one with the highest total utility after adding the explicit error term, -rather than the one reached by a cumulative-probability threshold. That can reduce -sensitivity to small differences in the final CDF draw when comparing nearby scenarios. -It does not eliminate the need to inspect invalid or unavailable alternatives, and it does -not guarantee identical results across different RNG seeds or different model -configurations. +Common random numbers. Stronger correlations for exptectation values of differences -> less +variance in the estimator. So we need less model runs to be representative. -For shadow-priced location choice, ActivitySim resets RNG offsets between iterations when -EET is enabled so each shadow-pricing iteration uses the same sequence of random numbers. -That keeps the comparison across iterations focused on the shadow price updates instead of -changing random draws between iterations. ### Runtime @@ -92,21 +83,12 @@ probabilities are computed. EET, however, does not need to compute probabilities Exact runtimes depend on the number of alternatives, nesting structure, interaction size, and sampling configuration. With default settings, current full-scale demand model runs with EET -are about 100% higher than the default MC method. While the relative runtime increase -of nested logit models is large, these typically contribute only a very small fraction to the -overall runtime and virtually all of the increase is due to sampling in location choice. To -avoid this penalty, it is possible to use MC for sampling only by adding the following to each -model setting where sampling is used (currently all location and destination choice models as -well as disaggregate accessibilities): +are about +%%% TODO: REVIEW THIS AND UPDATE +100% higher than the default MC method. Most of this is due to sampling in location choice. +%%% END TODO: REVIEW THIS AND UPDATE -```yaml -compute_settings: - use_explicit_error_terms: - sample: false -``` - -With this setting, model runtimes should be roughly equal. The influence of this change on -sampling noise is under investigation. +Memory usage should be comparable albeit slightly higher when running with EET. (explicit_error_terms_zone_encoding)= #### Zone ID encoding and runtime @@ -126,32 +108,30 @@ the array must still cover `max_zone_id + 1` entries, so the draws for the missi generated but never used. For zone systems with large or sparse IDs, this waste can be substantial. An alternative would be to draw only as many error terms as there are sampled alternatives and -retrieve the relevant term for each zone via a lookup. That would avoid unused draws but adds an -index-mapping step for every chooser-sample in the interaction frame, trading one form of overhead -for another. The current design favours the dense approach because the direct-offset indexing is -simpler and because the ``recode_columns`` setting can encode zone IDs as ``zero-based`` in -the input table list; see the +retrieve the relevant term for each zone via a lookup. That would avoid unused draws but it does +not fit naturally with with ActivitySim's current random number generation machinery, trading +one form of overhead for another. The current design favours the dense approach because +benchmarking suggested it was quicker and because ActivitySim has a ``recode_columns`` setting +that optionally encodes zone IDs as ``zero-based`` in the input table list; see the [Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. +We recommend using this option when running with EET. -(explicit_error_terms_memory)= -### Memory usage +#### Sampling method considerations for model components with sampled choice sets +ActivitySim uses sampling of alternatives to reduce runtime of location choice methods. + +TODO: Add details here once sampling method discussion have been resolved. Make clear this is +independent of overall simulation strategy. Maybe rename EET sampling so there is no confusion? + +To use MC sampling with EET simulation, add the following lines to the settings of all models +where location choice sampling is used (currently all location and destination choice models as +well as disaggregate accessibilities): -When running EET with MC for location sampling as described in the Runtime section above, -there should be only a small increase in memory usage for location choice models compared to full -MC simulation. +.. code-block:: yaml -However, when EET is run with its current default location sampling settings, an array of size -(number of choosers, number of alternatives, number of samples) is allocated for all random error -terms. This can quickly become unwieldy for machines with limited memory, and -[chunking](../users-guide/performance/chunking.md) will likely be needed. + compute_settings: + use_explicit_error_terms: + sample: false -When chunking is needed and [explicit chunking](../users-guide/performance/chunking.md#explicit-chunking) -is used, using fractional values for the chunk size rather than absolute numbers of choosers is -often a better fit. This is because the individual steps of location choice models -(location sampling, location logsums, and location choice from the sampled choice set) all have -very different chooser characteristics, but the chunk size currently can only be set at the model -level. Using absolute values for the explicit chunk size would lead to a large number of chunks -for the logsum calculations, which is relatively slow. ## Implementation Details and Adding New Models @@ -185,5 +165,5 @@ exponentiated utilities at 1e-300. To keep behavior consistent, EET treats alter utilities at or below that threshold as unavailable; see `activitysim.core.logit.validate_utils`. ### Scale of the distribution -Error terms are drawn from standard Gumbel distributions, i.e., the scale of the error term is +MNL error terms are drawn from standard Gumbel distributions, i.e., the scale of the error term is fixed to one. diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index ca569ca641..7daf4eb586 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -294,10 +294,14 @@ random component, and for each choice situation a single outcome is generated. With the default Monte Carlo draw method, ActivitySim first calculates analytical probabilities from the systematic utilities of a multinomial or nested logit model and then makes one draw from the cumulative distribution for each chooser. Explicit Error Terms (EET) replaces that final draw with a direct -random-utility simulation by drawing an independent standard EV1 (Gumbel) error term for each +random-utility simulation by drawing the unobserved portion of utility (error term) for each chooser-alternative pair, adding it to the systematic utility, and selecting the alternative with the highest total utility. Both methods simulate the same underlying model, but EET can be less affected by Monte Carlo -noise when comparing scenarios. For more details, see :doc:`/dev-guide/explicit-error-terms`. +noise when comparing scenarios and can make some comparisons easier to interpret. This is because the +selected alternative is the one with the highest total utility after adding the explicit +error term, and if the explicit error term is consistent between a base and scenario run then +only (relative) increases in the observed utility can lead to previously un-chosen alternatives +being chosen. For more details, see :doc:`/dev-guide/explicit-error-terms`. To enable EET for a model run, set the global switch in ``settings.yaml``: @@ -307,26 +311,11 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: Enable or disable this setting consistently across all runs being compared. -Using EET changes the simulation method, not the underlying model. Aggregate behavior should remain statistically -comparable to the default method, but individual simulated choices will not usually match record-by-record. -EET is currently slower than the default probability-based simulation method. Most of the slowdown comes from location -choice models, where the number of alternatives is large and the current importance-sampling workflow requires -many repeated error term draws. Work to reduce that overhead is ongoing. Until then, it is also possible to turn -off EET for the sampling part of these models by adding the following lines to the settings of all models where -location choice sampling is used (currently all location and destination choice models as well as disaggregate -accessibilities): +EET is currently slower than the default probability-based simulation method because it requires +many repeated error term draws. The exact slow-down depends on several factors, but generally location choice +models are most affected and can be up to XXX% slower. -.. code-block:: yaml - - compute_settings: - use_explicit_error_terms: - sample: false - -If you keep EET enabled for the sampling step, also consider memory usage during location sampling. -In that case, explicit chunking with a fractional ``explicit_chunk`` setting is often the most -practical approach; see :ref:`explicit_error_terms_memory` for details. - -For location choice models, encoding zone IDs as a 0-based contiguous index also reduces EET runtime and memory usage; -see :ref:`explicit_error_terms_zone_encoding` for a technical description. For models where the input data does not +For location choice models, encoding zone IDs as a 0-based contiguous index reduces EET runtime; +see :ref:`explicit_error_terms_zone_encoding` for details. For models where the input data does not already use contiguous zone IDs, the ``recode_columns`` option can be used to create them. See the *Zero-based Recoding of Zones* section in :doc:`/dev-guide/using-sharrow` for more details. From bd762c8b95650f418df21ee0091461e71324a81f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 6 May 2026 21:50:15 +1000 Subject: [PATCH 227/298] doc clean up --- docs/dev-guide/explicit-error-terms.md | 11 ++++------- docs/users-guide/ways_to_run.rst | 5 +++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 523df90db8..55fb002f6d 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -83,12 +83,8 @@ probabilities are computed. EET, however, does not need to compute probabilities Exact runtimes depend on the number of alternatives, nesting structure, interaction size, and sampling configuration. With default settings, current full-scale demand model runs with EET -are about -%%% TODO: REVIEW THIS AND UPDATE -100% higher than the default MC method. Most of this is due to sampling in location choice. -%%% END TODO: REVIEW THIS AND UPDATE - -Memory usage should be comparable albeit slightly higher when running with EET. +are about 1.5 to 2 times longer than the default MC method. Virtually all of this is due to +sampling in location choice. Memory usage should be comparable for both methods. (explicit_error_terms_zone_encoding)= #### Zone ID encoding and runtime @@ -116,6 +112,7 @@ that optionally encodes zone IDs as ``zero-based`` in the input table list; see [Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. We recommend using this option when running with EET. + ## Implementation Details and Adding New Models diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 7daf4eb586..57fbd6feb9 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -312,8 +312,9 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: Enable or disable this setting consistently across all runs being compared. EET is currently slower than the default probability-based simulation method because it requires -many repeated error term draws. The exact slow-down depends on several factors, but generally location choice -models are most affected and can be up to XXX% slower. +many repeated error term draws. The exact slow-down depends on several factors, but is generally on the +order of around 1.5-2 times compared to MC. Virtually all of the increase in runtime is caused by +location choice models, and work is under way to remedy this. For location choice models, encoding zone IDs as a 0-based contiguous index reduces EET runtime; see :ref:`explicit_error_terms_zone_encoding` for details. For models where the input data does not From 248defd5a5b831b1c746ab968ec0b1c599d5be56 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 10 May 2026 21:14:59 +1000 Subject: [PATCH 228/298] poisson sample tests and runtime improvements --- activitysim/core/interaction_sample.py | 102 +++--- .../test/test_interaction_sample_poisson.py | 308 ++++++++++++++++++ 2 files changed, 373 insertions(+), 37 deletions(-) create mode 100644 activitysim/core/test/test_interaction_sample_poisson.py diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 82621cefa3..61165c9f60 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -33,18 +33,36 @@ def _poisson_sample_alternatives_inner( - alternative_count: int, probs: pd.DataFrame, - poisson_inclusion_probs: pd.DataFrame, + poisson_inclusion_probs_values: np.ndarray, rng: Random, trace_label: str | None, chunk_sizer: ChunkSizer, -) -> pd.DataFrame: - rands = rng.random_for_df(probs, n=alternative_count) +) -> np.ndarray: + rands = rng.random_for_df(probs, n=probs.shape[1]) chunk_sizer.log_df(trace_label, "rands", rands) - sampled_mask = rands < poisson_inclusion_probs - sampled_results = poisson_inclusion_probs.where(sampled_mask) - return sampled_results + return np.where(rands < poisson_inclusion_probs_values, poisson_inclusion_probs_values, np.nan) + + +def _build_choices_df_from_sampled_alternatives( + sampled_alternatives: pd.DataFrame, + alternatives: pd.DataFrame, + alt_col_name: str, +) -> pd.DataFrame: + sampled_values = sampled_alternatives.to_numpy(copy=False) + chooser_positions, alt_positions = np.nonzero(~np.isnan(sampled_values)) + + chooser_col_name = sampled_alternatives.index.name or "index" + if len(chooser_positions) == 0: + return pd.DataFrame(columns=[chooser_col_name, "prob", alt_col_name]) + + return pd.DataFrame( + { + chooser_col_name: sampled_alternatives.index.to_numpy()[chooser_positions], + "prob": sampled_values[chooser_positions, alt_positions], + alt_col_name: alternatives.index.to_numpy()[alt_positions], + } + ) def make_sample_choices_utility_based( @@ -90,17 +108,13 @@ def make_sample_choices_utility_based( trace_choosers=choosers, ) inclusion_probs, sampled_alternatives = _poisson_sample_alternatives( - alternative_count, chunk_sizer, probs, sample_size, state, trace_label + chunk_sizer, probs, sample_size, state, trace_label ) - # Stack removes the NaNs (the ones that weren't sampled) - # and gives us a multi-index of (person_id, alt_id) - choices_df = ( - sampled_alternatives.rename_axis("alt_idx", axis=1) - .stack() - .reset_index(name="prob") - .assign(**{alt_col_name: lambda df: alternatives.index.values[df["alt_idx"]]}) - .drop(columns=["alt_idx"]) + choices_df = _build_choices_df_from_sampled_alternatives( + sampled_alternatives, + alternatives, + alt_col_name, ) # Here we return the inclusion probabilities i.e. the true probability of being sampled and (ab)use the fact @@ -113,7 +127,6 @@ def make_sample_choices_utility_based( def _poisson_sample_alternatives( - alternative_count, chunk_sizer: ChunkSizer, probs: pd.DataFrame, sample_size, @@ -122,52 +135,67 @@ def _poisson_sample_alternatives( ) -> tuple[pd.DataFrame, pd.DataFrame]: # compute the inclusion probability as the reciprocal of alt never being drawn # -- these are common, so compute once upfront - exclusion_probs = (1 - probs) ** sample_size - inclusion_probs = 1 - exclusion_probs + index = probs.index + columns = probs.columns + probs_values = probs.to_numpy(copy=False) + exclusion_probs_values = np.power(1.0 - probs_values, sample_size) + inclusion_probs_values = 1.0 - exclusion_probs_values n = 0 - probs_subset = probs - inclusion_probs_subset = inclusion_probs - sampled_alternatives = pd.DataFrame( - 0.0, index=inclusion_probs.index, columns=inclusion_probs.columns - ) - while True: + active_row_positions = np.arange(len(probs), dtype=np.int64) + sampled_values = np.full(inclusion_probs_values.shape, np.nan) + + while active_row_positions.size > 0: + probs_subset = probs.iloc[active_row_positions] sampled_results_subset = _poisson_sample_alternatives_inner( - alternative_count, probs_subset, - inclusion_probs_subset, + inclusion_probs_values[active_row_positions], state.get_rn_generator(), trace_label, chunk_sizer, ) - no_alts_sampled_mask = sampled_results_subset.isna().all(axis=1) - alts_with_sampled_alternatives = sampled_results_subset[~no_alts_sampled_mask] - sampled_alternatives.loc[ - alts_with_sampled_alternatives.index, : - ] = alts_with_sampled_alternatives + no_alts_sampled_mask = np.isnan(sampled_results_subset).all(axis=1) + sampled_values[active_row_positions[~no_alts_sampled_mask]] = sampled_results_subset[ + ~no_alts_sampled_mask + ] + if no_alts_sampled_mask.any(): # TODO if this happens in base but the project case is such that something is picked, random numbers won't # be consistent - we're asserting that this is very rare models where the sample size is not too small logger.info(f"Poisson sampling of alternatives failed with {n=}, retrying") # TODO put this behind a debug guard, because it will be slow logger.info( - f"Sampled size was {sample_size}, poisson method mean expected sample size was {inclusion_probs.sum(axis=1).mean():.1f}, actual sampled mean was {(sampled_alternatives > 0).sum(axis=1).mean():.1f} and highest zero selection prob was {(exclusion_probs).product(axis=1).max():.2g}" + f"Sampled size was {sample_size}, poisson method mean expected sample size was {inclusion_probs_values.sum(axis=1).mean():.1f}, actual sampled mean was {np.isfinite(sampled_values).sum(axis=1).mean():.1f} and highest zero selection prob was {exclusion_probs_values.prod(axis=1).max():.2g}" ) - probs_subset = probs[no_alts_sampled_mask] - inclusion_probs_subset = inclusion_probs[no_alts_sampled_mask] + active_row_positions = active_row_positions[no_alts_sampled_mask] else: # All alternatives are fine break n += 1 if n == 10: - choosers_no_alts_sampled = sampled_results_subset[no_alts_sampled_mask] + choosers_no_alts_sampled = pd.DataFrame( + sampled_results_subset[no_alts_sampled_mask], + index=probs_subset.index[no_alts_sampled_mask], + columns=probs.columns, + ) msg = ( f"Poisson choice set sampling failed after 10 attempts for these cases:\n" - f"{choosers_no_alts_sampled}\n{probs_subset}" + f"{choosers_no_alts_sampled}\n{probs_subset.loc[choosers_no_alts_sampled.index]}" ) raise ValueError(msg) + sampled_alternatives = pd.DataFrame( + sampled_values, + index=index, + columns=columns, + ) + inclusion_probs = pd.DataFrame( + inclusion_probs_values, + index=index, + columns=columns, + ) + chunk_sizer.log_df(trace_label, "sampled_alternatives", sampled_alternatives) return inclusion_probs, sampled_alternatives diff --git a/activitysim/core/test/test_interaction_sample_poisson.py b/activitysim/core/test/test_interaction_sample_poisson.py new file mode 100644 index 0000000000..6f6f23d015 --- /dev/null +++ b/activitysim/core/test/test_interaction_sample_poisson.py @@ -0,0 +1,308 @@ +# ActivitySim +# See full license in LICENSE.txt. + +import numpy as np +import pandas as pd +import pytest + +from activitysim.core import interaction_sample, workflow + + +class _DummyChunkSizer: + def log_df(self, *_args, **_kwargs): + return None + + +class _DummyState: + def __init__(self, rng): + self._rng = rng + + def get_rn_generator(self): + return self._rng + + +class _SequentialDummyRng: + def __init__(self, draws): + self._draws = list(draws) + + def random_for_df(self, df, n=1): + draw = self._draws.pop(0) + assert draw.shape == (len(df), n) + return draw + + +@pytest.fixture +def state() -> workflow.State: + state = workflow.State().default_settings() + state.settings.check_for_variability = False + return state + + +def _expected_choices_df(sampled_alternatives, alternatives, alt_col_name): + return ( + sampled_alternatives.rename_axis("alt_idx", axis=1) + .stack() + .reset_index(name="prob") + .assign(**{alt_col_name: lambda df: alternatives.index.values[df["alt_idx"]]}) + .drop(columns=["alt_idx"]) + ) + + +def test_poisson_sample_alternatives_inner_returns_masked_inclusion_probs(): + probs = pd.DataFrame( + [[0.2, 0.4, 0.6], [0.1, 0.3, 0.5]], + index=pd.Index([11, 17], name="person_id"), + columns=[0, 1, 2], + ) + inclusion_probs_values = np.array( + [[0.36, 0.64, 0.84], [0.19, 0.51, 0.75]], + dtype=np.float64, + ) + rng = _SequentialDummyRng( + [ + np.array( + [[0.10, 0.80, 0.20], [0.30, 0.50, 0.90]], + dtype=np.float64, + ) + ] + ) + + sampled = interaction_sample._poisson_sample_alternatives_inner( + probs, + inclusion_probs_values, + rng, + trace_label="test_poisson_sample_alternatives_inner_returns_masked_inclusion_probs", + chunk_sizer=_DummyChunkSizer(), + ) + + expected = np.array( + [[0.36, np.nan, 0.84], [np.nan, 0.51, np.nan]], + dtype=np.float64, + ) + + np.testing.assert_allclose(sampled, expected, equal_nan=True) + + +def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): + probs = pd.DataFrame( + [ + [0.20, 0.60, 0.10, 0.05], + [0.40, 0.10, 0.30, 0.20], + [0.30, 0.20, 0.70, 0.10], + ], + index=pd.Index([11, 17, 42], name="person_id"), + columns=np.arange(4), + ) + sample_size = 2 + expected_inclusion_probs = 1 - (1 - probs) ** sample_size + expected_sampled_alternatives = pd.DataFrame( + [ + [expected_inclusion_probs.iloc[0, 0], np.nan, np.nan, np.nan], + [expected_inclusion_probs.iloc[1, 0], expected_inclusion_probs.iloc[1, 1], np.nan, np.nan], + [np.nan, np.nan, expected_inclusion_probs.iloc[2, 2], np.nan], + ], + index=probs.index, + columns=probs.columns, + ) + state = _DummyState( + _SequentialDummyRng( + [ + np.array( + [ + [0.10, 0.90, 0.50, 0.90], + [0.90, 0.90, 0.90, 0.90], + [0.80, 0.90, 0.20, 0.80], + ], + dtype=np.float64, + ), + np.array([[0.10, 0.05, 0.70, 0.80]], dtype=np.float64), + ] + ) + ) + + inclusion_probs, sampled_alternatives = ( + interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + sample_size=sample_size, + state=state, + trace_label="test_poisson_sample_alternatives_retries_and_returns_expected_frames", + ) + ) + + pd.testing.assert_frame_equal(inclusion_probs, expected_inclusion_probs) + pd.testing.assert_frame_equal(sampled_alternatives, expected_sampled_alternatives) + + +def test_make_sample_choices_utility_based_preserves_sparse_choice_order( + monkeypatch, state +): + chooser_index = pd.Index([11, 17, 42], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([100, 300, 700, 900], name="alt_id")) + utilities = pd.DataFrame( + [[1.0, 0.0, -1.0, 0.5], [0.1, 0.2, 0.3, 0.4], [1.0, 2.0, 3.0, 4.0]], + index=chooser_index, + columns=np.arange(len(alternatives)), + ) + + sampled_alternatives = pd.DataFrame( + [ + [0.25, np.nan, 0.75, np.nan], + [np.nan, 0.50, np.nan, 0.20], + [0.10, np.nan, np.nan, 0.90], + ], + index=chooser_index, + columns=np.arange(len(alternatives)), + ) + inclusion_probs = pd.DataFrame( + [ + [0.25, 0.30, 0.75, 0.10], + [0.12, 0.50, 0.18, 0.20], + [0.10, 0.15, 0.05, 0.90], + ], + index=chooser_index, + columns=np.arange(len(alternatives)), + ) + + def fake_poisson_sample_alternatives( + chunk_sizer, + probs, + sample_size, + state, + trace_label, + ): + assert probs.shape == sampled_alternatives.shape + return inclusion_probs, sampled_alternatives + + monkeypatch.setattr( + interaction_sample, + "_poisson_sample_alternatives", + fake_poisson_sample_alternatives, + ) + + choices_df, returned_inclusion_probs = ( + interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=3, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_preserves_sparse_choice_order", + chunk_sizer=_DummyChunkSizer(), + ) + ) + + expected_choices_df = _expected_choices_df( + sampled_alternatives, alternatives, "alt_id" + ) + + pd.testing.assert_frame_equal(choices_df, expected_choices_df) + pd.testing.assert_frame_equal(returned_inclusion_probs, inclusion_probs) + + +def test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler( + monkeypatch, +): + chooser_index = pd.Index([11, 17, 42], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([100, 300, 700, 900], name="alt_id")) + utilities = pd.DataFrame( + [[1.0, 0.0, -1.0, 0.5], [0.1, 0.2, 0.3, 0.4], [1.0, 2.0, 3.0, 4.0]], + index=chooser_index, + columns=np.arange(len(alternatives)), + ) + probs = pd.DataFrame( + [ + [0.20, 0.60, 0.10, 0.05], + [0.40, 0.10, 0.30, 0.20], + [0.30, 0.20, 0.70, 0.10], + ], + index=chooser_index, + columns=np.arange(len(alternatives)), + ) + sample_size = 2 + inclusion_probs = 1 - (1 - probs) ** sample_size + sampled_alternatives = pd.DataFrame( + [ + [inclusion_probs.iloc[0, 0], np.nan, np.nan, np.nan], + [inclusion_probs.iloc[1, 0], inclusion_probs.iloc[1, 1], np.nan, np.nan], + [np.nan, np.nan, inclusion_probs.iloc[2, 2], np.nan], + ], + index=chooser_index, + columns=probs.columns, + ) + + monkeypatch.setattr( + interaction_sample.logit, + "utils_to_probs", + lambda *args, **kwargs: probs, + ) + + state = _DummyState( + _SequentialDummyRng( + [ + np.array( + [ + [0.10, 0.90, 0.50, 0.90], + [0.90, 0.90, 0.90, 0.90], + [0.80, 0.90, 0.20, 0.80], + ] + ), + np.array([[0.10, 0.05, 0.70, 0.80]]), + ] + ) + ) + + real_choices_df, real_inclusion_probs = ( + interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler", + chunk_sizer=_DummyChunkSizer(), + ) + ) + + def fake_poisson_sample_alternatives( + chunk_sizer, + probs_arg, + sample_size_arg, + state_arg, + trace_label, + ): + assert probs_arg.equals(probs) + assert sample_size_arg == sample_size + return inclusion_probs, sampled_alternatives + + monkeypatch.setattr( + interaction_sample, + "_poisson_sample_alternatives", + fake_poisson_sample_alternatives, + ) + + stubbed_choices_df, stubbed_inclusion_probs = ( + interaction_sample.make_sample_choices_utility_based( + state=_DummyState(_SequentialDummyRng([])), + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler.stub", + chunk_sizer=_DummyChunkSizer(), + ) + ) + + pd.testing.assert_frame_equal(real_choices_df, stubbed_choices_df) + pd.testing.assert_frame_equal(real_inclusion_probs, stubbed_inclusion_probs) From cfdd7da1a515e7c42f1b2ff68c747d54d0e503b3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 11:59:05 +1000 Subject: [PATCH 229/298] tests for random sampling as fallback, some doc strings and removal of superfluous probs --- activitysim/core/interaction_sample.py | 158 +++++++++++++----- .../test/test_interaction_sample_poisson.py | 75 +++++++++ 2 files changed, 190 insertions(+), 43 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 61165c9f60..2729586b68 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -39,11 +39,58 @@ def _poisson_sample_alternatives_inner( trace_label: str | None, chunk_sizer: ChunkSizer, ) -> np.ndarray: + """ + Draw one Bernoulli inclusion decision per chooser-alternative pair. + + Returns a dense 2-D array aligned to `probs` where sampled alternatives + contain their Poisson inclusion probability and unsampled alternatives are + `np.nan`. + """ rands = rng.random_for_df(probs, n=probs.shape[1]) chunk_sizer.log_df(trace_label, "rands", rands) return np.where(rands < poisson_inclusion_probs_values, poisson_inclusion_probs_values, np.nan) +def _poisson_fallback_sample_alternatives( + probs: pd.DataFrame, + sample_size: int, + rng: Random, + trace_label: str | None, + chunk_sizer: ChunkSizer, +) -> np.ndarray: + """ + Fallback sampler used when Poisson retries still leave empty chooser rows. + + This path samples exactly `sample_size` distinct alternatives per chooser + without replacement by ranking one random score per alternative. The + returned array uses the same sparse chooser-by-alternative representation as + the Poisson path: chosen alternatives are `1.0`, unchosen alternatives are + `np.nan`. + """ + if sample_size > probs.shape[1]: + raise ValueError( + "Fallback sampling without replacement requires sample_size <= number of alternatives" + ) + + fallback_rands = rng.random_for_df(probs, n=probs.shape[1]) + chunk_sizer.log_df(trace_label, "fallback_rands", fallback_rands) + + chosen_positions = np.argpartition( + fallback_rands, + kth=sample_size - 1, + axis=1, + )[:, :sample_size] + + fallback_sampled_values = np.full(probs.shape, np.nan) + chooser_positions = np.repeat(np.arange(len(probs)), sample_size) + fallback_sampled_values[ + chooser_positions, + chosen_positions.reshape(-1), + ] = 1.0 + + return fallback_sampled_values + + def _build_choices_df_from_sampled_alternatives( sampled_alternatives: pd.DataFrame, alternatives: pd.DataFrame, @@ -107,7 +154,8 @@ def make_sample_choices_utility_based( overflow_protection=not allow_zero_probs, trace_choosers=choosers, ) - inclusion_probs, sampled_alternatives = _poisson_sample_alternatives( + + sampled_alternatives = _poisson_sample_alternatives( chunk_sizer, probs, sample_size, state, trace_label ) @@ -117,13 +165,7 @@ def make_sample_choices_utility_based( alt_col_name, ) - # Here we return the inclusion probabilities i.e. the true probability of being sampled and (ab)use the fact - # that pick_count=1 by definition and ln(1)=0 and recover the standard sample correction term. - # In non-Poisson sampling, we would return the probs of sampling an alternative once - # and the sampling correction factor np.log(df.pick_count/df.prob) is applied to the simulate utilities. - # TODO is it safe change the meaning of df.prob, given it's referenced in expression csvs? - # (but the alternative is to update all the expression CSV for sampling?) - return choices_df, inclusion_probs + return choices_df def _poisson_sample_alternatives( @@ -132,18 +174,43 @@ def _poisson_sample_alternatives( sample_size, state: workflow.State, trace_label: str, -) -> tuple[pd.DataFrame, pd.DataFrame]: - # compute the inclusion probability as the reciprocal of alt never being drawn - # -- these are common, so compute once upfront - index = probs.index - columns = probs.columns - probs_values = probs.to_numpy(copy=False) - exclusion_probs_values = np.power(1.0 - probs_values, sample_size) - inclusion_probs_values = 1.0 - exclusion_probs_values +) -> pd.DataFrame: + """ + Build a Poisson-sampled choice set for each chooser. + + The primary path performs independent Poisson inclusion draws for every chooser-alternative pair and retries any + chooser row that sampled no alternatives. Both returned DataFrames are aligned to `probs`: + + - `sampled_alternatives` is sparse, with sampled cells holding the value to + carry forward as `prob` and unsampled cells set to `np.nan` + + If a chooser still has no sampled alternatives after 10 retries, we fall + back to sampling exactly `sample_size` distinct alternatives without + replacement and force those chosen probabilities to `1.0` so the sampling + correction factor cancels out. In practice we expect this to be very rare + with reasonable sample sizes and not too small choice sets, but it is a + known issue with Poisson sampling that we want to guard against. Note that + if this fallback is triggered it can lead to inconsistent random numbers + between two scenarios if the number of retries it takes in each scenario + differs, but again we expect this to be very rare and the alternative is + potentially infinite retries or raising an error. + + To make Poisson sampling interchangeable with other sampling methods, we return the inclusion probabilities + i.e. the true probability of being sampled. Pick_count will be 1 by definition (poisson sampling returns a yes/no + for each alternative, so if an alternative is included in the sample it is included once) and the standard + sampling correction factor can be recovered as np.log(df.pick_count/df.prob) = np.log(1/inclusion_prob). + """ + + # In the case of Poisson sampling, the inclusion probability for each chooser-alternative pair is the probability + # that the alternative was included in the sample at least once across the `sample_size` draws, which is the + # reciprocal of alt never being drawn in sample_size draws, so `1 - (1 - p)^sample_size` where `p` is the + # original choice probability. + inclusion_probs_values = 1.0 - np.power(1.0 - probs.to_numpy(copy=False), sample_size) + + sampled_values = np.full(inclusion_probs_values.shape, np.nan) n = 0 active_row_positions = np.arange(len(probs), dtype=np.int64) - sampled_values = np.full(inclusion_probs_values.shape, np.nan) while active_row_positions.size > 0: probs_subset = probs.iloc[active_row_positions] @@ -160,45 +227,50 @@ def _poisson_sample_alternatives( ] if no_alts_sampled_mask.any(): - # TODO if this happens in base but the project case is such that something is picked, random numbers won't - # be consistent - we're asserting that this is very rare models where the sample size is not too small logger.info(f"Poisson sampling of alternatives failed with {n=}, retrying") - # TODO put this behind a debug guard, because it will be slow - logger.info( - f"Sampled size was {sample_size}, poisson method mean expected sample size was {inclusion_probs_values.sum(axis=1).mean():.1f}, actual sampled mean was {np.isfinite(sampled_values).sum(axis=1).mean():.1f} and highest zero selection prob was {exclusion_probs_values.prod(axis=1).max():.2g}" + failed_row_positions = active_row_positions[no_alts_sampled_mask] + logger.debug( + f"Sampled size was {sample_size}, poisson method mean expected sample size was" + + f" {inclusion_probs_values[failed_row_positions].sum(axis=1).mean():.1f}, actual sampled mean was" + + f" {np.isfinite(sampled_values[failed_row_positions]).sum(axis=1).mean():.1f} and highest zero" + + f" selection prob was {(1.0 - inclusion_probs_values[failed_row_positions]).prod(axis=1).max():.2g}" ) - active_row_positions = active_row_positions[no_alts_sampled_mask] + active_row_positions = failed_row_positions - else: # All alternatives are fine + else: # All choosers have at least one alternative in sample set break n += 1 if n == 10: - choosers_no_alts_sampled = pd.DataFrame( - sampled_results_subset[no_alts_sampled_mask], - index=probs_subset.index[no_alts_sampled_mask], - columns=probs.columns, + logger.info( + "Poisson choice set sampling exceeded 10 retries; falling back to random sampling for %s choosers", + len(active_row_positions), + ) + fallback_sampled_values = _poisson_fallback_sample_alternatives( + probs.iloc[active_row_positions], + sample_size, + state.get_rn_generator(), + trace_label, + chunk_sizer, ) - msg = ( - f"Poisson choice set sampling failed after 10 attempts for these cases:\n" - f"{choosers_no_alts_sampled}\n{probs_subset.loc[choosers_no_alts_sampled.index]}" + sampled_values[active_row_positions] = fallback_sampled_values + fallback_mask = ~np.isnan(fallback_sampled_values) + inclusion_probs_values[active_row_positions] = np.where( + fallback_mask, + 1.0, + inclusion_probs_values[active_row_positions], ) - raise ValueError(msg) + break sampled_alternatives = pd.DataFrame( sampled_values, - index=index, - columns=columns, - ) - inclusion_probs = pd.DataFrame( - inclusion_probs_values, - index=index, - columns=columns, + index=probs.index, + columns=probs.columns, ) chunk_sizer.log_df(trace_label, "sampled_alternatives", sampled_alternatives) - return inclusion_probs, sampled_alternatives + return sampled_alternatives def make_sample_choices( @@ -673,7 +745,7 @@ def _interaction_sample( trace_choosers=choosers, ) - choices_df, probs = make_sample_choices_utility_based( + choices_df = make_sample_choices_utility_based( state, choosers, utilities, @@ -758,8 +830,8 @@ def _interaction_sample( choices_df = pd.concat([choices_df, survey_choices], ignore_index=True) choices_df.sort_values(by=[choosers.index.name], inplace=True) - del probs - chunk_sizer.log_df(trace_label, "probs", None) + del probs + chunk_sizer.log_df(trace_label, "probs", None) chunk_sizer.log_df(trace_label, "choices_df", choices_df) diff --git a/activitysim/core/test/test_interaction_sample_poisson.py b/activitysim/core/test/test_interaction_sample_poisson.py index 6f6f23d015..b7f610f641 100644 --- a/activitysim/core/test/test_interaction_sample_poisson.py +++ b/activitysim/core/test/test_interaction_sample_poisson.py @@ -83,6 +83,37 @@ def test_poisson_sample_alternatives_inner_returns_masked_inclusion_probs(): np.testing.assert_allclose(sampled, expected, equal_nan=True) +def test_poisson_fallback_sample_alternatives_selects_distinct_positions_with_prob_one(): + probs = pd.DataFrame( + [[0.20, 0.30, 0.50, 0.00], [0.40, 0.10, 0.30, 0.20]], + index=pd.Index([11, 17], name="person_id"), + columns=np.arange(4), + ) + rng = _SequentialDummyRng( + [ + np.array( + [[0.90, 0.10, 0.40, 0.20], [0.05, 0.70, 0.60, 0.10]], + dtype=np.float64, + ) + ] + ) + + sampled = interaction_sample._poisson_fallback_sample_alternatives( + probs=probs, + sample_size=2, + rng=rng, + trace_label="test_poisson_fallback_sample_alternatives_selects_distinct_positions_with_prob_one", + chunk_sizer=_DummyChunkSizer(), + ) + + expected = np.array( + [[np.nan, 1.0, np.nan, 1.0], [1.0, np.nan, np.nan, 1.0]], + dtype=np.float64, + ) + + np.testing.assert_allclose(sampled, expected, equal_nan=True) + + def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): probs = pd.DataFrame( [ @@ -134,6 +165,50 @@ def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): pd.testing.assert_frame_equal(sampled_alternatives, expected_sampled_alternatives) +def test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries(): + probs = pd.DataFrame( + [[0.20, 0.30, 0.50]], + index=pd.Index([11], name="person_id"), + columns=np.arange(3), + ) + sample_size = 2 + fail_draw = np.array([[0.99, 0.99, 0.99]], dtype=np.float64) + fallback_draw = np.array([[0.10, 0.80, 0.20]], dtype=np.float64) + state = _DummyState( + _SequentialDummyRng([fail_draw] * 10 + [fallback_draw]) + ) + + inclusion_probs, sampled_alternatives = ( + interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + sample_size=sample_size, + state=state, + trace_label="test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries", + ) + ) + + # randomly sampling sample_size alternatives, sample prob is 1 for these + # so that log(pick_count / prob) = log(1 / 1) = 0. + # note the second alternative is not chosen, but because the poisson inclusion probs + # are not overwritten for unused alternatives we expect it to be 1 - (1 - 0.30) ** 2 + expected_inclusion_probs = pd.DataFrame( + [[1.0, 1 - (1 - 0.30) ** 2, 1.0]], + index=probs.index, + columns=probs.columns, + ) + # first and third alternatives are randomly sampled with prob 1, second alternative is not + # sampled so prob is nan + expected_sampled_alternatives = pd.DataFrame( + [[1.0, np.nan, 1.0]], + index=probs.index, + columns=probs.columns, + ) + + pd.testing.assert_frame_equal(inclusion_probs, expected_inclusion_probs) + pd.testing.assert_frame_equal(sampled_alternatives, expected_sampled_alternatives) + + def test_make_sample_choices_utility_based_preserves_sparse_choice_order( monkeypatch, state ): From ee3c42976ac241547b9da6c25b6d9548985ee875 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 12:14:00 +1000 Subject: [PATCH 230/298] avoid unneccessary dataframe construction, better doco --- activitysim/core/interaction_sample.py | 109 +++++------- .../test/test_interaction_sample_poisson.py | 158 ++++++++---------- 2 files changed, 119 insertions(+), 148 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 2729586b68..4cf3e09900 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -91,27 +91,6 @@ def _poisson_fallback_sample_alternatives( return fallback_sampled_values -def _build_choices_df_from_sampled_alternatives( - sampled_alternatives: pd.DataFrame, - alternatives: pd.DataFrame, - alt_col_name: str, -) -> pd.DataFrame: - sampled_values = sampled_alternatives.to_numpy(copy=False) - chooser_positions, alt_positions = np.nonzero(~np.isnan(sampled_values)) - - chooser_col_name = sampled_alternatives.index.name or "index" - if len(chooser_positions) == 0: - return pd.DataFrame(columns=[chooser_col_name, "prob", alt_col_name]) - - return pd.DataFrame( - { - chooser_col_name: sampled_alternatives.index.to_numpy()[chooser_positions], - "prob": sampled_values[chooser_positions, alt_positions], - alt_col_name: alternatives.index.to_numpy()[alt_positions], - } - ) - - def make_sample_choices_utility_based( state: workflow.State, choosers, @@ -155,14 +134,14 @@ def make_sample_choices_utility_based( trace_choosers=choosers, ) - sampled_alternatives = _poisson_sample_alternatives( - chunk_sizer, probs, sample_size, state, trace_label - ) - - choices_df = _build_choices_df_from_sampled_alternatives( - sampled_alternatives, + choices_df = _poisson_sample_alternatives( + chunk_sizer, + probs, alternatives, + sample_size, alt_col_name, + state, + trace_label, ) return choices_df @@ -171,7 +150,9 @@ def make_sample_choices_utility_based( def _poisson_sample_alternatives( chunk_sizer: ChunkSizer, probs: pd.DataFrame, + alternatives: pd.DataFrame, sample_size, + alt_col_name: str, state: workflow.State, trace_label: str, ) -> pd.DataFrame: @@ -179,32 +160,30 @@ def _poisson_sample_alternatives( Build a Poisson-sampled choice set for each chooser. The primary path performs independent Poisson inclusion draws for every chooser-alternative pair and retries any - chooser row that sampled no alternatives. Both returned DataFrames are aligned to `probs`: - - - `sampled_alternatives` is sparse, with sampled cells holding the value to - carry forward as `prob` and unsampled cells set to `np.nan` - - If a chooser still has no sampled alternatives after 10 retries, we fall - back to sampling exactly `sample_size` distinct alternatives without - replacement and force those chosen probabilities to `1.0` so the sampling - correction factor cancels out. In practice we expect this to be very rare - with reasonable sample sizes and not too small choice sets, but it is a - known issue with Poisson sampling that we want to guard against. Note that - if this fallback is triggered it can lead to inconsistent random numbers - between two scenarios if the number of retries it takes in each scenario - differs, but again we expect this to be very rare and the alternative is - potentially infinite retries or raising an error. - - To make Poisson sampling interchangeable with other sampling methods, we return the inclusion probabilities - i.e. the true probability of being sampled. Pick_count will be 1 by definition (poisson sampling returns a yes/no - for each alternative, so if an alternative is included in the sample it is included once) and the standard - sampling correction factor can be recovered as np.log(df.pick_count/df.prob) = np.log(1/inclusion_prob). + chooser row that sampled no alternatives. Internally the sampler maintains a sparse chooser-by-alternative array + where sampled cells hold the probability to carry forward as `prob` and unsampled cells are np.nan. + + If a chooser still has no sampled alternatives after 10 retries, we fall back to sampling exactly sample_size + distinct alternatives without replacement and force those chosen probabilities to `1.0` so the sampling correction + factor cancels out. In practice we expect this to be very rare with reasonable sample sizes and not too small + choice sets, but it is a known issue with Poisson sampling that we want to guard against. Note that if this + fallback is triggered it can lead to inconsistent random numbers between two scenarios if the number of retries it + takes in each scenario differs, but again we expect this to be very rare and the alternative is potentially + infinite retries or raising an error. + + returns: DataFrame with one row per sampled chooser-alternative pair and columns for chooser index, alt_col_name, + and prob (the Poisson inclusion probability for that pair). + + In the case of Poisson sampling, the inclusion probability for each chooser-alternative pair is the probability + that the alternative was included in the sample at least once across the sample_size draws, which is the + reciprocal of it never being drawn in sample_size draws, so 1-(1-p)^sample_size where p is the + original choice probability. To make Poisson sampling interchangeable with other sampling methods, we return the + inclusion probabilities i.e. the true probability of being sampled. Pick_count will be 1 by definition + (poisson sampling returns a yes/no for each alternative, so if an alternative is included in the sample it is + included once) and the standard sampling correction factor can be recovered as np.log(df.pick_count/df.prob) + = np.log(1/inclusion_prob). """ - # In the case of Poisson sampling, the inclusion probability for each chooser-alternative pair is the probability - # that the alternative was included in the sample at least once across the `sample_size` draws, which is the - # reciprocal of alt never being drawn in sample_size draws, so `1 - (1 - p)^sample_size` where `p` is the - # original choice probability. inclusion_probs_values = 1.0 - np.power(1.0 - probs.to_numpy(copy=False), sample_size) sampled_values = np.full(inclusion_probs_values.shape, np.nan) @@ -254,23 +233,25 @@ def _poisson_sample_alternatives( chunk_sizer, ) sampled_values[active_row_positions] = fallback_sampled_values - fallback_mask = ~np.isnan(fallback_sampled_values) - inclusion_probs_values[active_row_positions] = np.where( - fallback_mask, - 1.0, - inclusion_probs_values[active_row_positions], - ) break - sampled_alternatives = pd.DataFrame( - sampled_values, - index=probs.index, - columns=probs.columns, - ) + chooser_positions, alt_positions = np.nonzero(~np.isnan(sampled_values)) + chooser_col_name = probs.index.name or "index" - chunk_sizer.log_df(trace_label, "sampled_alternatives", sampled_alternatives) + if len(chooser_positions) == 0: + choices_df = pd.DataFrame(columns=[chooser_col_name, "prob", alt_col_name]) + else: + choices_df = pd.DataFrame( + { + chooser_col_name: probs.index.to_numpy()[chooser_positions], + "prob": sampled_values[chooser_positions, alt_positions], + alt_col_name: alternatives.index.to_numpy()[alt_positions], + } + ) - return sampled_alternatives + chunk_sizer.log_df(trace_label, "choices_df", choices_df) + + return choices_df def make_sample_choices( diff --git a/activitysim/core/test/test_interaction_sample_poisson.py b/activitysim/core/test/test_interaction_sample_poisson.py index b7f610f641..641d6a85b1 100644 --- a/activitysim/core/test/test_interaction_sample_poisson.py +++ b/activitysim/core/test/test_interaction_sample_poisson.py @@ -125,6 +125,7 @@ def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): columns=np.arange(4), ) sample_size = 2 + alternatives = pd.DataFrame(index=pd.Index([100, 300, 700, 900], name="alt_id")) expected_inclusion_probs = 1 - (1 - probs) ** sample_size expected_sampled_alternatives = pd.DataFrame( [ @@ -151,18 +152,23 @@ def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): ) ) - inclusion_probs, sampled_alternatives = ( - interaction_sample._poisson_sample_alternatives( - chunk_sizer=_DummyChunkSizer(), - probs=probs, - sample_size=sample_size, - state=state, - trace_label="test_poisson_sample_alternatives_retries_and_returns_expected_frames", - ) + choices_df = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + alternatives=alternatives, + sample_size=sample_size, + alt_col_name="alt_id", + state=state, + trace_label="test_poisson_sample_alternatives_retries_and_returns_expected_frames", ) - pd.testing.assert_frame_equal(inclusion_probs, expected_inclusion_probs) - pd.testing.assert_frame_equal(sampled_alternatives, expected_sampled_alternatives) + expected_choices_df = _expected_choices_df( + expected_sampled_alternatives, + alternatives, + "alt_id", + ) + + pd.testing.assert_frame_equal(choices_df, expected_choices_df) def test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries(): @@ -172,41 +178,35 @@ def test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_ret columns=np.arange(3), ) sample_size = 2 + alternatives = pd.DataFrame(index=pd.Index([100, 300, 700], name="alt_id")) fail_draw = np.array([[0.99, 0.99, 0.99]], dtype=np.float64) fallback_draw = np.array([[0.10, 0.80, 0.20]], dtype=np.float64) state = _DummyState( _SequentialDummyRng([fail_draw] * 10 + [fallback_draw]) ) - inclusion_probs, sampled_alternatives = ( - interaction_sample._poisson_sample_alternatives( - chunk_sizer=_DummyChunkSizer(), - probs=probs, - sample_size=sample_size, - state=state, - trace_label="test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries", - ) + choices_df = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + alternatives=alternatives, + sample_size=sample_size, + alt_col_name="alt_id", + state=state, + trace_label="test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries", ) - # randomly sampling sample_size alternatives, sample prob is 1 for these - # so that log(pick_count / prob) = log(1 / 1) = 0. - # note the second alternative is not chosen, but because the poisson inclusion probs - # are not overwritten for unused alternatives we expect it to be 1 - (1 - 0.30) ** 2 - expected_inclusion_probs = pd.DataFrame( - [[1.0, 1 - (1 - 0.30) ** 2, 1.0]], - index=probs.index, - columns=probs.columns, - ) - # first and third alternatives are randomly sampled with prob 1, second alternative is not - # sampled so prob is nan expected_sampled_alternatives = pd.DataFrame( [[1.0, np.nan, 1.0]], index=probs.index, columns=probs.columns, ) + expected_choices_df = _expected_choices_df( + expected_sampled_alternatives, + alternatives, + "alt_id", + ) - pd.testing.assert_frame_equal(inclusion_probs, expected_inclusion_probs) - pd.testing.assert_frame_equal(sampled_alternatives, expected_sampled_alternatives) + pd.testing.assert_frame_equal(choices_df, expected_choices_df) def test_make_sample_choices_utility_based_preserves_sparse_choice_order( @@ -230,25 +230,19 @@ def test_make_sample_choices_utility_based_preserves_sparse_choice_order( index=chooser_index, columns=np.arange(len(alternatives)), ) - inclusion_probs = pd.DataFrame( - [ - [0.25, 0.30, 0.75, 0.10], - [0.12, 0.50, 0.18, 0.20], - [0.10, 0.15, 0.05, 0.90], - ], - index=chooser_index, - columns=np.arange(len(alternatives)), - ) - def fake_poisson_sample_alternatives( chunk_sizer, probs, + alternatives_arg, sample_size, + alt_col_name, state, trace_label, ): assert probs.shape == sampled_alternatives.shape - return inclusion_probs, sampled_alternatives + assert alternatives_arg.equals(alternatives) + assert alt_col_name == "alt_id" + return _expected_choices_df(sampled_alternatives, alternatives, alt_col_name) monkeypatch.setattr( interaction_sample, @@ -256,19 +250,17 @@ def fake_poisson_sample_alternatives( fake_poisson_sample_alternatives, ) - choices_df, returned_inclusion_probs = ( - interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=3, - alternative_count=len(alternatives), - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_preserves_sparse_choice_order", - chunk_sizer=_DummyChunkSizer(), - ) + choices_df = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=3, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_preserves_sparse_choice_order", + chunk_sizer=_DummyChunkSizer(), ) expected_choices_df = _expected_choices_df( @@ -276,7 +268,6 @@ def fake_poisson_sample_alternatives( ) pd.testing.assert_frame_equal(choices_df, expected_choices_df) - pd.testing.assert_frame_equal(returned_inclusion_probs, inclusion_probs) def test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler( @@ -332,31 +323,33 @@ def test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler( ) ) - real_choices_df, real_inclusion_probs = ( - interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=sample_size, - alternative_count=len(alternatives), - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler", - chunk_sizer=_DummyChunkSizer(), - ) + real_choices_df = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler", + chunk_sizer=_DummyChunkSizer(), ) def fake_poisson_sample_alternatives( chunk_sizer, probs_arg, + alternatives_arg, sample_size_arg, + alt_col_name, state_arg, trace_label, ): assert probs_arg.equals(probs) + assert alternatives_arg.equals(alternatives) assert sample_size_arg == sample_size - return inclusion_probs, sampled_alternatives + assert alt_col_name == "alt_id" + return _expected_choices_df(sampled_alternatives, alternatives, alt_col_name) monkeypatch.setattr( interaction_sample, @@ -364,20 +357,17 @@ def fake_poisson_sample_alternatives( fake_poisson_sample_alternatives, ) - stubbed_choices_df, stubbed_inclusion_probs = ( - interaction_sample.make_sample_choices_utility_based( - state=_DummyState(_SequentialDummyRng([])), - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=sample_size, - alternative_count=len(alternatives), - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler.stub", - chunk_sizer=_DummyChunkSizer(), - ) + stubbed_choices_df = interaction_sample.make_sample_choices_utility_based( + state=_DummyState(_SequentialDummyRng([])), + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler.stub", + chunk_sizer=_DummyChunkSizer(), ) pd.testing.assert_frame_equal(real_choices_df, stubbed_choices_df) - pd.testing.assert_frame_equal(real_inclusion_probs, stubbed_inclusion_probs) From 68faa941b2ae0027c715dbdd819779399733d9ee Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 12:19:10 +1000 Subject: [PATCH 231/298] update interaction sample tests for poisson --- .../core/test/test_interaction_sample.py | 141 +++++++----------- 1 file changed, 58 insertions(+), 83 deletions(-) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index f03984e6d7..0069b2715e 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -224,37 +224,23 @@ def get_rn_generator(self): return self._rng -class _DummyRngUtilityBased: - def __init__(self, rands_3d): - self.rands_3d = rands_3d +class _SequentialDummyRng: + def __init__(self, draws): + self._draws = list(draws) - def gumbel_max_positions_for_df( - self, - utilities, - sample_size, - stable_alt_positions=None, - n_total_alts=None, - ): - assert sample_size == self.rands_3d.shape[2] - if stable_alt_positions is None: - active_rands = self.rands_3d - else: - assert n_total_alts == self.rands_3d.shape[1] - active_rands = self.rands_3d[:, stable_alt_positions, :] - return np.argmax( - active_rands + utilities.to_numpy()[:, :, np.newaxis], - axis=1, - ) + def random_for_df(self, df, n=1): + draw = self._draws.pop(0) + assert draw.shape == (len(df), n) + return draw def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_heterogeneity(): # Edge case: utilities are close across alternatives but vary strongly by chooser. - # This is where wrong chooser/sample alignment can hide in aggregate checks. + # This checks that the flattened Poisson result keeps chooser/prob alignment. chooser_index = pd.Index([101, 102, 103, 104, 105, 106], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([0, 1, 2, 3], name="alt_id")) - n_choosers = len(choosers) n_alts = len(alternatives) sample_size = 3 @@ -268,9 +254,18 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het index=chooser_index, ) - # No random noise: chosen alternative is deterministic argmax of utilities. - rands_3d = np.zeros((n_choosers, n_alts, sample_size), dtype=np.float64) - state = _DummyState(_DummyRngUtilityBased(rands_3d)) + poisson_draws = np.array( + [ + [0.01, 0.90, 0.90, 0.90], + [0.80, 0.05, 0.90, 0.90], + [0.90, 0.10, 0.40, 0.90], + [0.90, 0.90, 0.10, 0.20], + [0.90, 0.90, 0.02, 0.10], + [0.90, 0.90, 0.90, 0.001], + ], + dtype=np.float64, + ) + state = _DummyState(_SequentialDummyRng([poisson_draws])) out = interaction_sample.make_sample_choices_utility_based( state=state, @@ -285,16 +280,6 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het chunk_sizer=_DummyChunkSizer(), ) - # Reconstruct expected indexing behavior. - chosen_2d = np.argmax( - rands_3d + utilities.to_numpy()[:, :, np.newaxis], - axis=1, - ) - chosen_flat = chosen_2d.reshape(-1) - - chooser_repeat = np.repeat(np.arange(n_choosers), sample_size) - chooser_tile = np.tile(np.arange(n_choosers), sample_size) - probs = interaction_sample.logit.utils_to_probs( state, utilities, @@ -303,12 +288,19 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het overflow_protection=True, trace_choosers=choosers, ).to_numpy() + inclusion_probs = 1 - np.power(1 - probs, sample_size) + sampled_values = np.where(poisson_draws < inclusion_probs, inclusion_probs, np.nan) + chooser_idx, alt_idx = np.nonzero(~np.isnan(sampled_values)) - expected_prob_repeat = probs[chooser_repeat, chosen_flat] - wrong_prob_tile = probs[chooser_tile, chosen_flat] + expected = pd.DataFrame( + { + "person_id": chooser_index.to_numpy()[chooser_idx], + "prob": sampled_values[chooser_idx, alt_idx], + "alt_id": alternatives.index.to_numpy()[alt_idx], + } + ) - assert np.array_equal(out["prob"].to_numpy(), expected_prob_repeat) - assert not np.array_equal(out["prob"].to_numpy(), wrong_prob_tile) + pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path(): @@ -320,16 +312,16 @@ def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path() index=chooser_index, ) sample_size = 2 - n_alts = len(alternatives) - rands_3d = np.array( + poisson_draws = np.array( [ - [[0.1, -0.3], [0.2, 0.4], [0.5, -0.1], [0.0, 0.2]], - [[-0.2, 0.3], [0.6, -0.5], [0.1, 0.7], [0.4, 0.2]], - [[0.0, 0.1], [0.3, -0.4], [0.2, 0.5], [-0.3, 0.2]], + [0.10, 0.20, 0.50, 0.00], + [0.60, 0.50, 0.10, 0.40], + [0.00, 0.30, 0.20, 0.90], ], dtype=np.float64, ) - state = _DummyState(_DummyRngUtilityBased(rands_3d)) + retry_draw = np.array([[0.40, 0.10, 0.90, 0.90]], dtype=np.float64) + state = _DummyState(_SequentialDummyRng([poisson_draws, retry_draw])) out = interaction_sample.make_sample_choices_utility_based( state=state, @@ -337,19 +329,13 @@ def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path() utilities=utilities, alternatives=alternatives, sample_size=sample_size, - alternative_count=n_alts, + alternative_count=len(alternatives), alt_col_name="alt_id", allow_zero_probs=False, trace_label="test_fused_rng_matches_materialized", chunk_sizer=_DummyChunkSizer(), ) - chosen_positions = np.argmax( - rands_3d + utilities.to_numpy()[:, :, np.newaxis], - axis=1, - ) - chosen_flat = chosen_positions.reshape(-1) - chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) probs = interaction_sample.logit.utils_to_probs( state, utilities, @@ -358,19 +344,27 @@ def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path() overflow_protection=True, trace_choosers=choosers, ).to_numpy() + inclusion_probs = 1 - np.power(1 - probs, sample_size) + sampled_values = np.full(inclusion_probs.shape, np.nan) + first_pass = np.where(poisson_draws < inclusion_probs, inclusion_probs, np.nan) + first_pass_empty = np.isnan(first_pass).all(axis=1) + sampled_values[~first_pass_empty] = first_pass[~first_pass_empty] + retry_pass = np.where(retry_draw < inclusion_probs[first_pass_empty], inclusion_probs[first_pass_empty], np.nan) + sampled_values[first_pass_empty] = retry_pass + chooser_idx, alt_idx = np.nonzero(~np.isnan(sampled_values)) expected = pd.DataFrame( { - "alt_id": alternatives.index.values[chosen_flat], - "prob": probs[chooser_idx, chosen_flat], "person_id": choosers.index.values[chooser_idx], + "prob": sampled_values[chooser_idx, alt_idx], + "alt_id": alternatives.index.values[alt_idx], } ) pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) -def test_make_sample_choices_utility_based_stable_alt_mapping_matches_materialized_path(): +def test_make_sample_choices_utility_based_falls_back_after_retries(): chooser_index = pd.Index([301, 302], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([10, 12, 14], name="alt_id")) @@ -379,16 +373,15 @@ def test_make_sample_choices_utility_based_stable_alt_mapping_matches_materializ index=chooser_index, ) sample_size = 2 - stable_alt_positions = np.array([0, 2, 4], dtype=np.int64) - n_total_alts = 5 - dense_rands_3d = np.array( + fail_draw = np.full((2, 3), 0.99, dtype=np.float64) + fallback_draw = np.array( [ - [[0.1, -0.3], [0.4, 0.2], [0.2, 0.4], [0.3, -0.2], [0.5, -0.1]], - [[-0.2, 0.3], [0.0, 0.5], [0.6, -0.5], [0.2, 0.1], [0.1, 0.7]], + [0.40, 0.10, 0.20], + [0.30, 0.20, 0.90], ], dtype=np.float64, ) - state = _DummyState(_DummyRngUtilityBased(dense_rands_3d)) + state = _DummyState(_SequentialDummyRng([fail_draw] * 10 + [fallback_draw])) out = interaction_sample.make_sample_choices_utility_based( state=state, @@ -399,33 +392,15 @@ def test_make_sample_choices_utility_based_stable_alt_mapping_matches_materializ alternative_count=len(alternatives), alt_col_name="alt_id", allow_zero_probs=False, - trace_label="test_stable_alt_mapping", + trace_label="test_falls_back_after_retries", chunk_sizer=_DummyChunkSizer(), - stable_alt_positions=stable_alt_positions, - n_total_alts=n_total_alts, ) - active_rands = dense_rands_3d[:, stable_alt_positions, :] - chosen_positions = np.argmax( - active_rands + utilities.to_numpy()[:, :, np.newaxis], - axis=1, - ) - chosen_flat = chosen_positions.reshape(-1) - chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) - probs = interaction_sample.logit.utils_to_probs( - state, - utilities, - allow_zero_probs=False, - trace_label="test_stable_alt_mapping", - overflow_protection=True, - trace_choosers=choosers, - ).to_numpy() - expected = pd.DataFrame( { - "alt_id": alternatives.index.values[chosen_flat], - "prob": probs[chooser_idx, chosen_flat], - "person_id": choosers.index.values[chooser_idx], + "person_id": [301, 301, 302, 302], + "prob": [1.0, 1.0, 1.0, 1.0], + "alt_id": [12, 14, 10, 12], } ) From 73d83e6d91e8dde969b4518b038cd1a636811c6e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 13:26:55 +1000 Subject: [PATCH 232/298] integration of sampling methods with clear separation from eet as simulation method --- activitysim/core/configuration/base.py | 20 +- activitysim/core/configuration/top.py | 8 + activitysim/core/interaction_sample.py | 170 +++++++++--- .../core/test/test_interaction_sample.py | 241 +++++++++++++++++- docs/core.rst | 3 +- docs/dev-guide/explicit-error-terms.md | 61 +---- docs/dev-guide/index.rst | 1 + docs/dev-guide/sampling-methods.md | 91 +++++++ docs/users-guide/index.rst | 1 + docs/users-guide/sampling-methods.rst | 42 +++ docs/users-guide/ways_to_run.rst | 11 +- 11 files changed, 526 insertions(+), 123 deletions(-) create mode 100644 docs/dev-guide/sampling-methods.md create mode 100644 docs/users-guide/sampling-methods.rst diff --git a/activitysim/core/configuration/base.py b/activitysim/core/configuration/base.py index aad266ff12..d8e59d0827 100644 --- a/activitysim/core/configuration/base.py +++ b/activitysim/core/configuration/base.py @@ -135,9 +135,14 @@ class ComputeSettings(PydanticBase): Sharrow settings for a component. """ - # Make this more general compute settings and use for explicit error term overrides - # Default None work for sub-components defined in getter below (eet_subcomponent) - use_explicit_error_terms: None | bool | dict[str, bool] = None + sample_method: None | Literal["monte_carlo", "eet", "poisson"] = None + """ + Override the alternative sampling method used by `interaction_sample`. + + When unset, `interaction_sample` preserves legacy behavior: it uses + `monte_carlo` when explicit error terms are off and `poisson` when they + are on. + """ sharrow_skip: bool | dict[str, bool] = False """Skip sharrow when evaluating this component. @@ -222,13 +227,6 @@ def should_skip(self, subcomponent: str) -> bool: else: return bool(self.sharrow_skip) - def eet_subcomponent(self, subcomponent: str) -> bool: - """Check for EET overrides for a particular subcomponent.""" - if isinstance(self.use_explicit_error_terms, dict): - return self.use_explicit_error_terms.get(subcomponent, None) - else: - return self.use_explicit_error_terms - @contextmanager def pandas_option_context(self): """Context manager to set pandas options for compute settings.""" @@ -277,7 +275,7 @@ def subcomponent_settings(self, subcomponent: str) -> ComputeSettings: use_numba=self.use_numba, drop_unused_columns=self.drop_unused_columns, protect_columns=self.protect_columns, - use_explicit_error_terms=self.eet_subcomponent(subcomponent), + sample_method=self.sample_method, ) diff --git a/activitysim/core/configuration/top.py b/activitysim/core/configuration/top.py index 6feb16e468..5a8746cc84 100644 --- a/activitysim/core/configuration/top.py +++ b/activitysim/core/configuration/top.py @@ -704,6 +704,7 @@ def _check_store_skims_in_shm(self): "instrument", "sharrow", "use_explicit_error_terms", + "sample_method", ) """ Setting to log on startup. @@ -788,6 +789,13 @@ def _check_store_skims_in_shm(self): .. versionadded:: 1.6 """ + sample_method: None | Literal["monte_carlo", "eet", "poisson"] = None + """ + Sampling method to use in `activitysim.core.interaction_sample`. + + When unset, `monte_carlo` is used when `use_explicit_error_terms` is false and `poisson` is used when it is true. + """ + check_model_settings: bool = True """ run checks to validate that YAML settings files are loadable and spec and coefficent csv can be resolved. diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 4cf3e09900..924d3d785e 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -31,6 +31,27 @@ DUMP = False +InteractionSampleMethod = typing.Literal["monte_carlo", "eet", "poisson"] + + +def _resolve_sample_method( + state: workflow.State, + compute_settings: ComputeSettings | None, + use_eet: bool, +) -> InteractionSampleMethod: + sampling_method = None + if compute_settings is not None: + sampling_method = compute_settings.sample_method + if sampling_method is None: + sampling_method = state.settings.sample_method + if sampling_method is None: + return "poisson" if use_eet else "monte_carlo" + if sampling_method not in typing.get_args(InteractionSampleMethod): + raise ValueError( + f"Unsupported sample_method {sampling_method!r}; expected one of {typing.get_args(InteractionSampleMethod)}" + ) + return sampling_method + def _poisson_sample_alternatives_inner( probs: pd.DataFrame, @@ -91,6 +112,65 @@ def _poisson_fallback_sample_alternatives( return fallback_sampled_values +def _eet_sample_alternatives_with_replacement( + state: workflow.State, + choosers: pd.DataFrame, + utilities: pd.DataFrame, + alternatives: pd.DataFrame, + sample_size: int, + alt_col_name: str, + trace_label: str, + chunk_sizer: ChunkSizer, + stable_alt_positions: np.ndarray | None = None, + n_total_alts: int | None = None, +) -> pd.DataFrame: + """ + Sample alternatives by repeated EET draws with replacement. + + Each chooser receives `sample_size` EV1 draw sets. The winning alternative + from each draw is recorded, allowing duplicates in the same way as the + Monte Carlo sampling path. + """ + chosen_destinations = state.get_rn_generator().gumbel_max_positions_for_df( + utilities, + sample_size, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ).reshape(-1) + chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) + + chooser_idx = np.repeat(np.arange(utilities.shape[0]), sample_size) + chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) + + probs = logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label=trace_label, + overflow_protection=True, + trace_choosers=choosers, + ) + chunk_sizer.log_df(trace_label, "probs", probs) + + choices_df = pd.DataFrame( + { + choosers.index.name: choosers.index.values[chooser_idx], + "prob": probs.to_numpy()[chooser_idx, chosen_destinations], + alt_col_name: alternatives.index.values[chosen_destinations], + } + ) + chunk_sizer.log_df(trace_label, "choices_df", choices_df) + + del chooser_idx + chunk_sizer.log_df(trace_label, "chooser_idx", None) + del chosen_destinations + chunk_sizer.log_df(trace_label, "chosen_destinations", None) + del probs + chunk_sizer.log_df(trace_label, "probs", None) + + return choices_df + + def make_sample_choices_utility_based( state: workflow.State, choosers, @@ -102,6 +182,9 @@ def make_sample_choices_utility_based( allow_zero_probs, trace_label: str, chunk_sizer: ChunkSizer, + sampling_method: InteractionSampleMethod = "poisson", + stable_alt_positions: np.ndarray | None = None, + n_total_alts: int | None = None, ): assert isinstance(utilities, pd.DataFrame) assert utilities.shape == (len(choosers), alternative_count) @@ -114,9 +197,7 @@ def make_sample_choices_utility_based( utilities.sum(axis=1) <= utilities.shape[1] * logit.UTIL_UNAVAILABLE ) if zero_probs.all(): - return pd.DataFrame( - columns=[alt_col_name, "rand", "prob", choosers.index.name] - ), pd.DataFrame(columns=["prob"]) + return pd.DataFrame(columns=[choosers.index.name, "prob", alt_col_name]) if zero_probs.any(): # remove from sample utilities = utilities[~zero_probs] @@ -125,24 +206,40 @@ def make_sample_choices_utility_based( utils_array = utilities.to_numpy() chunk_sizer.log_df(trace_label, "utils_array", utils_array) - probs = logit.utils_to_probs( - state, - utilities, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - overflow_protection=not allow_zero_probs, - trace_choosers=choosers, - ) - - choices_df = _poisson_sample_alternatives( - chunk_sizer, - probs, - alternatives, - sample_size, - alt_col_name, - state, - trace_label, - ) + if sampling_method == "eet": + choices_df = _eet_sample_alternatives_with_replacement( + state, + choosers, + utilities, + alternatives, + sample_size, + alt_col_name, + trace_label, + chunk_sizer, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + elif sampling_method == "poisson": + probs = logit.utils_to_probs( + state, + utilities, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + overflow_protection=not allow_zero_probs, + trace_choosers=choosers, + ) + + choices_df = _poisson_sample_alternatives( + chunk_sizer, + probs, + alternatives, + sample_size, + alt_col_name, + state, + trace_label, + ) + else: + raise ValueError(f"Unsupported utility-based sampling method {sampling_method!r}") return choices_df @@ -283,6 +380,8 @@ def make_sample_choices( Returns ------- + stable_alt_positions=None, + n_total_alts=None, """ @@ -364,6 +463,8 @@ def _interaction_sample( zone_layer=None, chunk_sizer: ChunkSizer | None = None, compute_settings: ComputeSettings | None = None, + stable_alt_positions=None, + n_total_alts=None, ): """ Run a MNL simulation in the situation in which alternatives must @@ -675,13 +776,8 @@ def _interaction_sample( state.tracing.dump_df(DUMP, utilities, trace_label, "utilities") - if compute_settings.use_explicit_error_terms is not None: - use_eet = compute_settings.use_explicit_error_terms - logger.info( - f"Interaction sample model-specific EET overrides for {trace_label}: eet = {use_eet}" - ) - else: - use_eet = state.settings.use_explicit_error_terms + use_eet = state.settings.use_explicit_error_terms + sampling_method = _resolve_sample_method(state, compute_settings, use_eet) if sample_size == 0: # Return full alternative set rather than sample @@ -711,11 +807,11 @@ def _interaction_sample( return choices_df - if use_eet: + if sampling_method != "monte_carlo": if estimation.manager.enabled: raise ValueError( - "cannot use explicit error terms with estimation mode at this time" + f"sample_method={sampling_method!r} is not supported with estimation mode" ) utilities = logit.validate_utils( @@ -737,6 +833,9 @@ def _interaction_sample( allow_zero_probs=allow_zero_probs, trace_label=trace_label, chunk_sizer=chunk_sizer, + sampling_method=sampling_method, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) del utilities chunk_sizer.log_df(trace_label, "utilities", None) @@ -846,7 +945,7 @@ def _interaction_sample( column_labels=["sample_alt", "alternative"], ) - if not state.settings.use_explicit_error_terms: + if "rand" in choices_df.columns and not use_eet: # don't need this after tracing del choices_df["rand"] @@ -877,6 +976,8 @@ def interaction_sample( zone_layer: str | None = None, explicit_chunk_size: float = 0, compute_settings: ComputeSettings | None = None, + stable_alt_positions=None, + n_total_alts=None, ): """ Run a simulation in the situation in which alternatives must @@ -951,8 +1052,11 @@ def interaction_sample( if not choosers.index.is_monotonic_increasing: assert choosers.index.is_monotonic_increasing + use_eet = state.settings.use_explicit_error_terms + sampling_method = _resolve_sample_method(state, compute_settings, use_eet) + # FIXME - legacy logic - not sure this is needed or even correct? - if not state.settings.use_explicit_error_terms: + if sampling_method != "poisson": sample_size = min(sample_size, len(alternatives.index)) # with poisson sampling, definitely don't want to reduce sample size - it's not a sample size but a number # of theoretical draws. Another options would be to disable sampling if # alts < sample size to ensure @@ -985,6 +1089,8 @@ def interaction_sample( zone_layer=zone_layer, chunk_sizer=chunk_sizer, compute_settings=compute_settings, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) if choices.shape[0] > 0: diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 0069b2715e..ec8f906fac 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -6,6 +6,7 @@ import pytest from activitysim.core import interaction_sample, workflow +from activitysim.core.configuration.base import ComputeSettings @pytest.fixture @@ -15,6 +16,11 @@ def state() -> workflow.State: return state +def _weighted_shares(df: pd.DataFrame) -> pd.Series: + counts = df.groupby("alt_id")["pick_count"].sum() + return (counts / counts.sum()).sort_index() + + def test_interaction_sample_parity(state): # Run interaction_sample with and without explicit error terms and check that results are similar. @@ -138,9 +144,12 @@ def test_interaction_sample_eet_unavailable_alternatives(state): assert not choices_eet["alt_id"].isin([5, 6, 7, 8, 9]).any() -def test_interaction_sample_parity_peaked_utilities(state): - # Stress parity under a highly peaked utility profile: - # one dominant alternative, one secondary, and many tiny utilities. +def test_interaction_sample_parity_peaked_utilities_eet_with_replacement(state): + # Under highly peaked utilities, the EET-with-replacement sampler should still + # approximate repeated-draw MNL shares because both sample with replacement. + # This test also documents that per-model compute settings can override the + # global default: global EET implies Poisson by default, but this model opts + # into EET-with-replacement explicitly. num_choosers = 20_000 num_alts = 100 sample_size = 5 @@ -175,7 +184,7 @@ def test_interaction_sample_parity_peaked_utilities(state): alt_col_name="alt_id", ) - # Run EET path with the same seed. + # Run EET-with-replacement path with the same seed. state.init_state() state.settings.use_explicit_error_terms = True state.rng().set_base_seed(42) @@ -188,14 +197,11 @@ def test_interaction_sample_parity_peaked_utilities(state): spec, sample_size=sample_size, alt_col_name="alt_id", + compute_settings=ComputeSettings(sample_method="eet"), ) - def weighted_shares(df: pd.DataFrame) -> pd.Series: - counts = df.groupby("alt_id")["pick_count"].sum() - return (counts / counts.sum()).sort_index() - - mnl_shares = weighted_shares(choices_mnl) - explicit_shares = weighted_shares(choices_explicit) + mnl_shares = _weighted_shares(choices_mnl) + explicit_shares = _weighted_shares(choices_explicit) all_alts = set(mnl_shares.index) | set(explicit_shares.index) for alt in all_alts: @@ -211,6 +217,70 @@ def weighted_shares(df: pd.DataFrame) -> pd.Series: assert explicit_shares.get(0, 0.0) > 0.99 +def test_interaction_sample_peaked_utilities_poisson_matches_inclusion_shares(state): + # Poisson sampling does not reproduce repeated-draw MNL shares in peaked cases. + # It samples each alternative independently with inclusion probability + # 1 - (1 - p)^sample_size, so the dominant alternative's share is flattened + # relative to MNL once the included set is normalized. This is also the + # default interaction_sample behavior when global EET is enabled. + num_choosers = 20_000 + num_alts = 100 + sample_size = 5 + + choosers = pd.DataFrame( + {"chooser_attr": np.ones(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + alt_utils = np.array([10.0, 1.0] + [0.0] * (num_alts - 2), dtype=np.float64) + alternatives = pd.DataFrame( + {"alt_attr": alt_utils}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["alt_attr"], name="Expression"), + ) + + state.settings.use_explicit_error_terms = False + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_peaked_mnl_poisson_compare") + choices_mnl = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + ) + + state.init_state() + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_peaked_poisson") + choices_poisson = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + ) + + mnl_shares = _weighted_shares(choices_mnl) + poisson_shares = _weighted_shares(choices_poisson) + + weights = np.exp(alt_utils) + probs = weights / weights.sum() + expected_poisson_shares = 1 - np.power(1 - probs, sample_size) + expected_poisson_shares /= expected_poisson_shares.sum() + + assert mnl_shares.get(0, 0.0) > poisson_shares.get(0, 0.0) + 0.01 + assert abs(poisson_shares.get(0, 0.0) - expected_poisson_shares[0]) < 0.005 + assert abs(poisson_shares.get(1, 0.0) - expected_poisson_shares[1]) < 0.002 + + class _DummyChunkSizer: def log_df(self, *_args, **_kwargs): return None @@ -234,6 +304,29 @@ def random_for_df(self, df, n=1): return draw +class _DummyRngUtilityBased: + def __init__(self, rands_3d): + self.rands_3d = rands_3d + + def gumbel_max_positions_for_df( + self, + utilities, + sample_size, + stable_alt_positions=None, + n_total_alts=None, + ): + assert sample_size == self.rands_3d.shape[2] + if stable_alt_positions is None: + active_rands = self.rands_3d + else: + assert n_total_alts == self.rands_3d.shape[1] + active_rands = self.rands_3d[:, stable_alt_positions, :] + return np.argmax( + active_rands + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) + + def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_heterogeneity(): # Edge case: utilities are close across alternatives but vary strongly by chooser. # This checks that the flattened Poisson result keeps chooser/prob alignment. @@ -278,6 +371,7 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het allow_zero_probs=False, trace_label="test_repeat_alignment_chooser_heterogeneity", chunk_sizer=_DummyChunkSizer(), + sampling_method="poisson", ) probs = interaction_sample.logit.utils_to_probs( @@ -303,7 +397,7 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) -def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path(): +def test_make_sample_choices_utility_based_poisson_retry_matches_materialized_path(): chooser_index = pd.Index([201, 202, 203], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([10, 11, 12, 13], name="alt_id")) @@ -334,6 +428,7 @@ def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path() allow_zero_probs=False, trace_label="test_fused_rng_matches_materialized", chunk_sizer=_DummyChunkSizer(), + sampling_method="poisson", ) probs = interaction_sample.logit.utils_to_probs( @@ -364,6 +459,129 @@ def test_make_sample_choices_utility_based_fused_rng_matches_materialized_path() pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) +def test_make_sample_choices_utility_based_eet_matches_materialized_path(): + chooser_index = pd.Index([201, 202, 203], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([10, 11, 12, 13], name="alt_id")) + utilities = pd.DataFrame( + [[0.0, 0.3, -0.2, 0.1], [1.0, 0.2, 0.4, -0.5], [-0.1, 0.0, 0.8, 0.7]], + index=chooser_index, + ) + sample_size = 2 + n_alts = len(alternatives) + rands_3d = np.array( + [ + [[0.1, -0.3], [0.2, 0.4], [0.5, -0.1], [0.0, 0.2]], + [[-0.2, 0.3], [0.6, -0.5], [0.1, 0.7], [0.4, 0.2]], + [[0.0, 0.1], [0.3, -0.4], [0.2, 0.5], [-0.3, 0.2]], + ], + dtype=np.float64, + ) + state = _DummyState(_DummyRngUtilityBased(rands_3d)) + + out = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=n_alts, + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_eet_matches_materialized_path", + chunk_sizer=_DummyChunkSizer(), + sampling_method="eet", + ) + + chosen_positions = np.argmax( + rands_3d + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) + chosen_flat = chosen_positions.reshape(-1) + chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_eet_matches_materialized_path", + overflow_protection=True, + trace_choosers=choosers, + ).to_numpy() + + expected = pd.DataFrame( + { + "person_id": choosers.index.values[chooser_idx], + "prob": probs[chooser_idx, chosen_flat], + "alt_id": alternatives.index.values[chosen_flat], + } + ) + + pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) + + +def test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materialized_path(): + chooser_index = pd.Index([301, 302], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([10, 12, 14], name="alt_id")) + utilities = pd.DataFrame( + [[0.0, 0.3, -0.2], [1.0, 0.2, 0.4]], + index=chooser_index, + ) + sample_size = 2 + stable_alt_positions = np.array([0, 2, 4], dtype=np.int64) + n_total_alts = 5 + dense_rands_3d = np.array( + [ + [[0.1, -0.3], [0.4, 0.2], [0.2, 0.4], [0.3, -0.2], [0.5, -0.1]], + [[-0.2, 0.3], [0.0, 0.5], [0.6, -0.5], [0.2, 0.1], [0.1, 0.7]], + ], + dtype=np.float64, + ) + state = _DummyState(_DummyRngUtilityBased(dense_rands_3d)) + + out = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materialized_path", + chunk_sizer=_DummyChunkSizer(), + sampling_method="eet", + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + + active_rands = dense_rands_3d[:, stable_alt_positions, :] + chosen_positions = np.argmax( + active_rands + utilities.to_numpy()[:, :, np.newaxis], + axis=1, + ) + chosen_flat = chosen_positions.reshape(-1) + chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materialized_path", + overflow_protection=True, + trace_choosers=choosers, + ).to_numpy() + + expected = pd.DataFrame( + { + "person_id": choosers.index.values[chooser_idx], + "prob": probs[chooser_idx, chosen_flat], + "alt_id": alternatives.index.values[chosen_flat], + } + ) + + pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) + + def test_make_sample_choices_utility_based_falls_back_after_retries(): chooser_index = pd.Index([301, 302], name="person_id") choosers = pd.DataFrame(index=chooser_index) @@ -394,6 +612,7 @@ def test_make_sample_choices_utility_based_falls_back_after_retries(): allow_zero_probs=False, trace_label="test_falls_back_after_retries", chunk_sizer=_DummyChunkSizer(), + sampling_method="poisson", ) expected = pd.DataFrame( diff --git a/docs/core.rst b/docs/core.rst index 4ceaa5c34f..6b27a34914 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -336,7 +336,8 @@ EET changes the final simulation step, not the utility expressions, availability practice, it can reduce Monte Carlo noise in scenario comparisons and between demand and network assignment iterations. For configuration guidance see :ref:`explicit_error_terms_ways_to_run`. For detailed implementation notes -see :doc:`/dev-guide/explicit-error-terms`. +see :doc:`/dev-guide/explicit-error-terms`. For interaction-sample-specific sampling behavior, +see :ref:`sampling_methods_ways_to_run` and :doc:`/dev-guide/sampling-methods`. API ^^^ diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 55fb002f6d..a8dbac71ad 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -19,8 +19,9 @@ use_explicit_error_terms: True The top-level switch is defined in `activitysim.core.configuration.top.SimulationSettings.use_explicit_error_terms`. -Choice simulation code reads that setting through the model compute settings and routes -supported logit simulations through the EET path. +Choice simulation code reads that setting through the supported logit wrappers and routes +final choice simulation through the EET path. For interaction-sample-specific sampling +configuration, see {doc}`/dev-guide/sampling-methods`. ## Default Draw Versus EET @@ -75,62 +76,6 @@ Common random numbers. Stronger correlations for exptectation values of differen variance in the estimator. So we need less model runs to be representative. -### Runtime - -Runtime differs between the methods. EET generates one EV1 error term per chooser-alternative -pair, while the default Monte Carlo path draws only one uniform random number per chooser after -probabilities are computed. EET, however, does not need to compute probabilities to make choices. - -Exact runtimes depend on the number of alternatives, nesting structure, interaction size, and -sampling configuration. With default settings, current full-scale demand model runs with EET -are about 1.5 to 2 times longer than the default MC method. Virtually all of this is due to -sampling in location choice. Memory usage should be comparable for both methods. - -(explicit_error_terms_zone_encoding)= -#### Zone ID encoding and runtime - -For location choice models, encoding zone IDs as a 0-based contiguous index reduces EET runtime -and memory use during sampling. - -The current implementation draws error terms into a dense 1-D array of length `max_zone_id + 1` -per chooser (see `AltsContext.n_alts_to_cover_max_id` in `activitysim.core.logit`). Each sampled -alternative is then looked up by direct offset into that array, so the same zone always receives -the same error term regardless of which alternatives are in the sampled choice set — a property -needed for consistent scenario comparisons. - -When zone IDs are a contiguous 0-based sequence, the dense array has exactly as many entries as -there are zones and every draw is used. When zone IDs contain gaps or start from a large value, -the array must still cover `max_zone_id + 1` entries, so the draws for the missing IDs are -generated but never used. For zone systems with large or sparse IDs, this waste can be substantial. - -An alternative would be to draw only as many error terms as there are sampled alternatives and -retrieve the relevant term for each zone via a lookup. That would avoid unused draws but it does -not fit naturally with with ActivitySim's current random number generation machinery, trading -one form of overhead for another. The current design favours the dense approach because -benchmarking suggested it was quicker and because ActivitySim has a ``recode_columns`` setting -that optionally encodes zone IDs as ``zero-based`` in the input table list; see the -[Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. -We recommend using this option when running with EET. - - - - ## Implementation Details and Adding New Models The core simulation is implemented in `activitysim.core.logit.make_choices_utility_based`. Most diff --git a/docs/dev-guide/index.rst b/docs/dev-guide/index.rst index 82051ff08e..99fe1c6647 100644 --- a/docs/dev-guide/index.rst +++ b/docs/dev-guide/index.rst @@ -34,6 +34,7 @@ Contents components/index ../core explicit-error-terms + sampling-methods ../benchmarking build-docs changes diff --git a/docs/dev-guide/sampling-methods.md b/docs/dev-guide/sampling-methods.md new file mode 100644 index 0000000000..4dc9e76308 --- /dev/null +++ b/docs/dev-guide/sampling-methods.md @@ -0,0 +1,91 @@ +(sampling-methods-dev)= +# Sampling Methods for Interaction Sample + +`activitysim.core.interaction_sample` supports multiple alternative-sampling methods. +These methods are independent of the global final-choice switch controlled by +`use_explicit_error_terms`, although the global switch determines the default when no +sampling-method override is provided. + +For user-facing configuration guidance, see {ref}`sampling_methods_ways_to_run`. + +## Available Methods + +- `monte_carlo`: importance sampling with replacement using probabilities and uniform draws +- `eet`: importance sampling with replacement using explicit error-term draws +- `poisson`: independent Poisson inclusion sampling + +## Defaults and Overrides + +At the top level, `sample_method` may be set in `settings.yaml`. +When it is omitted, ActivitySim preserves the intended default behavior: + +- if `use_explicit_error_terms` is `False`, `interaction_sample` defaults to `monte_carlo` +- if `use_explicit_error_terms` is `True`, `interaction_sample` defaults to `poisson` + +A model may override this default through: + +```yaml +compute_settings: + sample_method: eet +``` + +This override affects only `activitysim.core.interaction_sample`. +It does not change the final-choice simulation method used by +`simulate`, `interaction_simulate`, or `interaction_sample_simulate`. + +## Behavioral Differences + +### Monte Carlo and EET-with-replacement + +The `monte_carlo` and `eet` sampling methods both draw sampled alternatives with replacement. +As a result, duplicates are possible within a chooser's sampled set, and the resulting sampled +shares track repeated-draw MNL behavior closely. + +The difference between them is how each draw is made: + +- `monte_carlo` draws from analytical probabilities using uniform random numbers +- `eet` draws explicit EV1 error terms and chooses the utility-plus-error argmax + +### Poisson Sampling + +`poisson` does not perform repeated draws with replacement. Instead, each chooser-alternative +pair is sampled independently with inclusion probability +$1 - (1 - p)^s$, where $p$ is the original choice probability and $s$ is the configured +sample size. + +Because sampled alternatives appear at most once per chooser, raw sampled shares can differ +substantially from repeated-draw MNL shares in highly peaked cases. This is structural behavior, +not numerical noise. The interaction-sample tests document this explicitly. + +## Runtime and Zone Encoding + +Sampling runtime differs significantly between methods. + +- `monte_carlo` draws one uniform random number per repeated sample +- `eet` draws one EV1 error term per chooser-alternative-sample combination +- `poisson` draws one Bernoulli inclusion test per chooser-alternative pair and may retry rows + that sample no alternatives + +For location choice models, encoding zone IDs as a 0-based contiguous index can reduce runtime +and memory use for the `eet` sampling method. + +(explicit_error_terms_zone_encoding)= +(sampling_methods_zone_encoding)= +### Zone ID encoding and runtime + +For location choice models, encoding zone IDs as a 0-based contiguous index reduces EET runtime +and memory use during sampling. + +The current `eet` sampling implementation draws error terms into a dense 1-D array of length +`max_zone_id + 1` per chooser (see `AltsContext.n_alts_to_cover_max_id` in +`activitysim.core.logit`). Each sampled alternative is then looked up by direct offset into that +array, so the same zone always receives the same error term regardless of which alternatives are +in the sampled choice set. + +When zone IDs are a contiguous 0-based sequence, the dense array has exactly as many entries as +there are zones and every draw is used. When zone IDs contain gaps or start from a large value, +the array must still cover `max_zone_id + 1` entries, so draws for missing IDs are generated but +never used. + +ActivitySim's `recode_columns` option can create contiguous zero-based IDs where needed; see the +[Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. diff --git a/docs/users-guide/index.rst b/docs/users-guide/index.rst index d464a6cd0d..342f67f07c 100644 --- a/docs/users-guide/index.rst +++ b/docs/users-guide/index.rst @@ -36,6 +36,7 @@ Contents modelsetup ways_to_run + sampling-methods performance/index run_primary_example model_anatomy diff --git a/docs/users-guide/sampling-methods.rst b/docs/users-guide/sampling-methods.rst new file mode 100644 index 0000000000..b20f9e72bb --- /dev/null +++ b/docs/users-guide/sampling-methods.rst @@ -0,0 +1,42 @@ +.. _sampling_methods_ways_to_run : + +Sampling Methods +________________ + +ActivitySim supports multiple sampling methods for ``activitysim.core.interaction_sample``. +These methods affect how sampled choice sets are constructed for models such as destination +and location choice. They are separate from the global final-choice switch controlled by +``use_explicit_error_terms``. + +Available methods are: + +* ``monte_carlo``: importance sampling with replacement using probabilities and uniform draws +* ``eet``: importance sampling with replacement using explicit error-term draws +* ``poisson``: independent Poisson inclusion sampling + +Default behavior depends on the global EET setting: + +* if ``use_explicit_error_terms: False``, the default sampling method is ``monte_carlo`` +* if ``use_explicit_error_terms: True``, the default sampling method is ``poisson`` + +To override the default for a particular model, set the component's compute settings: + +.. code-block:: yaml + + compute_settings: + sample_method: eet + +This override applies only to ``interaction_sample``. It does not change how final choices +are simulated elsewhere in ActivitySim. + +Practical differences: + +* ``monte_carlo`` and ``eet`` both sample with replacement, so duplicated sampled alternatives + are possible and their aggregate sampled shares track repeated-draw MNL behavior more closely. +* ``poisson`` samples alternatives by inclusion probability, so each sampled alternative appears + at most once per chooser. This can materially change raw sampled shares in highly peaked cases, + even though the downstream sampling correction remains well defined. +* ``poisson`` is the current default when global EET is enabled because it avoids repeated + chooser-by-alternative explicit-error draws during sampling. + +For implementation details and runtime considerations, see :doc:`/dev-guide/sampling-methods`. diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 57fbd6feb9..63c29aea33 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -310,13 +310,4 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: use_explicit_error_terms: True Enable or disable this setting consistently across all runs being compared. - -EET is currently slower than the default probability-based simulation method because it requires -many repeated error term draws. The exact slow-down depends on several factors, but is generally on the -order of around 1.5-2 times compared to MC. Virtually all of the increase in runtime is caused by -location choice models, and work is under way to remedy this. - -For location choice models, encoding zone IDs as a 0-based contiguous index reduces EET runtime; -see :ref:`explicit_error_terms_zone_encoding` for details. For models where the input data does not -already use contiguous zone IDs, the ``recode_columns`` option can be used to create them. See the -*Zero-based Recoding of Zones* section in :doc:`/dev-guide/using-sharrow` for more details. + \ No newline at end of file From e8fb1d71206227ea924bcaaf6585d33a1690cd2b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 13:39:22 +1000 Subject: [PATCH 233/298] poisson sampling optimization --- activitysim/core/interaction_sample.py | 34 ++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 924d3d785e..ed5027ff97 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -915,21 +915,25 @@ def _interaction_sample( chunk_sizer.log_df(trace_label, "choices_df", choices_df) - # pick_count and pick_dup - # pick_count is number of duplicate picks - # pick_dup flag is True for all but first of duplicates - pick_group = choices_df.groupby([choosers.index.name, alt_col_name]) - - # number each item in each group from 0 to the length of that group - 1. - choices_df["pick_count"] = pick_group.cumcount(ascending=True) - # flag duplicate rows after first - choices_df["pick_dup"] = choices_df["pick_count"] > 0 - # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge) - choices_df["pick_count"] += pick_group.cumcount(ascending=False) + 1 - - # drop the duplicates - choices_df = choices_df[~choices_df["pick_dup"]] - del choices_df["pick_dup"] + if sampling_method == "poisson": + choices_df['pick_count'] = 1 + else: + # pick_count and pick_dup + # pick_count is number of duplicate picks + # pick_dup flag is True for all but first of duplicates + pick_group = choices_df.groupby([choosers.index.name, alt_col_name]) + + # number each item in each group from 0 to the length of that group - 1. + choices_df["pick_count"] = pick_group.cumcount(ascending=True) + # flag duplicate rows after first + choices_df["pick_dup"] = choices_df["pick_count"] > 0 + # add reverse cumcount to get total pick_count (conveniently faster than groupby.count + merge) + choices_df["pick_count"] += pick_group.cumcount(ascending=False) + 1 + + # drop the duplicates + choices_df = choices_df[~choices_df["pick_dup"]] + del choices_df["pick_dup"] + chunk_sizer.log_df(trace_label, "choices_df", choices_df) # set index after groupby so we can trace on it From ccd509831b32e680b696602ebbb65b811ad33cea Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 13:54:25 +1000 Subject: [PATCH 234/298] poisson sampling with stable rands across scenarios where attractions flip from 0 to non-zero, other clean ups --- activitysim/core/interaction_sample.py | 41 +- activitysim/core/logit.py | 57 --- activitysim/core/random.py | 101 +++++ .../core/test/test_interaction_sample.py | 309 +++++++++++++-- .../test/test_interaction_sample_poisson.py | 373 ------------------ activitysim/core/test/test_logit.py | 143 +------ activitysim/core/test/test_random.py | 39 ++ 7 files changed, 460 insertions(+), 603 deletions(-) delete mode 100644 activitysim/core/test/test_interaction_sample_poisson.py diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index ed5027ff97..74f58547ab 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -59,6 +59,8 @@ def _poisson_sample_alternatives_inner( rng: Random, trace_label: str | None, chunk_sizer: ChunkSizer, + stable_alt_positions: np.ndarray | None = None, + n_total_alts: int | None = None, ) -> np.ndarray: """ Draw one Bernoulli inclusion decision per chooser-alternative pair. @@ -67,7 +69,18 @@ def _poisson_sample_alternatives_inner( contain their Poisson inclusion probability and unsampled alternatives are `np.nan`. """ - rands = rng.random_for_df(probs, n=probs.shape[1]) + if stable_alt_positions is None and n_total_alts is None: + rands = rng.random_for_df(probs, n=probs.shape[1]) + elif stable_alt_positions is not None and n_total_alts is not None: + rands = rng.random_for_df_stable_alt_positions( + probs, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + else: + raise ValueError( + "stable_alt_positions and n_total_alts must both be provided or omitted together" + ) chunk_sizer.log_df(trace_label, "rands", rands) return np.where(rands < poisson_inclusion_probs_values, poisson_inclusion_probs_values, np.nan) @@ -78,6 +91,8 @@ def _poisson_fallback_sample_alternatives( rng: Random, trace_label: str | None, chunk_sizer: ChunkSizer, + stable_alt_positions: np.ndarray | None = None, + n_total_alts: int | None = None, ) -> np.ndarray: """ Fallback sampler used when Poisson retries still leave empty chooser rows. @@ -93,7 +108,18 @@ def _poisson_fallback_sample_alternatives( "Fallback sampling without replacement requires sample_size <= number of alternatives" ) - fallback_rands = rng.random_for_df(probs, n=probs.shape[1]) + if stable_alt_positions is None and n_total_alts is None: + fallback_rands = rng.random_for_df(probs, n=probs.shape[1]) + elif stable_alt_positions is not None and n_total_alts is not None: + fallback_rands = rng.random_for_df_stable_alt_positions( + probs, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + else: + raise ValueError( + "stable_alt_positions and n_total_alts must both be provided or omitted together" + ) chunk_sizer.log_df(trace_label, "fallback_rands", fallback_rands) chosen_positions = np.argpartition( @@ -237,6 +263,8 @@ def make_sample_choices_utility_based( alt_col_name, state, trace_label, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) else: raise ValueError(f"Unsupported utility-based sampling method {sampling_method!r}") @@ -252,6 +280,8 @@ def _poisson_sample_alternatives( alt_col_name: str, state: workflow.State, trace_label: str, + stable_alt_positions: np.ndarray | None = None, + n_total_alts: int | None = None, ) -> pd.DataFrame: """ Build a Poisson-sampled choice set for each chooser. @@ -296,6 +326,8 @@ def _poisson_sample_alternatives( state.get_rn_generator(), trace_label, chunk_sizer, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) no_alts_sampled_mask = np.isnan(sampled_results_subset).all(axis=1) sampled_values[active_row_positions[~no_alts_sampled_mask]] = sampled_results_subset[ @@ -328,6 +360,8 @@ def _poisson_sample_alternatives( state.get_rn_generator(), trace_label, chunk_sizer, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, ) sampled_values[active_row_positions] = fallback_sampled_values break @@ -380,9 +414,6 @@ def make_sample_choices( Returns ------- - stable_alt_positions=None, - n_total_alts=None, - """ assert isinstance(probs, pd.DataFrame) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 0972ed8ff2..ec6f2943f6 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -378,63 +378,6 @@ def utils_to_probs( return probs -def add_ev1_random( - state: workflow.State, - df: pd.DataFrame, - alt_info: AltsContext | None = None, - alt_nrs_df: pd.DataFrame | None = None, -): - """ - Add iid EV1 (Gumbel) random error terms to utilities for EET choice. - - Parameters - ---------- - state : workflow.State - df : pandas.DataFrame - Utilities indexed by chooser and with alternatives as columns. - - Returns - ------- - pandas.DataFrame - Utilities with EV1 errors added. - """ - nest_utils_for_choice = df.copy() - assert (alt_info is None) == ( - alt_nrs_df is None - ), "alt_info and alt_nrs_df must both be provided or omitted together" - - if alt_info is None: - # Fallback behaviour for models where alt_info/alt_nrs_df are not provided (e.g. non-integer alts) - rands = state.get_rn_generator().gumbel_for_df( - nest_utils_for_choice, n=nest_utils_for_choice.shape[1] - ) - nest_utils_for_choice += rands - return nest_utils_for_choice - - idx_array = alt_nrs_df.values - mask = idx_array == -999 - safe_idx = np.where(mask, 1, idx_array) # replace -999 with a temp value inbounds - # generate random number for all alts - this is wasteful, but ensures that the same zone - # gets the same random number if the sampled choice set changes between base and project - # (alternatively, one could seed a channel for (persons x zones) and use the zone seed to ensure consistency. - # Trade off is needing to seed (persons x zones) rows and multiindex channels to - # avoid extra random numbers generated here. Quick benchmark suggests seeding per row is likely slower - rands_dense = state.get_rn_generator().gumbel_for_df( - nest_utils_for_choice, n=alt_info.n_alts_to_cover_max_id - ) - # generate n=alt_info.max_alt_id+1 rather than n_alts so that indexing works - # (this is drawing a random number for a redundant zeroth zone in 1 based zoning systems) - # TODO deal with non 0->n-1 indexed land use more efficiently? ideally do where alt_nrs_df is constructed, - # not on the fly here. Potentially via state.get_injectable('network_los').get_skim_dict('taz').zone_ids - rands = np.take_along_axis(rands_dense, safe_idx, axis=1) - rands[ - mask - ] = 0 # zero out the masked zones so they don't have the util adjustment of alt 0 - - nest_utils_for_choice += rands - return nest_utils_for_choice - - def _log_positive_stable_for_df( state: workflow.State, df: pd.DataFrame, alpha: float ) -> np.ndarray: diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 398c9dac99..115d4ed539 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -247,6 +247,55 @@ def random_for_df(self, df, step_name, n=1): self.row_states.loc[df.index, "offset"] += n return rands + def random_for_df_stable_alt_positions( + self, + df, + step_name, + stable_alt_positions, + n_total_alts, + ): + """ + Return one uniform draw per stable-universe alternative and chooser row, + then project to the active alternative positions. + + Parameters + ---------- + df : pandas.DataFrame + DataFrame with one row per chooser and one column per active alternative. + stable_alt_positions : 1-D ndarray + Mapping from active columns in `df` to positions in the larger stable + alternative universe. + n_total_alts : int + Number of alternatives in the larger stable universe. + + Returns + ------- + rands : 2-D ndarray + Array with shape `(len(df), df.shape[1])` containing uniforms aligned to + the active alternatives. + """ + + assert self.step_name + assert self.step_name == step_name + + n_alts = df.shape[1] + stable_alt_positions = np.asarray(stable_alt_positions) + if stable_alt_positions.shape != (n_alts,): + raise ValueError( + "stable_alt_positions must be a 1-D array aligned to df columns" + ) + if stable_alt_positions.min() < 0 or stable_alt_positions.max() >= n_total_alts: + raise ValueError( + "stable_alt_positions values must be within [0, n_total_alts)" + ) + + generators = self._generators_for_df(df) + rands = np.asanyarray( + [prng.rand(n_total_alts)[stable_alt_positions] for prng in generators] + ) + self.row_states.loc[df.index, "offset"] += n_total_alts + return rands + def gumbel_for_df(self, df, step_name, n=1): """ Return n floating point gumbel-distributed numbers for each row in df @@ -836,6 +885,58 @@ def random_for_df(self, df, n=1): rands = channel.random_for_df(df, self.step_name, n) return rands + def random_for_df_stable_alt_positions( + self, + df, + stable_alt_positions, + n_total_alts, + ): + """ + Return per-row uniform draws aligned to active alternatives using a stable + larger alternative universe. + + Parameters + ---------- + df : pandas.DataFrame + DataFrame with one row per chooser and one column per active alternative. + stable_alt_positions : 1-D ndarray + Mapping from active columns to positions in the larger stable alternative + universe. + n_total_alts : int + Number of alternatives in the larger stable universe. + + Returns + ------- + rands : 2-D ndarray + Array with shape `(len(df), df.shape[1])` containing uniforms aligned to + the active alternatives. + """ + + n_alts = df.shape[1] + stable_alt_positions = np.asarray(stable_alt_positions) + if stable_alt_positions.shape != (n_alts,): + raise ValueError( + "stable_alt_positions must be a 1-D array aligned to df columns" + ) + if stable_alt_positions.min() < 0 or stable_alt_positions.max() >= n_total_alts: + raise ValueError( + "stable_alt_positions values must be within [0, n_total_alts)" + ) + + if not self.channels: + rng = np.random.RandomState(0) + return np.asanyarray( + [rng.rand(n_total_alts)[stable_alt_positions] for _ in range(len(df))] + ) + + channel = self.get_channel_for_df(df) + return channel.random_for_df_stable_alt_positions( + df, + self.step_name, + stable_alt_positions, + n_total_alts, + ) + def gumbel_for_df(self, df, n=1): """ Return a single floating point gumbel for each row in df diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index ec8f906fac..e8c8abff8a 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -22,7 +22,8 @@ def _weighted_shares(df: pd.DataFrame) -> pd.Series: def test_interaction_sample_parity(state): - # Run interaction_sample with and without explicit error terms and check that results are similar. + # Run all three sampling methods on a realistic synthetic case and check + # that their aggregate sampled shares stay close. num_choosers = 100_000 num_alts = 100 @@ -46,7 +47,7 @@ def test_interaction_sample_parity(state): index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), ) - # Run _without_ explicit error terms + # Run Monte Carlo with replacement. state.settings.use_explicit_error_terms = False state.rng().set_base_seed(42) state.rng().add_channel("person_id", choosers) @@ -61,14 +62,14 @@ def test_interaction_sample_parity(state): alt_col_name="alt_id", ) - # Run _with_ explicit error terms + # Run Poisson inclusion sampling, which is the default when global EET is enabled. state.init_state() # reset the state to rerun with same seed state.settings.use_explicit_error_terms = True state.rng().set_base_seed(42) state.rng().add_channel("person_id", choosers) - state.rng().begin_step("test_step_explicit") + state.rng().begin_step("test_step_poisson") - choices_explicit = interaction_sample.interaction_sample( + choices_poisson = interaction_sample.interaction_sample( state, choosers, alternatives, @@ -77,30 +78,52 @@ def test_interaction_sample_parity(state): alt_col_name="alt_id", ) - assert "alt_id" in choices_mnl.columns - assert "alt_id" in choices_explicit.columns - assert not choices_mnl["alt_id"].isna().any() - assert not choices_explicit["alt_id"].isna().any() - assert choices_mnl["alt_id"].isin(alternatives.index).all() - assert choices_explicit["alt_id"].isin(alternatives.index).all() + # Run EET-with-replacement with the same global EET setting. + state.init_state() + state.settings.use_explicit_error_terms = True + state.rng().set_base_seed(42) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step("test_step_eet") - # In interaction_sample, choices_explicit and choices_mnl are DataFrames with sampled alternatives. - # The statistics of chosen alternatives should be similar. - mnl_counts = choices_mnl["alt_id"].value_counts(normalize=True).sort_index() - explicit_counts = ( - choices_explicit["alt_id"].value_counts(normalize=True).sort_index() + choices_eet = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + compute_settings=ComputeSettings(sample_method="eet"), ) - # Check top choices overlap significantly or shares are close - all_alts = set(mnl_counts.index) | set(explicit_counts.index) - for alt in all_alts: - share_mnl = mnl_counts.get(alt, 0) - share_explicit = explicit_counts.get(alt, 0) - diff = abs(share_mnl - share_explicit) - assert diff < 0.01, ( - f"Large discrepancy at alt {alt}: " - f"mnl={share_mnl:.4f}, explicit={share_explicit:.4f}, diff={diff:.4f}" - ) + assert "alt_id" in choices_mnl.columns + assert "alt_id" in choices_poisson.columns + assert "alt_id" in choices_eet.columns + assert not choices_mnl["alt_id"].isna().any() + assert not choices_poisson["alt_id"].isna().any() + assert not choices_eet["alt_id"].isna().any() + assert choices_mnl["alt_id"].isin(alternatives.index).all() + assert choices_poisson["alt_id"].isin(alternatives.index).all() + assert choices_eet["alt_id"].isin(alternatives.index).all() + + shares = { + "monte_carlo": _weighted_shares(choices_mnl), + "poisson": _weighted_shares(choices_poisson), + "eet": _weighted_shares(choices_eet), + } + + for left, right in [ + ("monte_carlo", "poisson"), + ("monte_carlo", "eet"), + ("poisson", "eet"), + ]: + all_alts = set(shares[left].index) | set(shares[right].index) + for alt in all_alts: + diff = abs(shares[left].get(alt, 0.0) - shares[right].get(alt, 0.0)) + assert diff < 0.01, ( + f"Large discrepancy at alt {alt} between {left} and {right}: " + f"{left}={shares[left].get(alt, 0.0):.4f}, " + f"{right}={shares[right].get(alt, 0.0):.4f}, diff={diff:.4f}" + ) def test_interaction_sample_eet_unavailable_alternatives(state): @@ -303,6 +326,11 @@ def random_for_df(self, df, n=1): assert draw.shape == (len(df), n) return draw + def random_for_df_stable_alt_positions(self, df, stable_alt_positions, n_total_alts): + draw = self._draws.pop(0) + assert draw.shape == (len(df), n_total_alts) + return draw[:, stable_alt_positions] + class _DummyRngUtilityBased: def __init__(self, rands_3d): @@ -327,6 +355,175 @@ def gumbel_max_positions_for_df( ) +def _expected_choices_df(sampled_alternatives, alternatives, alt_col_name): + return ( + sampled_alternatives.rename_axis("alt_idx", axis=1) + .stack() + .reset_index(name="prob") + .assign(**{alt_col_name: lambda df: alternatives.index.values[df["alt_idx"]]}) + .drop(columns=["alt_idx"]) + ) + + +def test_poisson_sample_alternatives_inner_returns_masked_inclusion_probs(): + probs = pd.DataFrame( + [[0.2, 0.4, 0.6], [0.1, 0.3, 0.5]], + index=pd.Index([11, 17], name="person_id"), + columns=[0, 1, 2], + ) + inclusion_probs_values = np.array( + [[0.36, 0.64, 0.84], [0.19, 0.51, 0.75]], + dtype=np.float64, + ) + rng = _SequentialDummyRng( + [ + np.array( + [[0.10, 0.80, 0.20], [0.30, 0.50, 0.90]], + dtype=np.float64, + ) + ] + ) + + sampled = interaction_sample._poisson_sample_alternatives_inner( + probs, + inclusion_probs_values, + rng, + trace_label="test_poisson_sample_alternatives_inner_returns_masked_inclusion_probs", + chunk_sizer=_DummyChunkSizer(), + ) + + expected = np.array( + [[0.36, np.nan, 0.84], [np.nan, 0.51, np.nan]], + dtype=np.float64, + ) + + np.testing.assert_allclose(sampled, expected, equal_nan=True) + + +def test_poisson_fallback_sample_alternatives_selects_distinct_positions_with_prob_one(): + probs = pd.DataFrame( + [[0.20, 0.30, 0.50, 0.00], [0.40, 0.10, 0.30, 0.20]], + index=pd.Index([11, 17], name="person_id"), + columns=np.arange(4), + ) + rng = _SequentialDummyRng( + [ + np.array( + [[0.90, 0.10, 0.40, 0.20], [0.05, 0.70, 0.60, 0.10]], + dtype=np.float64, + ) + ] + ) + + sampled = interaction_sample._poisson_fallback_sample_alternatives( + probs=probs, + sample_size=2, + rng=rng, + trace_label="test_poisson_fallback_sample_alternatives_selects_distinct_positions_with_prob_one", + chunk_sizer=_DummyChunkSizer(), + ) + + expected = np.array( + [[np.nan, 1.0, np.nan, 1.0], [1.0, np.nan, np.nan, 1.0]], + dtype=np.float64, + ) + + np.testing.assert_allclose(sampled, expected, equal_nan=True) + + +def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): + probs = pd.DataFrame( + [ + [0.20, 0.60, 0.10, 0.05], + [0.40, 0.10, 0.30, 0.20], + [0.30, 0.20, 0.70, 0.10], + ], + index=pd.Index([11, 17, 42], name="person_id"), + columns=np.arange(4), + ) + sample_size = 2 + alternatives = pd.DataFrame(index=pd.Index([100, 300, 700, 900], name="alt_id")) + expected_inclusion_probs = 1 - (1 - probs) ** sample_size + expected_sampled_alternatives = pd.DataFrame( + [ + [expected_inclusion_probs.iloc[0, 0], np.nan, np.nan, np.nan], + [expected_inclusion_probs.iloc[1, 0], expected_inclusion_probs.iloc[1, 1], np.nan, np.nan], + [np.nan, np.nan, expected_inclusion_probs.iloc[2, 2], np.nan], + ], + index=probs.index, + columns=probs.columns, + ) + state = _DummyState( + _SequentialDummyRng( + [ + np.array( + [ + [0.10, 0.90, 0.50, 0.90], + [0.90, 0.90, 0.90, 0.90], + [0.80, 0.90, 0.20, 0.80], + ], + dtype=np.float64, + ), + np.array([[0.10, 0.05, 0.70, 0.80]], dtype=np.float64), + ] + ) + ) + + choices_df = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + alternatives=alternatives, + sample_size=sample_size, + alt_col_name="alt_id", + state=state, + trace_label="test_poisson_sample_alternatives_retries_and_returns_expected_frames", + ) + + expected_choices_df = _expected_choices_df( + expected_sampled_alternatives, + alternatives, + "alt_id", + ) + + pd.testing.assert_frame_equal(choices_df, expected_choices_df) + + +def test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries(): + probs = pd.DataFrame( + [[0.20, 0.30, 0.50]], + index=pd.Index([11], name="person_id"), + columns=np.arange(3), + ) + sample_size = 2 + alternatives = pd.DataFrame(index=pd.Index([100, 300, 700], name="alt_id")) + fail_draw = np.array([[0.99, 0.99, 0.99]], dtype=np.float64) + fallback_draw = np.array([[0.10, 0.80, 0.20]], dtype=np.float64) + state = _DummyState(_SequentialDummyRng([fail_draw] * 10 + [fallback_draw])) + + choices_df = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + alternatives=alternatives, + sample_size=sample_size, + alt_col_name="alt_id", + state=state, + trace_label="test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries", + ) + + expected_sampled_alternatives = pd.DataFrame( + [[1.0, np.nan, 1.0]], + index=probs.index, + columns=probs.columns, + ) + expected_choices_df = _expected_choices_df( + expected_sampled_alternatives, + alternatives, + "alt_id", + ) + + pd.testing.assert_frame_equal(choices_df, expected_choices_df) + + def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_heterogeneity(): # Edge case: utilities are close across alternatives but vary strongly by chooser. # This checks that the flattened Poisson result keeps chooser/prob alignment. @@ -582,6 +779,66 @@ def test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materi pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) +def test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_materialized_path(): + chooser_index = pd.Index([311, 312], name="person_id") + choosers = pd.DataFrame(index=chooser_index) + alternatives = pd.DataFrame(index=pd.Index([10, 12, 14], name="alt_id")) + utilities = pd.DataFrame( + [[0.0, 0.3, -0.2], [1.0, 0.2, 0.4]], + index=chooser_index, + ) + sample_size = 2 + stable_alt_positions = np.array([0, 2, 4], dtype=np.int64) + n_total_alts = 5 + dense_uniforms = np.array( + [ + [0.05, 0.90, 0.10, 0.80, 0.20], + [0.90, 0.70, 0.05, 0.60, 0.10], + ], + dtype=np.float64, + ) + state = _DummyState(_SequentialDummyRng([dense_uniforms])) + + out = interaction_sample.make_sample_choices_utility_based( + state=state, + choosers=choosers, + utilities=utilities, + alternatives=alternatives, + sample_size=sample_size, + alternative_count=len(alternatives), + alt_col_name="alt_id", + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_materialized_path", + chunk_sizer=_DummyChunkSizer(), + sampling_method="poisson", + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_materialized_path", + overflow_protection=True, + trace_choosers=choosers, + ).to_numpy() + inclusion_probs = 1 - np.power(1 - probs, sample_size) + active_uniforms = dense_uniforms[:, stable_alt_positions] + sampled_values = np.where(active_uniforms < inclusion_probs, inclusion_probs, np.nan) + chooser_idx, alt_idx = np.nonzero(~np.isnan(sampled_values)) + + expected = pd.DataFrame( + { + "person_id": choosers.index.values[chooser_idx], + "prob": sampled_values[chooser_idx, alt_idx], + "alt_id": alternatives.index.values[alt_idx], + } + ) + + pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) + + def test_make_sample_choices_utility_based_falls_back_after_retries(): chooser_index = pd.Index([301, 302], name="person_id") choosers = pd.DataFrame(index=chooser_index) diff --git a/activitysim/core/test/test_interaction_sample_poisson.py b/activitysim/core/test/test_interaction_sample_poisson.py deleted file mode 100644 index 641d6a85b1..0000000000 --- a/activitysim/core/test/test_interaction_sample_poisson.py +++ /dev/null @@ -1,373 +0,0 @@ -# ActivitySim -# See full license in LICENSE.txt. - -import numpy as np -import pandas as pd -import pytest - -from activitysim.core import interaction_sample, workflow - - -class _DummyChunkSizer: - def log_df(self, *_args, **_kwargs): - return None - - -class _DummyState: - def __init__(self, rng): - self._rng = rng - - def get_rn_generator(self): - return self._rng - - -class _SequentialDummyRng: - def __init__(self, draws): - self._draws = list(draws) - - def random_for_df(self, df, n=1): - draw = self._draws.pop(0) - assert draw.shape == (len(df), n) - return draw - - -@pytest.fixture -def state() -> workflow.State: - state = workflow.State().default_settings() - state.settings.check_for_variability = False - return state - - -def _expected_choices_df(sampled_alternatives, alternatives, alt_col_name): - return ( - sampled_alternatives.rename_axis("alt_idx", axis=1) - .stack() - .reset_index(name="prob") - .assign(**{alt_col_name: lambda df: alternatives.index.values[df["alt_idx"]]}) - .drop(columns=["alt_idx"]) - ) - - -def test_poisson_sample_alternatives_inner_returns_masked_inclusion_probs(): - probs = pd.DataFrame( - [[0.2, 0.4, 0.6], [0.1, 0.3, 0.5]], - index=pd.Index([11, 17], name="person_id"), - columns=[0, 1, 2], - ) - inclusion_probs_values = np.array( - [[0.36, 0.64, 0.84], [0.19, 0.51, 0.75]], - dtype=np.float64, - ) - rng = _SequentialDummyRng( - [ - np.array( - [[0.10, 0.80, 0.20], [0.30, 0.50, 0.90]], - dtype=np.float64, - ) - ] - ) - - sampled = interaction_sample._poisson_sample_alternatives_inner( - probs, - inclusion_probs_values, - rng, - trace_label="test_poisson_sample_alternatives_inner_returns_masked_inclusion_probs", - chunk_sizer=_DummyChunkSizer(), - ) - - expected = np.array( - [[0.36, np.nan, 0.84], [np.nan, 0.51, np.nan]], - dtype=np.float64, - ) - - np.testing.assert_allclose(sampled, expected, equal_nan=True) - - -def test_poisson_fallback_sample_alternatives_selects_distinct_positions_with_prob_one(): - probs = pd.DataFrame( - [[0.20, 0.30, 0.50, 0.00], [0.40, 0.10, 0.30, 0.20]], - index=pd.Index([11, 17], name="person_id"), - columns=np.arange(4), - ) - rng = _SequentialDummyRng( - [ - np.array( - [[0.90, 0.10, 0.40, 0.20], [0.05, 0.70, 0.60, 0.10]], - dtype=np.float64, - ) - ] - ) - - sampled = interaction_sample._poisson_fallback_sample_alternatives( - probs=probs, - sample_size=2, - rng=rng, - trace_label="test_poisson_fallback_sample_alternatives_selects_distinct_positions_with_prob_one", - chunk_sizer=_DummyChunkSizer(), - ) - - expected = np.array( - [[np.nan, 1.0, np.nan, 1.0], [1.0, np.nan, np.nan, 1.0]], - dtype=np.float64, - ) - - np.testing.assert_allclose(sampled, expected, equal_nan=True) - - -def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): - probs = pd.DataFrame( - [ - [0.20, 0.60, 0.10, 0.05], - [0.40, 0.10, 0.30, 0.20], - [0.30, 0.20, 0.70, 0.10], - ], - index=pd.Index([11, 17, 42], name="person_id"), - columns=np.arange(4), - ) - sample_size = 2 - alternatives = pd.DataFrame(index=pd.Index([100, 300, 700, 900], name="alt_id")) - expected_inclusion_probs = 1 - (1 - probs) ** sample_size - expected_sampled_alternatives = pd.DataFrame( - [ - [expected_inclusion_probs.iloc[0, 0], np.nan, np.nan, np.nan], - [expected_inclusion_probs.iloc[1, 0], expected_inclusion_probs.iloc[1, 1], np.nan, np.nan], - [np.nan, np.nan, expected_inclusion_probs.iloc[2, 2], np.nan], - ], - index=probs.index, - columns=probs.columns, - ) - state = _DummyState( - _SequentialDummyRng( - [ - np.array( - [ - [0.10, 0.90, 0.50, 0.90], - [0.90, 0.90, 0.90, 0.90], - [0.80, 0.90, 0.20, 0.80], - ], - dtype=np.float64, - ), - np.array([[0.10, 0.05, 0.70, 0.80]], dtype=np.float64), - ] - ) - ) - - choices_df = interaction_sample._poisson_sample_alternatives( - chunk_sizer=_DummyChunkSizer(), - probs=probs, - alternatives=alternatives, - sample_size=sample_size, - alt_col_name="alt_id", - state=state, - trace_label="test_poisson_sample_alternatives_retries_and_returns_expected_frames", - ) - - expected_choices_df = _expected_choices_df( - expected_sampled_alternatives, - alternatives, - "alt_id", - ) - - pd.testing.assert_frame_equal(choices_df, expected_choices_df) - - -def test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries(): - probs = pd.DataFrame( - [[0.20, 0.30, 0.50]], - index=pd.Index([11], name="person_id"), - columns=np.arange(3), - ) - sample_size = 2 - alternatives = pd.DataFrame(index=pd.Index([100, 300, 700], name="alt_id")) - fail_draw = np.array([[0.99, 0.99, 0.99]], dtype=np.float64) - fallback_draw = np.array([[0.10, 0.80, 0.20]], dtype=np.float64) - state = _DummyState( - _SequentialDummyRng([fail_draw] * 10 + [fallback_draw]) - ) - - choices_df = interaction_sample._poisson_sample_alternatives( - chunk_sizer=_DummyChunkSizer(), - probs=probs, - alternatives=alternatives, - sample_size=sample_size, - alt_col_name="alt_id", - state=state, - trace_label="test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_retries", - ) - - expected_sampled_alternatives = pd.DataFrame( - [[1.0, np.nan, 1.0]], - index=probs.index, - columns=probs.columns, - ) - expected_choices_df = _expected_choices_df( - expected_sampled_alternatives, - alternatives, - "alt_id", - ) - - pd.testing.assert_frame_equal(choices_df, expected_choices_df) - - -def test_make_sample_choices_utility_based_preserves_sparse_choice_order( - monkeypatch, state -): - chooser_index = pd.Index([11, 17, 42], name="person_id") - choosers = pd.DataFrame(index=chooser_index) - alternatives = pd.DataFrame(index=pd.Index([100, 300, 700, 900], name="alt_id")) - utilities = pd.DataFrame( - [[1.0, 0.0, -1.0, 0.5], [0.1, 0.2, 0.3, 0.4], [1.0, 2.0, 3.0, 4.0]], - index=chooser_index, - columns=np.arange(len(alternatives)), - ) - - sampled_alternatives = pd.DataFrame( - [ - [0.25, np.nan, 0.75, np.nan], - [np.nan, 0.50, np.nan, 0.20], - [0.10, np.nan, np.nan, 0.90], - ], - index=chooser_index, - columns=np.arange(len(alternatives)), - ) - def fake_poisson_sample_alternatives( - chunk_sizer, - probs, - alternatives_arg, - sample_size, - alt_col_name, - state, - trace_label, - ): - assert probs.shape == sampled_alternatives.shape - assert alternatives_arg.equals(alternatives) - assert alt_col_name == "alt_id" - return _expected_choices_df(sampled_alternatives, alternatives, alt_col_name) - - monkeypatch.setattr( - interaction_sample, - "_poisson_sample_alternatives", - fake_poisson_sample_alternatives, - ) - - choices_df = interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=3, - alternative_count=len(alternatives), - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_preserves_sparse_choice_order", - chunk_sizer=_DummyChunkSizer(), - ) - - expected_choices_df = _expected_choices_df( - sampled_alternatives, alternatives, "alt_id" - ) - - pd.testing.assert_frame_equal(choices_df, expected_choices_df) - - -def test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler( - monkeypatch, -): - chooser_index = pd.Index([11, 17, 42], name="person_id") - choosers = pd.DataFrame(index=chooser_index) - alternatives = pd.DataFrame(index=pd.Index([100, 300, 700, 900], name="alt_id")) - utilities = pd.DataFrame( - [[1.0, 0.0, -1.0, 0.5], [0.1, 0.2, 0.3, 0.4], [1.0, 2.0, 3.0, 4.0]], - index=chooser_index, - columns=np.arange(len(alternatives)), - ) - probs = pd.DataFrame( - [ - [0.20, 0.60, 0.10, 0.05], - [0.40, 0.10, 0.30, 0.20], - [0.30, 0.20, 0.70, 0.10], - ], - index=chooser_index, - columns=np.arange(len(alternatives)), - ) - sample_size = 2 - inclusion_probs = 1 - (1 - probs) ** sample_size - sampled_alternatives = pd.DataFrame( - [ - [inclusion_probs.iloc[0, 0], np.nan, np.nan, np.nan], - [inclusion_probs.iloc[1, 0], inclusion_probs.iloc[1, 1], np.nan, np.nan], - [np.nan, np.nan, inclusion_probs.iloc[2, 2], np.nan], - ], - index=chooser_index, - columns=probs.columns, - ) - - monkeypatch.setattr( - interaction_sample.logit, - "utils_to_probs", - lambda *args, **kwargs: probs, - ) - - state = _DummyState( - _SequentialDummyRng( - [ - np.array( - [ - [0.10, 0.90, 0.50, 0.90], - [0.90, 0.90, 0.90, 0.90], - [0.80, 0.90, 0.20, 0.80], - ] - ), - np.array([[0.10, 0.05, 0.70, 0.80]]), - ] - ) - ) - - real_choices_df = interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=sample_size, - alternative_count=len(alternatives), - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler", - chunk_sizer=_DummyChunkSizer(), - ) - - def fake_poisson_sample_alternatives( - chunk_sizer, - probs_arg, - alternatives_arg, - sample_size_arg, - alt_col_name, - state_arg, - trace_label, - ): - assert probs_arg.equals(probs) - assert alternatives_arg.equals(alternatives) - assert sample_size_arg == sample_size - assert alt_col_name == "alt_id" - return _expected_choices_df(sampled_alternatives, alternatives, alt_col_name) - - monkeypatch.setattr( - interaction_sample, - "_poisson_sample_alternatives", - fake_poisson_sample_alternatives, - ) - - stubbed_choices_df = interaction_sample.make_sample_choices_utility_based( - state=_DummyState(_SequentialDummyRng([])), - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=sample_size, - alternative_count=len(alternatives), - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_retry_path_matches_stubbed_sampler.stub", - chunk_sizer=_DummyChunkSizer(), - ) - - pd.testing.assert_frame_equal(real_choices_df, stubbed_choices_df) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 45adcaa21c..71b37aab59 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -3,7 +3,6 @@ from __future__ import annotations import os.path -import re import numpy as np import pandas as pd @@ -12,7 +11,7 @@ from activitysim.core import logit, random, simulate, workflow from activitysim.core.exceptions import InvalidTravelError -from activitysim.core.logit import AltsContext, add_ev1_random +from activitysim.core.logit import AltsContext from activitysim.core.simulate import eval_variables @@ -337,77 +336,6 @@ def get_rn_generator(): ) -# -# EV1 Random Tests -# -def test_add_ev1_random(): - class DummyRNG: - def gumbel_for_df(self, df, n): - # Deterministic, non-constant draws make it easy to verify - # correct per-row/per-column addition behavior. - row_component = df.index.to_numpy(dtype=float).reshape(-1, 1) / 10.0 - col_component = np.arange(n, dtype=float).reshape(1, -1) - return row_component + col_component - - rng = DummyRNG() - - class DummyState: - @staticmethod - def get_rn_generator(): - return rng - - utilities = pd.DataFrame( - [[1.0, 2.0], [3.0, 4.0]], - index=[10, 11], - columns=["a", "b"], - ) - - randomized = logit.add_ev1_random(DummyState(), utilities) - - expected = pd.DataFrame( - [[2.0, 4.0], [4.1, 6.1]], - index=[10, 11], - columns=["a", "b"], - ) - - # check that the random component was added correctly, and that the original utilities were not mutated - pdt.assert_frame_equal(randomized, expected) - pdt.assert_index_equal(randomized.index, utilities.index) - pdt.assert_index_equal(randomized.columns, utilities.columns) - pdt.assert_frame_equal( - utilities, - pd.DataFrame( - [[1.0, 2.0], [3.0, 4.0]], - index=[10, 11], - columns=["a", "b"], - ), - ) - - -def test_add_ev1_random_requires_paired_alt_context_args(): - class DummyRNG: - def gumbel_for_df(self, df, n): - return np.zeros((len(df), n)) - - class DummyState: - @staticmethod - def get_rn_generator(): - return DummyRNG() - - utilities = pd.DataFrame([[1.0, 2.0]], index=[1], columns=["a", "b"]) - - with pytest.raises( - AssertionError, - match="alt_info and alt_nrs_df must both be provided or omitted together", - ): - logit.add_ev1_random( - DummyState(), - utilities, - alt_info=AltsContext.from_num_alts(2), - alt_nrs_df=None, - ) - -# # EET Choice Behavior Tests # def test_make_choices_eet_mnl(monkeypatch): @@ -1748,75 +1676,6 @@ def test_interaction_dataset_sampled(interaction_choosers, interaction_alts): pdt.assert_frame_equal(interacted, expected) -def reset_step(state, name="test_step"): - state.get_rn_generator().end_step(name) - state.get_rn_generator().begin_step(name) - - -def test_make_choices_utility_based_sampled_alts(): - """Test the situation of making choices from a sampled choice set""" - # TODO should these tests go in test_random? - state = workflow.State().default_settings() - # Make explicit that there's two indexing schemes - the raw alts, and the 0 based internals - utils_project_raw = pd.DataFrame( - {"a": 10.582999, "b": 10.680792, "c": 10.710443}, - index=pd.Index([0], name="person_id"), - ) - # zero based indexes - utils_project = utils_project_raw.rename(columns={"a": 0, "b": 1, "c": 2}) - utils_base = utils_project_raw[["a", "c"]].rename(columns={"a": 0, "c": 1}) - - assert utils_project.index.name == "person_id" - state.get_rn_generator().add_channel("persons", utils_project) - state.get_rn_generator().begin_step("test_step") - # mock base case, where alt 1 is omitted (it was improved in the project) - # this situation is quite common with poisson sampling with a variable choice set size, - # but it can also happen in with-replacement EET sampling e.g. if alt 2 had a pick_count of 2 in the base case. - # In principle, it can also be problematic for non-sampled choices where there is a base project difference in the - # availability of alternatives .e.g a new mode was introduced in the project case - - utils_project_with_rands = add_ev1_random(state, utils_project) - rands_project = utils_project_with_rands - utils_project - reset_step(state) - utils_base_with_rands = add_ev1_random(state, utils_base) - rands_base = utils_base_with_rands - utils_base - rands_base_labeled = rands_base.rename(columns={0: "a", 1: "c"}) - rands_project_labeled = rands_project.rename(columns={0: "a", 1: "b", 2: "c"}) - with pytest.raises( - AssertionError, match=re.escape('(column name="c") are different') - ): - # TODO this should pass - pdt.assert_frame_equal( - rands_base_labeled, rands_project_labeled.loc[:, rands_base_labeled.columns] - ) - # document incorrect invariant - first two columns have the same random numbers: - pdt.assert_frame_equal(rands_base, rands_project.iloc[:, :2]) - - # revised approach - reset_step(state) - alt_nrs_df = pd.DataFrame({0: 0, 1: 1, 2: 2}, index=utils_project_raw.index) - alt_info = AltsContext.from_num_alts(3, zero_based=True) - utils_project_with_rands = add_ev1_random( - state, utils_project, alt_info=alt_info, alt_nrs_df=alt_nrs_df - ) - rands_project = utils_project_with_rands - utils_project - reset_step(state) - - # alt "b" is missing from the sampled choice set, alt_nrs_df is set to reflect that - alt_nrs_df = pd.DataFrame({0: 0, 1: 2}, index=utils_project_raw.index) - utils_base_with_rands = add_ev1_random( - state, utils_base, alt_info=alt_info, alt_nrs_df=alt_nrs_df - ) - rands_base = utils_base_with_rands - utils_base - rands_base_labeled = rands_base.rename(columns={0: "a", 1: "c"}) - rands_project_labeled = rands_project.rename(columns={0: "a", 1: "b", 2: "c"}) - - # Corrected invariant holds true - pdt.assert_frame_equal( - rands_base_labeled, rands_project_labeled.loc[:, rands_base_labeled.columns] - ) - - def test_alts_context_from_series_and_properties(): ctx = AltsContext.from_series(pd.Index([3, 5, 9, 4])) diff --git a/activitysim/core/test/test_random.py b/activitysim/core/test/test_random.py index fdf23213c9..d5f84bd124 100644 --- a/activitysim/core/test/test_random.py +++ b/activitysim/core/test/test_random.py @@ -215,6 +215,45 @@ def test_gumbel_max_positions_for_df_matches_stable_alt_mapping_and_offsets(): npt.assert_allclose(next_random_after_fused, next_random_after_materialized) +def test_random_for_df_stable_alt_mapping_and_offsets(): + persons = pd.DataFrame( + {"household_id": [1, 1, 2]}, + index=pd.Index([51, 52, 53], name="person_id"), + ) + active_alts = pd.DataFrame( + np.zeros((len(persons), 3), dtype=np.float64), + index=persons.index, + ) + stable_alt_positions = np.array([0, 2, 4], dtype=np.int64) + n_total_alts = 5 + + baseline_rng = random.Random() + baseline_rng.set_base_seed(0) + baseline_rng.begin_step("test_step") + baseline_rng.add_channel("persons", persons) + + materialized = baseline_rng.random_for_df(active_alts, n=n_total_alts) + expected_rands = materialized[:, stable_alt_positions] + next_random_after_materialized = baseline_rng.random_for_df(persons) + baseline_rng.end_step("test_step") + + fused_rng = random.Random() + fused_rng.set_base_seed(0) + fused_rng.begin_step("test_step") + fused_rng.add_channel("persons", persons) + + observed_rands = fused_rng.random_for_df_stable_alt_positions( + active_alts, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + next_random_after_fused = fused_rng.random_for_df(persons) + fused_rng.end_step("test_step") + + npt.assert_allclose(observed_rands, expected_rands) + npt.assert_allclose(next_random_after_fused, next_random_after_materialized) + + def test_gumbel_choice_positions_for_df_matches_materialized_path_and_offsets(): persons = pd.DataFrame( {"household_id": [1, 1, 2]}, From fecb26d7a8c749e39b65bcd3fd12bb714a3d84d8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 16:36:20 +1000 Subject: [PATCH 235/298] do not throw on poisson fallback sampling with sample_size > alternatives --- activitysim/core/interaction_sample.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 74f58547ab..004125e5bd 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -104,9 +104,11 @@ def _poisson_fallback_sample_alternatives( `np.nan`. """ if sample_size > probs.shape[1]: - raise ValueError( - "Fallback sampling without replacement requires sample_size <= number of alternatives" + logger.info( + f"Poisson fallback sampling without replacement with sample_size={sample_size} > number of alternatives=" + + f"{probs.shape[1]}; returning all alternatives for {len(probs)} choosers" ) + return np.full(probs.shape, 1.0) if stable_alt_positions is None and n_total_alts is None: fallback_rands = rng.random_for_df(probs, n=probs.shape[1]) From 0cb92890edb2f6708a9bf3a59970c0f2b2ffbf8e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 11 May 2026 16:40:06 +1000 Subject: [PATCH 236/298] debug logging --- activitysim/core/interaction_sample.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 004125e5bd..780c423d70 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -1091,6 +1091,7 @@ def interaction_sample( use_eet = state.settings.use_explicit_error_terms sampling_method = _resolve_sample_method(state, compute_settings, use_eet) + logger.debug(f" interaction_sample sample method = {sampling_method}") # FIXME - legacy logic - not sure this is needed or even correct? if sampling_method != "poisson": @@ -1100,7 +1101,7 @@ def interaction_sample( # all are included (but this wouldn't behave well if there were land use changes in the project case which # switched regimes) - logger.info(f" --- interaction_sample sample size = {sample_size}") + logger.debug(f" interaction_sample sample size = {sample_size}") result_list = [] for ( From 3c4a3b180c1eaec178c94d8ffb98c89cfaf231fb Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 14:03:30 +1000 Subject: [PATCH 237/298] clean up --- activitysim/core/test/test_logit.py | 27 ++++++++------------ docs/dev-guide/explicit-error-terms.md | 35 +++++++++++++++++++------- docs/dev-guide/sampling-methods.md | 31 ++++++++++++----------- docs/users-guide/sampling-methods.rst | 7 +++--- 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 71b37aab59..414fb5f91f 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -583,10 +583,6 @@ def test_make_choices_vs_eet_nl_same_distribution(): ], } # Utilities for car, bus, walk - # For NL, we need utilities for all nodes in the tree for EET, - # but for probability-based choice we usually use the flattened/logsummed probabilities. - # To compare them fairly, we use the same base utilities. - # car=0.5, bus=0.2, walk=0.4 leaf_utilities = pd.DataFrame( [[0.5, 0.2, 0.4]], columns=["car", "bus", "walk"], @@ -652,7 +648,6 @@ def tracing(self): mc_fracs = np.bincount(choices_mc.values.astype(int), minlength=3) / n_draws eet_fracs = np.bincount(choices_eet.values.astype(int), minlength=3) / n_draws - # They should be close np.testing.assert_allclose(mc_fracs, eet_fracs, atol=a_tol) @@ -1623,17 +1618,17 @@ def test_three_level_nested_logit_methods_follow_monte_carlo_power_law(): # # @pytest.mark.slow -# def test_three_level_nested_logit_methods_follow_monte_carlo_power_law_large_draws(): -# _assert_three_level_nested_logit_methods_follow_power_law( -# draw_counts=np.array([8_000, 32_000, 128_000]), -# seeds=[17, 29, 43], -# slope_lower=-0.7, -# slope_upper=-0.3, -# pair_slope_lower=-1.0, -# pair_slope_upper=-0.2, -# max_final_method_error=0.0015, -# max_final_pair_error=0.0020, -# ) +def test_three_level_nested_logit_methods_follow_monte_carlo_power_law_large_draws(): + _assert_three_level_nested_logit_methods_follow_power_law( + draw_counts=np.array([8_000, 32_000, 128_000]), + seeds=[17, 29, 43], + slope_lower=-0.7, + slope_upper=-0.3, + pair_slope_lower=-1.0, + pair_slope_upper=-0.2, + max_final_method_error=0.0015, + max_final_pair_error=0.0020, + ) # diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index a8dbac71ad..e77f722fdf 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -68,12 +68,28 @@ which necessarily change for all alternatives if any observed utility changes. T with sensitivity to small differences in the final CDF draw when comparing nearby scenarios means that EET is a good candidate to remove noise from scenario comparisons. - -#### EET as a variance reduction method -TODO: expand on this here. - -Common random numbers. Stronger correlations for exptectation values of differences -> less -variance in the estimator. So we need less model runs to be representative. +### Runtime and memory usage +EET draws one error term per chooser and alternative, which requires many more random numbers +than MC's one per chooser. For models with many alternatives, this can lead to a large amount +of random numbers being calculated. To keep memory usage in line with MC simulation, the +implementation of EET avoids materialization of large chooser-alternative arrays of error +terms in memory. +Regarding runtimes, EET with default settings currently carries a runtime penalty of about 5-10% +per demand model run. However, when run in combination with an assignment model the overall +system converges faster and can cancel out any runtime penalty completely. Precise numbers are +hard to provide, but overall runtime and memory usage should not differ from runs with MC too +much. +For location choice models, keeping error terms aligned to zone IDs also affects runtime and +memory usage. To keep the same unobserved error term attached to the same zone across runs, +ActivitySim indexes EET draws by zone ID over the full universal choice set rather than only the +alternatives that happen to appear in a given calculation. + +When zone IDs are a contiguous 0-based sequence, this indexing is efficient because the dense +draw array has one entry per zone. When zone IDs contain gaps or start from a large value, the +implementation must still allocate draws up to the maximum zone ID, so additional random numbers +are generated for missing IDs and never used. Encoding zone IDs as a contiguous 0-based index can +therefore reduce both runtime and memory use for location choice models with EET; see +{ref}`explicit_error_terms_zone_encoding` for how to set this up. ## Implementation Details and Adding New Models @@ -106,6 +122,7 @@ used in the probability-based path, which is about -691 because ActivitySim clip exponentiated utilities at 1e-300. To keep behavior consistent, EET treats alternatives with utilities at or below that threshold as unavailable; see `activitysim.core.logit.validate_utils`. -### Scale of the distribution -MNL error terms are drawn from standard Gumbel distributions, i.e., the scale of the error term is -fixed to one. +### Normalization +For MNL, the error term scale is normalized to 1 by using the standard Gumbel distribution. For +nested logit, ActivitySim uses the normalized formulation in which the root nest coefficient is +fixed at 1; the EET implementation relies on that convention. diff --git a/docs/dev-guide/sampling-methods.md b/docs/dev-guide/sampling-methods.md index 4dc9e76308..ff2577e29d 100644 --- a/docs/dev-guide/sampling-methods.md +++ b/docs/dev-guide/sampling-methods.md @@ -67,25 +67,28 @@ Sampling runtime differs significantly between methods. that sample no alternatives For location choice models, encoding zone IDs as a 0-based contiguous index can reduce runtime -and memory use for the `eet` sampling method. +and memory use for the aligned `eet` and `poisson` sampling methods. (explicit_error_terms_zone_encoding)= (sampling_methods_zone_encoding)= ### Zone ID encoding and runtime -For location choice models, encoding zone IDs as a 0-based contiguous index reduces EET runtime -and memory use during sampling. - -The current `eet` sampling implementation draws error terms into a dense 1-D array of length -`max_zone_id + 1` per chooser (see `AltsContext.n_alts_to_cover_max_id` in -`activitysim.core.logit`). Each sampled alternative is then looked up by direct offset into that -array, so the same zone always receives the same error term regardless of which alternatives are -in the sampled choice set. - -When zone IDs are a contiguous 0-based sequence, the dense array has exactly as many entries as -there are zones and every draw is used. When zone IDs contain gaps or start from a large value, -the array must still cover `max_zone_id + 1` entries, so draws for missing IDs are generated but -never used. +For location choice models, ActivitySim can align random draws to positions in the full zone +universe rather than only to the alternatives active in the current sampled set. This keeps the +same zone attached to the same random draws regardless of which alternatives are present in a +particular chooser's calculation. + +Both aligned `eet` and aligned `poisson` sampling use this stable mapping. For `eet`, each chooser +receives `sample_size` sets of Gumbel draws over the full encoded zone universe, and the active +alternatives are selected from those draws by their stable zone positions. For `poisson`, each +chooser receives one aligned uniform draw per encoded zone, and those draws are used for the +Bernoulli inclusion tests. + +When zone IDs are a contiguous 0-based sequence, the aligned draw universe has exactly as many +positions as there are zones and every position is potentially useful. When zone IDs contain gaps +or start from a large value, the implementation must still cover the full encoded range, so draws +for missing IDs are generated but never used. This increases runtime and memory use, especially +for `eet`, where the aligned draw cost also scales with `sample_size`. ActivitySim's `recode_columns` option can create contiguous zero-based IDs where needed; see the [Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. diff --git a/docs/users-guide/sampling-methods.rst b/docs/users-guide/sampling-methods.rst index b20f9e72bb..172924d221 100644 --- a/docs/users-guide/sampling-methods.rst +++ b/docs/users-guide/sampling-methods.rst @@ -5,8 +5,7 @@ ________________ ActivitySim supports multiple sampling methods for ``activitysim.core.interaction_sample``. These methods affect how sampled choice sets are constructed for models such as destination -and location choice. They are separate from the global final-choice switch controlled by -``use_explicit_error_terms``. +and location choice. Available methods are: @@ -23,8 +22,8 @@ To override the default for a particular model, set the component's compute sett .. code-block:: yaml - compute_settings: - sample_method: eet + compute_settings: + sample_method: eet This override applies only to ``interaction_sample``. It does not change how final choices are simulated elsewhere in ActivitySim. From e4e415ab7561ada7eac19ba819e71e3bd91fc365 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 14:52:47 +1000 Subject: [PATCH 238/298] sampling doco --- docs/dev-guide/sampling-methods.md | 156 ++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 16 deletions(-) diff --git a/docs/dev-guide/sampling-methods.md b/docs/dev-guide/sampling-methods.md index ff2577e29d..a9e4534879 100644 --- a/docs/dev-guide/sampling-methods.md +++ b/docs/dev-guide/sampling-methods.md @@ -8,11 +8,33 @@ sampling-method override is provided. For user-facing configuration guidance, see {ref}`sampling_methods_ways_to_run`. +## Why sample alternatives? + +`interaction_sample` is mainly used in destination and location choice models, where the full +utility can be expensive to evaluate for every chooser-alternative pair. The most common example +is mode choice logsums: computing a logsum for every chooser and every possible destination can be +much more expensive than the final destination-choice simulation itself. + +ActivitySim handles this by splitting the problem into two stages: + +1. Build a sampled choice set using a cheaper approximate utility. +2. Compute the expensive terms only for the sampled alternatives and make the final choice from + that sampled set. + +In the example models, the sampling utility usually replaces `mode_choice_logsum` with cheaper +proxies such as distance skims. For example, +`activitysim/examples/prototype_arc/configs/school_location_sample.csv` and +`activitysim/examples/prototype_mtc/configs/workplace_location_sample.csv` use distance-based +sampling utilities, while the corresponding final-choice specs in +`activitysim/examples/prototype_arc/configs/school_location.csv` and +`activitysim/examples/prototype_mtc/configs/workplace_location.csv` add the full +`mode_choice_logsum` and a sampling correction term. + ## Available Methods - `monte_carlo`: importance sampling with replacement using probabilities and uniform draws - `eet`: importance sampling with replacement using explicit error-term draws -- `poisson`: independent Poisson inclusion sampling +- `poisson`: importance sampling via independent Poisson inclusion sampling ## Defaults and Overrides @@ -33,7 +55,71 @@ This override affects only `activitysim.core.interaction_sample`. It does not change the final-choice simulation method used by `simulate`, `interaction_simulate`, or `interaction_sample_simulate`. -## Behavioral Differences +## Workflow + +At a high level, the sampled-choice workflow is: + +1. Evaluate a simplified sampling utility for the full active alternative set. +2. Draw a sample of alternatives using one of the methods described below. +3. Return a sampled-alternative table with one row per chooser-sampled-alternative pair. +4. Compute expensive terms, such as `mode_choice_logsum`, only for that sampled table. +5. Add the sampling correction term to the final utility and choose from the sampled set. + +This is the standard sample-of-alternatives pattern: the sampling stage can use an approximation, +as long as the final stage includes the appropriate correction term. + +## Returned Sample Table + +`interaction_sample` returns a dataframe indexed by chooser id with columns including: + +- the sampled alternative id column named by `alt_col_name` +- `prob` +- `pick_count` + +For `monte_carlo`, the implementation also creates a `rand` column during sampling, but that +column is only kept for tracing and is dropped from the returned dataframe in normal operation. + +The meanings of `prob` and `pick_count` are important: + +- For `monte_carlo` and `eet`, `pick_count` is the number of times the alternative was selected in + the repeated with-replacement draws. +- For `poisson`, `pick_count` is always `1`, because an alternative is either included or not + included. +- For all methods, `prob` is the quantity used in the correction term, but it does not mean the + same thing for every method. + +## Sampling Correction + +ActivitySim's final sampled-choice specs typically include the term: + +```python +np.log(df.pick_count/df.prob) +``` + +This is the sample-of-alternatives correction factor used in the final choice model. + +For `monte_carlo` and `eet`, `prob` is the one-draw sampling probability implied by the +approximate sampling utility, and `pick_count` is the number of times that alternative appeared in +the repeated sample. In textbook notation, the correction for repeated with-replacement sampling is +proportional to `pick_count / (sample_size * prob)`. ActivitySim omits the common `sample_size` +factor because it is the same for every sampled alternative for that chooser and therefore only +adds a constant to utility for all alternatives, which does not change probabilities. + +For `poisson`, `prob` is the inclusion probability of the alternative in the sampled set, not the +one-draw choice probability. Specifically, if the original approximate choice probability is $p$ +and the configured sample size is $s$, then the returned `prob` is: + +$$ +1 - (1 - p)^s +$$ + +Since `pick_count` is always `1` for `poisson`, the correction becomes exactly +$\log(1 / \text{prob})$. + +This means that all sampling methods can be used interchangeably as long as the correction factor +is specified as `np.log(df.pick_count/df.prob)`. + +## Methods in Detail ### Monte Carlo and EET-with-replacement @@ -46,6 +132,14 @@ The difference between them is how each draw is made: - `monte_carlo` draws from analytical probabilities using uniform random numbers - `eet` draws explicit EV1 error terms and chooses the utility-plus-error argmax +For both methods, the returned `prob` column is the one-draw sampling probability of the selected +alternative under the approximate sampling utility. If an alternative is drawn multiple times, +those duplicate rows are collapsed and the total multiplicity is stored in `pick_count`. + +In practice, these methods are useful when a modeler wants the sampled set to behave like repeated +draws from the approximate choice model. `eet` preserves that with-replacement behavior while also +freezing the unobserved draws in a way that can greatly reduce scenario-to-scenario sampling noise. + ### Poisson Sampling `poisson` does not perform repeated draws with replacement. Instead, each chooser-alternative @@ -54,25 +148,47 @@ $1 - (1 - p)^s$, where $p$ is the original choice probability and $s$ is the con sample size. Because sampled alternatives appear at most once per chooser, raw sampled shares can differ -substantially from repeated-draw MNL shares in highly peaked cases. This is structural behavior, +noticeably from repeated-draw MNL shares in highly peaked cases. This is structural behavior, not numerical noise. The interaction-sample tests document this explicitly. -## Runtime and Zone Encoding - -Sampling runtime differs significantly between methods. - -- `monte_carlo` draws one uniform random number per repeated sample -- `eet` draws one EV1 error term per chooser-alternative-sample combination -- `poisson` draws one Bernoulli inclusion test per chooser-alternative pair and may retry rows - that sample no alternatives - -For location choice models, encoding zone IDs as a 0-based contiguous index can reduce runtime -and memory use for the aligned `eet` and `poisson` sampling methods. +There can be cases where a chooser does not receive any alternatives with Poisson sampling: +each alternative is subjected to an independent inclusion test with one random draw. However, for +the models that require sampling in ActivitySim, this is rare. If it happens, the sampler retries +that chooser row up to 10 times and then falls back to a simple without-replacement random sample. +That retry-and-fallback path is important in practice. It makes the method robust, but it also +means that Poisson sampling can have rare edge cases where two nearby scenarios consume different +random numbers because one scenario needed retries or fallback and the other did not. + +## Runtime and Simulation Noise + +Runtime and noise characteristics differ significantly across methods. + +- `monte_carlo` is the cheapest method runtime wise. It draws one uniform random number per + repeated sample, but it also has the most simulation noise because small changes in approximate + probabilities can change the sampled set substantially. +- `poisson` is also relatively inexpensive. It draws one Bernoulli inclusion test per + chooser-alternative pair, with possible retries for chooser rows that initially sample no + alternatives. With stable alternative alignment it is much less noisy than Monte Carlo, + but it can still show structural sample-set differences in highly peaked cases and rare retry + edge cases. +- `eet` is the most expensive sampling method. It draws one EV1 error term per chooser, + alternative, and repeated sample draw. In return, it produces the most stable sampled sets across + nearby scenarios because unchanged alternatives can keep the same unobserved error draws. + +For location choice models, this often leads to a practical ranking of: + +- runtime: `monte_carlo` and `poisson` low, `eet` high +- simulation noise: `monte_carlo` high, `poisson` low, `eet` lowest + +`eet` does not remove the dependence on the approximate sampling utility itself: if that utility +changes, the sampled set can still change. What it removes is the extra Monte Carlo noise from the +sampling draw. `poisson` also benefits from stable alignment, but unlike `eet` it still has some +edge cases because it uses probabilites for sampling and these depend on the utility of all +alternatives, as well as the retry/fallback edge case described above. (explicit_error_terms_zone_encoding)= (sampling_methods_zone_encoding)= -### Zone ID encoding and runtime - +### Runtime and Zone Encoding For location choice models, ActivitySim can align random draws to positions in the full zone universe rather than only to the alternatives active in the current sampled set. This keeps the same zone attached to the same random draws regardless of which alternatives are present in a @@ -92,3 +208,11 @@ for `eet`, where the aligned draw cost also scales with `sample_size`. ActivitySim's `recode_columns` option can create contiguous zero-based IDs where needed; see the [Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. + +## References + +- Kenneth Train, *Discrete Choice Methods with Simulation*, 2nd edition, Cambridge University + Press, 2009. Chapter 3.7 treats sampled choice sets and choice-model correction terms from + an estimation perspective. +- Carl-Erik Sarndal, Bengt Swensson, and Jan Wretman, *Model Assisted Survey Sampling*, Springer, + 1992. This is a standard reference for Poisson sampling as independent inclusion sampling. From b7b1d9004497175e3ab353c14a1db1ca627ec4f4 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 14:54:17 +1000 Subject: [PATCH 239/298] clean up --- docs/dev-guide/sampling-methods.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/dev-guide/sampling-methods.md b/docs/dev-guide/sampling-methods.md index a9e4534879..944b2ca97a 100644 --- a/docs/dev-guide/sampling-methods.md +++ b/docs/dev-guide/sampling-methods.md @@ -184,10 +184,11 @@ For location choice models, this often leads to a practical ranking of: changes, the sampled set can still change. What it removes is the extra Monte Carlo noise from the sampling draw. `poisson` also benefits from stable alignment, but unlike `eet` it still has some edge cases because it uses probabilites for sampling and these depend on the utility of all -alternatives, as well as the retry/fallback edge case described above. +alternatives, as well as the retry/fallback edge case described above. The exact influence on +practical scenario comparisons is an empirical question that would profit from more data points. + (explicit_error_terms_zone_encoding)= -(sampling_methods_zone_encoding)= ### Runtime and Zone Encoding For location choice models, ActivitySim can align random draws to positions in the full zone universe rather than only to the alternatives active in the current sampled set. This keeps the From bf24aa5adef02e024c16342d1089429e78fc4488 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 15:03:28 +1000 Subject: [PATCH 240/298] doco update --- docs/dev-guide/sampling-methods.md | 73 +++++++++++++++--------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/docs/dev-guide/sampling-methods.md b/docs/dev-guide/sampling-methods.md index 944b2ca97a..6407f76845 100644 --- a/docs/dev-guide/sampling-methods.md +++ b/docs/dev-guide/sampling-methods.md @@ -57,7 +57,7 @@ It does not change the final-choice simulation method used by ## Workflow -At a high level, the sampled-choice workflow is: +The sampled-choice workflow is: 1. Evaluate a simplified sampling utility for the full active alternative set. 2. Draw a sample of alternatives using one of the methods described below. @@ -65,8 +65,8 @@ At a high level, the sampled-choice workflow is: 4. Compute expensive terms, such as `mode_choice_logsum`, only for that sampled table. 5. Add the sampling correction term to the final utility and choose from the sampled set. -This is the standard sample-of-alternatives pattern: the sampling stage can use an approximation, -as long as the final stage includes the appropriate correction term. +This is the standard sample-of-alternatives pattern: the sampling stage uses an approximation, +and the final stage corrects for it. ## Returned Sample Table @@ -76,8 +76,8 @@ as long as the final stage includes the appropriate correction term. - `prob` - `pick_count` -For `monte_carlo`, the implementation also creates a `rand` column during sampling, but that -column is only kept for tracing and is dropped from the returned dataframe in normal operation. +For `monte_carlo`, the implementation also creates a `rand` column during sampling, but it is only +kept for tracing and is dropped from the returned dataframe in normal operation. The meanings of `prob` and `pick_count` are important: @@ -102,8 +102,8 @@ For `monte_carlo` and `eet`, `prob` is the one-draw sampling probability implied approximate sampling utility, and `pick_count` is the number of times that alternative appeared in the repeated sample. In textbook notation, the correction for repeated with-replacement sampling is proportional to `pick_count / (sample_size * prob)`. ActivitySim omits the common `sample_size` -factor because it is the same for every sampled alternative for that chooser and therefore only -adds a constant to utility for all alternatives, which does not change probabilities. +factor because it is the same for every sampled alternative for that chooser and therefore adds +only a chooser-specific constant to utility, which does not affect probabilities. For `poisson`, `prob` is the inclusion probability of the alternative in the sampled set, not the one-draw choice probability. Specifically, if the original approximate choice probability is $p$ @@ -116,16 +116,17 @@ $$ Since `pick_count` is always `1` for `poisson`, the correction becomes exactly $\log(1 / \text{prob})$. -This means that all sampling methods can be used interchangeably as long as the correction factor -is specified as `np.log(df.pick_count/df.prob)`. +This means that all three methods use the same correction expression, +`np.log(df.pick_count/df.prob)`, even though `prob` has a different interpretation for `poisson` +than for the with-replacement methods. ## Methods in Detail ### Monte Carlo and EET-with-replacement -The `monte_carlo` and `eet` sampling methods both draw sampled alternatives with replacement. -As a result, duplicates are possible within a chooser's sampled set, and the resulting sampled -shares track repeated-draw MNL behavior closely. +The `monte_carlo` and `eet` sampling methods both draw alternatives with replacement. As a result, +duplicates are possible within a chooser's sampled set, and sampled shares track repeated-draw MNL +behavior closely. The difference between them is how each draw is made: @@ -133,12 +134,12 @@ The difference between them is how each draw is made: - `eet` draws explicit EV1 error terms and chooses the utility-plus-error argmax For both methods, the returned `prob` column is the one-draw sampling probability of the selected -alternative under the approximate sampling utility. If an alternative is drawn multiple times, -those duplicate rows are collapsed and the total multiplicity is stored in `pick_count`. +alternative under the approximate sampling utility. If an alternative is drawn multiple times, the +duplicate rows are collapsed and the total multiplicity is stored in `pick_count`. -In practice, these methods are useful when a modeler wants the sampled set to behave like repeated -draws from the approximate choice model. `eet` preserves that with-replacement behavior while also -freezing the unobserved draws in a way that can greatly reduce scenario-to-scenario sampling noise. +These methods are useful when the sampled set should behave like repeated draws from the +approximate choice model. `eet` preserves that with-replacement behavior while also freezing the +unobserved draws, which can greatly reduce scenario-to-scenario sampling noise. ### Poisson Sampling @@ -148,30 +149,29 @@ $1 - (1 - p)^s$, where $p$ is the original choice probability and $s$ is the con sample size. Because sampled alternatives appear at most once per chooser, raw sampled shares can differ -noticeably from repeated-draw MNL shares in highly peaked cases. This is structural behavior, -not numerical noise. The interaction-sample tests document this explicitly. +noticeably from repeated-draw MNL shares in highly peaked cases. This is structural behavior, not +numerical noise. The interaction-sample tests document this explicitly. -There can be cases where a chooser does not receive any alternatives with Poisson sampling: -each alternative is subjected to an independent inclusion test with one random draw. However, for -the models that require sampling in ActivitySim, this is rare. If it happens, the sampler retries -that chooser row up to 10 times and then falls back to a simple without-replacement random sample. -That retry-and-fallback path is important in practice. It makes the method robust, but it also -means that Poisson sampling can have rare edge cases where two nearby scenarios consume different -random numbers because one scenario needed retries or fallback and the other did not. +A chooser can occasionally receive no sampled alternatives under Poisson sampling, because each +alternative is tested independently. In the models that use sampling in ActivitySim, this should be +rare. If it happens, the sampler retries that chooser row up to 10 times and then falls back to a +simple without-replacement random sample. This makes the method robust, but it also creates rare +edge cases where two nearby scenarios consume different random numbers because one scenario needed +retries or fallback and the other did not. ## Runtime and Simulation Noise -Runtime and noise characteristics differ significantly across methods. +Runtime and noise characteristics differ across methods. -- `monte_carlo` is the cheapest method runtime wise. It draws one uniform random number per - repeated sample, but it also has the most simulation noise because small changes in approximate +- `monte_carlo` is usually the cheapest method. It draws one uniform random number per repeated + sample, but it also has the most simulation noise because small changes in approximate probabilities can change the sampled set substantially. - `poisson` is also relatively inexpensive. It draws one Bernoulli inclusion test per chooser-alternative pair, with possible retries for chooser rows that initially sample no - alternatives. With stable alternative alignment it is much less noisy than Monte Carlo, - but it can still show structural sample-set differences in highly peaked cases and rare retry - edge cases. -- `eet` is the most expensive sampling method. It draws one EV1 error term per chooser, + alternatives. With stable alternative alignment it is much less noisy than Monte Carlo, but it + can still show structural sample-set differences in highly peaked cases and rare retry edge + cases. +- `eet` is usually the most expensive sampling method. It draws one EV1 error term per chooser, alternative, and repeated sample draw. In return, it produces the most stable sampled sets across nearby scenarios because unchanged alternatives can keep the same unobserved error draws. @@ -182,10 +182,9 @@ For location choice models, this often leads to a practical ranking of: `eet` does not remove the dependence on the approximate sampling utility itself: if that utility changes, the sampled set can still change. What it removes is the extra Monte Carlo noise from the -sampling draw. `poisson` also benefits from stable alignment, but unlike `eet` it still has some -edge cases because it uses probabilites for sampling and these depend on the utility of all -alternatives, as well as the retry/fallback edge case described above. The exact influence on -practical scenario comparisons is an empirical question that would profit from more data points. +sampling draw. `poisson` also benefits from stable alignment, but unlike `eet` it still depends on +probability-based inclusion tests and retains the retry/fallback edge case described above. The +practical effect on scenario comparisons is ultimately empirical. (explicit_error_terms_zone_encoding)= From 067448c4d9d087b256a8fdffed8965e6844aa319 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 15:37:52 +1000 Subject: [PATCH 241/298] linting --- .../abm/models/joint_tour_participation.py | 2 +- .../abm/models/util/tour_destination.py | 8 ++-- .../test_location_choice_sampling.py | 8 +++- .../test_tour_destination_sampling.py | 12 +++-- .../test_trip_destination_sampling.py | 12 +++-- activitysim/core/configuration/top.py | 8 ++-- activitysim/core/interaction_sample.py | 48 +++++++++++-------- activitysim/core/logit.py | 1 - activitysim/core/random.py | 20 +++++--- activitysim/core/simulate.py | 18 +++++-- .../core/test/test_interaction_sample.py | 23 +++++++-- .../test/test_interaction_sample_simulate.py | 2 + activitysim/core/test/test_logit.py | 9 +++- docs/users-guide/ways_to_run.rst | 1 - 14 files changed, 117 insertions(+), 55 deletions(-) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index 2b441d82a4..b77fe71cfb 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -20,8 +20,8 @@ ) from activitysim.core.configuration.base import ComputeSettings, PreprocessorSettings from activitysim.core.configuration.logit import LogitComponentSettings -from activitysim.core.util import assign_in_place, reindex from activitysim.core.exceptions import InvalidTravelError +from activitysim.core.util import assign_in_place, reindex logger = logging.getLogger(__name__) diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index b9a65dd1c2..04ffc2fa3b 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -12,11 +12,11 @@ from activitysim.core import ( config, estimation, + expressions, los, simulate, tracing, workflow, - expressions, ) from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.interaction_sample import interaction_sample @@ -949,9 +949,9 @@ def run_tour_destination( segment_destination_size_terms = size_term_calculator.dest_size_terms_df( segment_name, segment_trace_label ) - full_segment_destination_size_terms = size_term_calculator.destination_size_terms[ - [segment_name] - ].copy() + full_segment_destination_size_terms = ( + size_term_calculator.destination_size_terms[[segment_name]].copy() + ) full_segment_destination_size_terms.columns = ["size_term"] if choosers.shape[0] == 0: diff --git a/activitysim/abm/test/test_misc/test_location_choice_sampling.py b/activitysim/abm/test/test_misc/test_location_choice_sampling.py index f5e5846059..678dfeb1e9 100644 --- a/activitysim/abm/test/test_misc/test_location_choice_sampling.py +++ b/activitysim/abm/test/test_misc/test_location_choice_sampling.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pandas as pd from activitysim.abm.models import location_choice @@ -59,7 +61,9 @@ def fake_location_sample( index=pd.Index([1001], name="person_id"), ) - def fake_choose_maz_for_taz(_state, _taz_sample, _maz_size_terms, _trace_label, _model_settings): + def fake_choose_maz_for_taz( + _state, _taz_sample, _maz_size_terms, _trace_label, _model_settings + ): return pd.DataFrame( {"dest_MAZ": [101]}, index=pd.Index([1001], name="person_id"), @@ -137,4 +141,4 @@ def fake_choose_maz_for_taz(_state, _taz_sample, _maz_size_terms, _trace_label, assert captured["alt_dest_col_name"] == location_choice.DEST_TAZ assert captured["zone_layer"] == "taz" assert captured["n_total_alts"] == 3 - assert list(captured["stable_alt_positions"]) == [0, 2] \ No newline at end of file + assert list(captured["stable_alt_positions"]) == [0, 2] diff --git a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py index 74efd450af..f353cb1cb8 100644 --- a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pandas as pd from activitysim.abm.models.util import tour_destination @@ -64,7 +66,9 @@ def fake_choose_maz_for_taz( index=pd.Index([7001], name="tour_id"), ) - monkeypatch.setattr(tour_destination, "_destination_sample", fake_destination_sample) + monkeypatch.setattr( + tour_destination, "_destination_sample", fake_destination_sample + ) monkeypatch.setattr(tour_destination, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) state = workflow.State().default_settings() @@ -145,7 +149,9 @@ def fake_destination_sample( index=pd.Index([7001], name="tour_id"), ) - monkeypatch.setattr(tour_destination, "_destination_sample", fake_destination_sample) + monkeypatch.setattr( + tour_destination, "_destination_sample", fake_destination_sample + ) state = workflow.State().default_settings() choosers = pd.DataFrame( @@ -199,4 +205,4 @@ def fake_destination_sample( assert list(captured["stable_alt_positions"]) == [0, 2] assert captured["n_total_alts"] == 3 assert captured["alt_dest_col_name"] == "zone_id" - assert captured["zone_layer"] is None \ No newline at end of file + assert captured["zone_layer"] is None diff --git a/activitysim/abm/test/test_misc/test_trip_destination_sampling.py b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py index d4d9903841..f49bc08e5c 100644 --- a/activitysim/abm/test/test_misc/test_trip_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pandas as pd from activitysim.abm.models import trip_destination @@ -48,7 +50,9 @@ def fake_destination_sample( index=pd.Index([7001], name="trip_id"), ) - monkeypatch.setattr(trip_destination, "_destination_sample", fake_destination_sample) + monkeypatch.setattr( + trip_destination, "_destination_sample", fake_destination_sample + ) state = workflow.State().default_settings() trips = pd.DataFrame(index=pd.Index([7001], name="trip_id")) @@ -131,7 +135,9 @@ def fake_choose_maz_for_taz( index=pd.Index([7001], name="trip_id"), ) - monkeypatch.setattr(trip_destination, "_destination_sample", fake_destination_sample) + monkeypatch.setattr( + trip_destination, "_destination_sample", fake_destination_sample + ) monkeypatch.setattr(trip_destination, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) state = workflow.State().default_settings() @@ -184,4 +190,4 @@ def fake_choose_maz_for_taz( assert captured["alt_dest_col_name"] == "dest_taz" assert captured["chunk_tag"] == "trip_destination.presample" assert captured["zone_layer"] == "taz" - assert captured["presample"] is True \ No newline at end of file + assert captured["presample"] is True diff --git a/activitysim/core/configuration/top.py b/activitysim/core/configuration/top.py index 5a8746cc84..cf5bfb297b 100644 --- a/activitysim/core/configuration/top.py +++ b/activitysim/core/configuration/top.py @@ -1,9 +1,9 @@ from __future__ import annotations -from pathlib import Path -from typing import Any, Literal import struct import time +from pathlib import Path +from typing import Any, Literal from pydantic import model_validator, validator @@ -782,7 +782,7 @@ def _check_store_skims_in_shm(self): """ Make choice from random utility model by drawing from distribution of unobserved part of utility and taking the maximum of total utility. - + Defaults to standard Monte Carlo method, i.e., calculating probabilities and then drawing a single uniform random number to draw from cumulative probabily. @@ -800,7 +800,7 @@ def _check_store_skims_in_shm(self): """ run checks to validate that YAML settings files are loadable and spec and coefficent csv can be resolved. - should catch many common errors early, including missing required configurations or specified coefficient labels without defined values. + should catch many common errors early, including missing required configurations or specified coefficient labels without defined values. """ other_settings: dict[str, Any] = None diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 780c423d70..6bf91b1822 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -82,7 +82,9 @@ def _poisson_sample_alternatives_inner( "stable_alt_positions and n_total_alts must both be provided or omitted together" ) chunk_sizer.log_df(trace_label, "rands", rands) - return np.where(rands < poisson_inclusion_probs_values, poisson_inclusion_probs_values, np.nan) + return np.where( + rands < poisson_inclusion_probs_values, poisson_inclusion_probs_values, np.nan + ) def _poisson_fallback_sample_alternatives( @@ -105,8 +107,8 @@ def _poisson_fallback_sample_alternatives( """ if sample_size > probs.shape[1]: logger.info( - f"Poisson fallback sampling without replacement with sample_size={sample_size} > number of alternatives=" + - f"{probs.shape[1]}; returning all alternatives for {len(probs)} choosers" + f"Poisson fallback sampling without replacement with sample_size={sample_size} > number of alternatives=" + + f"{probs.shape[1]}; returning all alternatives for {len(probs)} choosers" ) return np.full(probs.shape, 1.0) @@ -159,12 +161,16 @@ def _eet_sample_alternatives_with_replacement( from each draw is recorded, allowing duplicates in the same way as the Monte Carlo sampling path. """ - chosen_destinations = state.get_rn_generator().gumbel_max_positions_for_df( - utilities, - sample_size, - stable_alt_positions=stable_alt_positions, - n_total_alts=n_total_alts, - ).reshape(-1) + chosen_destinations = ( + state.get_rn_generator() + .gumbel_max_positions_for_df( + utilities, + sample_size, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + .reshape(-1) + ) chunk_sizer.log_df(trace_label, "chosen_destinations", chosen_destinations) chooser_idx = np.repeat(np.arange(utilities.shape[0]), sample_size) @@ -269,7 +275,9 @@ def make_sample_choices_utility_based( n_total_alts=n_total_alts, ) else: - raise ValueError(f"Unsupported utility-based sampling method {sampling_method!r}") + raise ValueError( + f"Unsupported utility-based sampling method {sampling_method!r}" + ) return choices_df @@ -313,7 +321,9 @@ def _poisson_sample_alternatives( = np.log(1/inclusion_prob). """ - inclusion_probs_values = 1.0 - np.power(1.0 - probs.to_numpy(copy=False), sample_size) + inclusion_probs_values = 1.0 - np.power( + 1.0 - probs.to_numpy(copy=False), sample_size + ) sampled_values = np.full(inclusion_probs_values.shape, np.nan) @@ -332,18 +342,18 @@ def _poisson_sample_alternatives( n_total_alts=n_total_alts, ) no_alts_sampled_mask = np.isnan(sampled_results_subset).all(axis=1) - sampled_values[active_row_positions[~no_alts_sampled_mask]] = sampled_results_subset[ - ~no_alts_sampled_mask - ] + sampled_values[ + active_row_positions[~no_alts_sampled_mask] + ] = sampled_results_subset[~no_alts_sampled_mask] if no_alts_sampled_mask.any(): logger.info(f"Poisson sampling of alternatives failed with {n=}, retrying") failed_row_positions = active_row_positions[no_alts_sampled_mask] logger.debug( - f"Sampled size was {sample_size}, poisson method mean expected sample size was" + - f" {inclusion_probs_values[failed_row_positions].sum(axis=1).mean():.1f}, actual sampled mean was" + - f" {np.isfinite(sampled_values[failed_row_positions]).sum(axis=1).mean():.1f} and highest zero" + - f" selection prob was {(1.0 - inclusion_probs_values[failed_row_positions]).prod(axis=1).max():.2g}" + f"Sampled size was {sample_size}, poisson method mean expected sample size was" + + f" {inclusion_probs_values[failed_row_positions].sum(axis=1).mean():.1f}, actual sampled mean was" + + f" {np.isfinite(sampled_values[failed_row_positions]).sum(axis=1).mean():.1f} and highest zero" + + f" selection prob was {(1.0 - inclusion_probs_values[failed_row_positions]).prod(axis=1).max():.2g}" ) active_row_positions = failed_row_positions @@ -949,7 +959,7 @@ def _interaction_sample( chunk_sizer.log_df(trace_label, "choices_df", choices_df) if sampling_method == "poisson": - choices_df['pick_count'] = 1 + choices_df["pick_count"] = 1 else: # pick_count and pick_dup # pick_count is number of duplicate picks diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index ec6f2943f6..f8fb02707f 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -650,7 +650,6 @@ def make_choices_utility_based( return choices, rands - def make_choices( state: workflow.State, probs: pd.DataFrame, diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 115d4ed539..45cd4dcfb2 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -388,7 +388,10 @@ def gumbel_max_positions_for_df( raise ValueError( "stable_alt_positions must be a 1-D array aligned to utilities columns" ) - if stable_alt_positions.min() < 0 or stable_alt_positions.max() >= n_total_alts: + if ( + stable_alt_positions.min() < 0 + or stable_alt_positions.max() >= n_total_alts + ): raise ValueError( "stable_alt_positions values must be within [0, n_total_alts)" ) @@ -463,7 +466,9 @@ def gumbel_choice_positions_for_df( if n_rands is None: n_rands = n_alts elif n_rands != n_alts: - raise ValueError("n_rands must equal utilities.shape[1] when alt_nrs_df is omitted") + raise ValueError( + "n_rands must equal utilities.shape[1] when alt_nrs_df is omitted" + ) alt_nr_values = masked = safe_alt_nrs = None generators = self._generators_for_df(utilities) @@ -1018,7 +1023,10 @@ def gumbel_max_positions_for_df( raise ValueError( "stable_alt_positions must be a 1-D array aligned to utilities columns" ) - if stable_alt_positions.min() < 0 or stable_alt_positions.max() >= n_total_alts: + if ( + stable_alt_positions.min() < 0 + or stable_alt_positions.max() >= n_total_alts + ): raise ValueError( "stable_alt_positions values must be within [0, n_total_alts)" ) @@ -1027,9 +1035,9 @@ def gumbel_max_positions_for_df( n_gumbels = n_alts for row_num, utility_row in enumerate(utility_values): - row_gumbels = -np.log(-np.log(rng.rand(n_gumbels * sample_size))).reshape( - (sample_size, n_gumbels) - ) + row_gumbels = -np.log( + -np.log(rng.rand(n_gumbels * sample_size)) + ).reshape((sample_size, n_gumbels)) if stable_alt_positions is not None: row_gumbels = row_gumbels[:, stable_alt_positions] positions[row_num, :] = np.argmax( diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 2c88dbdb2e..55153cb29f 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -32,9 +32,7 @@ LogitNestSpec, TemplatedLogitComponentSettings, ) - -if TYPE_CHECKING: - from activitysim.core.estimation import Estimator +from activitysim.core.exceptions import ModelConfigurationError from activitysim.core.fast_eval import fast_eval from activitysim.core.simulate_consts import ( ALT_LOSER_UTIL, @@ -42,15 +40,25 @@ SPEC_EXPRESSION_NAME, SPEC_LABEL_NAME, ) -from activitysim.core.exceptions import ModelConfigurationError + +if TYPE_CHECKING: + from activitysim.core.estimation import Estimator logger = logging.getLogger(__name__) CustomChooser_T = Callable[ - [workflow.State, pd.DataFrame, pd.DataFrame, pd.DataFrame, str, dict | LogitNestSpec | None], + [ + workflow.State, + pd.DataFrame, + pd.DataFrame, + pd.DataFrame, + str, + dict | LogitNestSpec | None, + ], tuple[pd.Series, pd.Series], ] + def random_rows(state: workflow.State, df, n): # only sample if df has more than n rows if len(df.index) > n: diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index e8c8abff8a..54669a6eb6 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -1,6 +1,8 @@ # ActivitySim # See full license in LICENSE.txt. +from __future__ import annotations + import numpy as np import pandas as pd import pytest @@ -326,7 +328,9 @@ def random_for_df(self, df, n=1): assert draw.shape == (len(df), n) return draw - def random_for_df_stable_alt_positions(self, df, stable_alt_positions, n_total_alts): + def random_for_df_stable_alt_positions( + self, df, stable_alt_positions, n_total_alts + ): draw = self._draws.pop(0) assert draw.shape == (len(df), n_total_alts) return draw[:, stable_alt_positions] @@ -447,7 +451,12 @@ def test_poisson_sample_alternatives_retries_and_returns_expected_frames(): expected_sampled_alternatives = pd.DataFrame( [ [expected_inclusion_probs.iloc[0, 0], np.nan, np.nan, np.nan], - [expected_inclusion_probs.iloc[1, 0], expected_inclusion_probs.iloc[1, 1], np.nan, np.nan], + [ + expected_inclusion_probs.iloc[1, 0], + expected_inclusion_probs.iloc[1, 1], + np.nan, + np.nan, + ], [np.nan, np.nan, expected_inclusion_probs.iloc[2, 2], np.nan], ], index=probs.index, @@ -641,7 +650,11 @@ def test_make_sample_choices_utility_based_poisson_retry_matches_materialized_pa first_pass = np.where(poisson_draws < inclusion_probs, inclusion_probs, np.nan) first_pass_empty = np.isnan(first_pass).all(axis=1) sampled_values[~first_pass_empty] = first_pass[~first_pass_empty] - retry_pass = np.where(retry_draw < inclusion_probs[first_pass_empty], inclusion_probs[first_pass_empty], np.nan) + retry_pass = np.where( + retry_draw < inclusion_probs[first_pass_empty], + inclusion_probs[first_pass_empty], + np.nan, + ) sampled_values[first_pass_empty] = retry_pass chooser_idx, alt_idx = np.nonzero(~np.isnan(sampled_values)) @@ -825,7 +838,9 @@ def test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_ma ).to_numpy() inclusion_probs = 1 - np.power(1 - probs, sample_size) active_uniforms = dense_uniforms[:, stable_alt_positions] - sampled_values = np.where(active_uniforms < inclusion_probs, inclusion_probs, np.nan) + sampled_values = np.where( + active_uniforms < inclusion_probs, inclusion_probs, np.nan + ) chooser_idx, alt_idx = np.nonzero(~np.isnan(sampled_values)) expected = pd.DataFrame( diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 7683c058c3..adf4937211 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -1,6 +1,8 @@ # ActivitySim # See full license in LICENSE.txt. +from __future__ import annotations + import numpy as np import pandas as pd import pytest diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 414fb5f91f..143920fda6 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -340,7 +340,9 @@ def get_rn_generator(): # def test_make_choices_eet_mnl(monkeypatch): class DummyRNG: - def gumbel_choice_positions_for_df(self, utilities, alt_nrs_df=None, n_rands=None): + def gumbel_choice_positions_for_df( + self, utilities, alt_nrs_df=None, n_rands=None + ): assert alt_nrs_df is None assert n_rands is None assert list(utilities.columns) == ["a", "b"] @@ -538,7 +540,9 @@ def random_for_df(self, df, n=1): def gumbel_for_df(self, df, n): return eet_rng.gumbel(size=(len(df), n)) - def gumbel_choice_positions_for_df(self, utilities, alt_nrs_df=None, n_rands=None): + def gumbel_choice_positions_for_df( + self, utilities, alt_nrs_df=None, n_rands=None + ): assert alt_nrs_df is None assert n_rands is None return np.argmax( @@ -1490,6 +1494,7 @@ def test_make_choices_vs_eet_nl_exact_leaf_parity_across_structures( # assert all(dtype == np.float64 for dtype in error_terms.dtypes) + def test_make_choices_utility_based_routes_nested_logit_to_nl_eet(monkeypatch): sentinel = pd.Series([1, 0], index=pd.Index([100, 101], name="chooser_id")) diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 63c29aea33..3a12b5169a 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -310,4 +310,3 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: use_explicit_error_terms: True Enable or disable this setting consistently across all runs being compared. - \ No newline at end of file From 1f731cab53a67d78d6bb7ad2f14acf05592afc6d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 16:26:08 +1000 Subject: [PATCH 242/298] razzmatazz. I mean maz for taz. --- activitysim/abm/models/trip_destination.py | 49 ++++++--- .../abm/models/util/tour_destination.py | 43 +++++--- .../test_tour_destination_sampling.py | 101 ++++++++++++++++++ 3 files changed, 164 insertions(+), 29 deletions(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index df16941489..3ce4867849 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -367,17 +367,24 @@ def choose_MAZ_for_TAZ( # for random_for_df, we need df with de-duplicated chooser canonical index chooser_df = pd.DataFrame(index=taz_sample.index[~taz_sample.index.duplicated()]) - num_choosers = len(chooser_df) assert chooser_df.index.name == chooser_id_col - # to make choices, rands for each chooser (one rand for each sampled TAZ) - # taz_sample_size will be model_settings['SAMPLE_SIZE'] samples, except if we are estimating - taz_sample_size = taz_choices.groupby(chooser_id_col)[DEST_TAZ].count().max() + # to make choices, draw enough rands for the chooser with the largest TAZ sample, + # then keep only the draws corresponding to actual TAZ rows for each chooser. + taz_choice_counts = ( + taz_choices.groupby(chooser_id_col)[DEST_TAZ] + .count() + .reindex(chooser_df.index) + .astype(np.int64) + ) + taz_sample_size = taz_choice_counts.max() + uniform_taz_choice_counts = (taz_choice_counts == taz_sample_size).all() - # taz_choices index values should be contiguous - assert ( - taz_choices[chooser_id_col] == np.repeat(chooser_df.index, taz_sample_size) - ).all() + # taz_choices rows should remain grouped by chooser in chooser_df order + expected_chooser_ids = np.repeat( + chooser_df.index.to_numpy(), taz_choice_counts.to_numpy() + ) + assert (taz_choices[chooser_id_col].to_numpy() == expected_chooser_ids).all() # we need to choose a MAZ for each DEST_TAZ choice # probability of choosing MAZ based on MAZ size_term fraction of TAZ total @@ -445,14 +452,24 @@ def choose_MAZ_for_TAZ( # prob array with one row TAZ_choice, one column per alternative row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - assert maz_probs.shape == (num_choosers * taz_sample_size, max_maz_count) - - rands = ( - state.get_rn_generator() - .random_for_df(chooser_df, n=taz_sample_size) - .reshape(-1, 1) - ) - assert len(rands) == num_choosers * taz_sample_size + if uniform_taz_choice_counts: + assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) + rands = ( + state.get_rn_generator() + .random_for_df(chooser_df, n=taz_sample_size) + .reshape(-1, 1) + ) + assert len(rands) == len(chooser_df) * taz_sample_size + else: + assert maz_probs.shape == (len(taz_choices), max_maz_count) + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) + ) + chooser_rand_mask = ( + np.arange(taz_sample_size) < taz_choice_counts.to_numpy()[:, np.newaxis] + ) + rands = chooser_rands[chooser_rand_mask].reshape(-1, 1) + assert len(rands) == len(taz_choices) assert len(rands) == maz_probs.shape[0] # make choices diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 04ffc2fa3b..5f00c657ea 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -334,17 +334,24 @@ def choose_MAZ_for_TAZ( # for random_for_df, we need df with de-duplicated chooser canonical index chooser_df = pd.DataFrame(index=taz_sample.index[~taz_sample.index.duplicated()]) - num_choosers = len(chooser_df) assert chooser_df.index.name == chooser_id_col - # to make choices, rands for each chooser (one rand for each sampled TAZ) - # taz_sample_size will be model_settings['SAMPLE_SIZE'] samples, except if we are estimating - taz_sample_size = taz_choices.groupby(chooser_id_col)[DEST_TAZ].count().max() + # to make choices, draw enough rands for the chooser with the largest TAZ sample, + # then keep only the draws corresponding to actual TAZ rows for each chooser. + taz_choice_counts = ( + taz_choices.groupby(chooser_id_col)[DEST_TAZ] + .count() + .reindex(chooser_df.index) + .astype(np.int64) + ) + taz_sample_size = taz_choice_counts.max() + uniform_taz_choice_counts = (taz_choice_counts == taz_sample_size).all() - # taz_choices index values should be contiguous - assert ( - (taz_choices[chooser_id_col] == np.repeat(chooser_df.index, taz_sample_size)) - ).all() + # taz_choices rows should remain grouped by chooser in chooser_df order + expected_chooser_ids = np.repeat( + chooser_df.index.to_numpy(), taz_choice_counts.to_numpy() + ) + assert (taz_choices[chooser_id_col].to_numpy() == expected_chooser_ids).all() # we need to choose a MAZ for each DEST_TAZ choice # probability of choosing MAZ based on MAZ size_term fraction of TAZ total @@ -402,11 +409,21 @@ def choose_MAZ_for_TAZ( # prob array with one row TAZ_choice, one column per alternative row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - assert maz_probs.shape == (num_choosers * taz_sample_size, max_maz_count) - - rands = state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) - rands = rands.reshape(-1, 1) - assert len(rands) == num_choosers * taz_sample_size + if uniform_taz_choice_counts: + assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) + rands = state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) + rands = rands.reshape(-1, 1) + assert len(rands) == len(chooser_df) * taz_sample_size + else: + assert maz_probs.shape == (len(taz_choices), max_maz_count) + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) + ) + chooser_rand_mask = ( + np.arange(taz_sample_size) < taz_choice_counts.to_numpy()[:, np.newaxis] + ) + rands = chooser_rands[chooser_rand_mask].reshape(-1, 1) + assert len(rands) == len(taz_choices) assert len(rands) == maz_probs.shape[0] # make choices diff --git a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py index f353cb1cb8..017c44b3ab 100644 --- a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py @@ -1,5 +1,8 @@ from __future__ import annotations +from types import SimpleNamespace + +import numpy as np import pandas as pd from activitysim.abm.models.util import tour_destination @@ -28,6 +31,24 @@ def get_skim_dict(self, layer): return _DummySkimDict() +class _DummyRng: + def __init__(self, draws): + self._draws = np.asarray(draws) + + def random_for_df(self, df, n): + assert self._draws.shape == (len(df), n) + return self._draws.copy() + + +class _DummyState: + def __init__(self, draws): + self.settings = SimpleNamespace(trace_hh_id=None) + self._rng = _DummyRng(draws) + + def get_rn_generator(self): + return self._rng + + def test_destination_presample_uses_taz_stable_mapping(monkeypatch): captured = {} @@ -121,6 +142,86 @@ def fake_choose_maz_for_taz( assert list(captured["stable_alt_positions"]) == [0, 2] +def test_choose_maz_for_taz_supports_variable_taz_counts(): + state = _DummyState([[0.2, 0.81], [0.1, 0.9]]) + + taz_sample = pd.DataFrame( + { + tour_destination.DEST_TAZ: [1, 2, 2], + "prob": [0.4, 0.6, 1.0], + "pick_count": [1, 1, 1], + }, + index=pd.Index([7001, 7001, 7002], name="tour_id"), + ) + maz_size_terms = pd.DataFrame( + { + "zone_id": [101, 102, 201, 202], + tour_destination.DEST_TAZ: [1, 1, 2, 2], + "size_term": [1.0, 3.0, 4.0, 1.0], + } + ) + + out = tour_destination.choose_MAZ_for_TAZ( + state, + taz_sample, + maz_size_terms, + "test_trace", + SimpleNamespace(ESTIMATION_SAMPLE_SIZE=0, SAMPLE_SIZE=0), + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + { + tour_destination.DEST_MAZ: [101, 202, 201], + "prob": [0.10, 0.12, 0.80], + "pick_count": [1, 1, 1], + }, + index=pd.Index([7001, 7001, 7002], name="tour_id"), + ), + ) + + +def test_choose_maz_for_taz_preserves_fixed_width_path(): + state = _DummyState([[0.2, 0.81], [0.1, 0.9]]) + + taz_sample = pd.DataFrame( + { + tour_destination.DEST_TAZ: [1, 2, 1, 2], + "prob": [0.4, 0.6, 0.25, 0.75], + "pick_count": [1, 1, 1, 1], + }, + index=pd.Index([7001, 7001, 7002, 7002], name="tour_id"), + ) + maz_size_terms = pd.DataFrame( + { + "zone_id": [101, 102, 201, 202], + tour_destination.DEST_TAZ: [1, 1, 2, 2], + "size_term": [1.0, 3.0, 4.0, 1.0], + } + ) + + out = tour_destination.choose_MAZ_for_TAZ( + state, + taz_sample, + maz_size_terms, + "test_trace", + SimpleNamespace(ESTIMATION_SAMPLE_SIZE=0, SAMPLE_SIZE=0), + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + { + tour_destination.DEST_MAZ: [101, 202, 101, 202], + "prob": [0.10, 0.12, 0.0625, 0.15], + "pick_count": [1, 1, 1, 1], + }, + index=pd.Index([7001, 7001, 7002, 7002], name="tour_id"), + ), + ) + + def test_destination_sample_uses_maz_stable_mapping(monkeypatch): captured = {} From 7731971f4318a5d47b4a7654794772862390adde Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 18:00:51 +1000 Subject: [PATCH 243/298] test fixes and stable sorting for tour/trip loc choice --- activitysim/abm/models/location_choice.py | 18 +++ .../abm/models/util/tour_destination.py | 18 +++ .../test_location_choice_sampling.py | 113 ++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 1cf3f01eb0..2d7826392d 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -117,6 +117,8 @@ def _location_sample( chunk_tag, trace_label, zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, ): """ select a sample of alternative locations. @@ -212,6 +214,8 @@ def _location_sample( chunk_tag=chunk_tag, trace_label=trace_label, zone_layer=zone_layer, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, explicit_chunk_size=model_settings.explicit_chunk, compute_settings=model_settings.compute_settings.subcomponent_settings( "sample" @@ -227,6 +231,7 @@ def location_sample( persons_merged, network_los, dest_size_terms, + full_dest_size_terms, estimator, model_settings: TourLocationComponentSettings, chunk_size, @@ -245,6 +250,8 @@ def location_sample( skims = skim_dict.wrap("home_zone_id", "zone_id") alt_dest_col_name = model_settings.ALT_DEST_COL_NAME + stable_alt_positions = full_dest_size_terms.index.get_indexer(dest_size_terms.index) + assert (stable_alt_positions >= 0).all() choices = _location_sample( state, @@ -258,6 +265,8 @@ def location_sample( chunk_size, chunk_tag, trace_label, + stable_alt_positions=stable_alt_positions, + n_total_alts=len(full_dest_size_terms), ) return choices @@ -381,6 +390,12 @@ def location_presample( ) if full_dest_size_terms is None: full_dest_size_terms = dest_size_terms + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_dest_size_terms.index), name=DEST_TAZ + ) + full_taz_index = full_taz_index[~full_taz_index.duplicated()] + stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) + assert (stable_alt_positions >= 0).all() # convert MAZ zone_id to 'TAZ' in choosers (persons_merged) # persons_merged[HOME_TAZ] = persons_merged[HOME_MAZ].map(maz_to_taz) @@ -415,6 +430,8 @@ def location_presample( chunk_tag, trace_label, zone_layer="taz", + stable_alt_positions=stable_alt_positions, + n_total_alts=len(full_taz_index), ) # print(f"taz_sample\n{taz_sample}") @@ -512,6 +529,7 @@ def run_location_sample( persons_merged, network_los, dest_size_terms, + full_dest_size_terms, estimator, model_settings, chunk_size, diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 5f00c657ea..67aff1a455 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -82,6 +82,8 @@ def _destination_sample( chunk_tag, trace_label: str, zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, ): model_spec = simulate.spec_for_segment( state, @@ -155,6 +157,8 @@ def _destination_sample( chunk_tag=chunk_tag, trace_label=trace_label, zone_layer=zone_layer, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, explicit_chunk_size=model_settings.explicit_chunk, compute_settings=model_settings.compute_settings.subcomponent_settings( "sample" @@ -198,6 +202,10 @@ def destination_sample( # the name of the dest column to be returned in choices alt_dest_col_name = model_settings.ALT_DEST_COL_NAME + stable_alt_positions = full_destination_size_terms.index.get_indexer( + destination_size_terms.index + ) + assert (stable_alt_positions >= 0).all() choices = _destination_sample( state, @@ -210,6 +218,8 @@ def destination_sample( alt_dest_col_name, chunk_tag=chunk_tag, trace_label=trace_label, + stable_alt_positions=stable_alt_positions, + n_total_alts=len(full_destination_size_terms), ) return choices @@ -590,6 +600,12 @@ def destination_presample( MAZ_size_terms, TAZ_size_terms = aggregate_size_terms( destination_size_terms, network_los ) + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_destination_size_terms.index), name=DEST_TAZ + ) + full_taz_index = full_taz_index[~full_taz_index.duplicated()] + stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) + assert (stable_alt_positions >= 0).all() orig_maz = model_settings.CHOOSER_ORIG_COL_NAME assert orig_maz in choosers @@ -614,6 +630,8 @@ def destination_presample( chunk_tag=chunk_tag, trace_label=trace_label, zone_layer="taz", + stable_alt_positions=stable_alt_positions, + n_total_alts=len(full_taz_index), ) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total diff --git a/activitysim/abm/test/test_misc/test_location_choice_sampling.py b/activitysim/abm/test/test_misc/test_location_choice_sampling.py index 678dfeb1e9..dac2f670bc 100644 --- a/activitysim/abm/test/test_misc/test_location_choice_sampling.py +++ b/activitysim/abm/test/test_misc/test_location_choice_sampling.py @@ -142,3 +142,116 @@ def fake_choose_maz_for_taz( assert captured["zone_layer"] == "taz" assert captured["n_total_alts"] == 3 assert list(captured["stable_alt_positions"]) == [0, 2] + + +def test_location_sample_uses_maz_stable_mapping(monkeypatch): + captured = {} + + def fake_load_shadow_price_calculator(_state, _model_settings): + return type( + "ShadowPriceCalculator", + (), + { + "use_shadow_pricing": False, + }, + )() + + def fake_location_sample( + _state, + _segment_name, + _choosers, + alternatives, + _skims, + _estimator, + _model_settings, + alt_dest_col_name, + _chunk_size, + _chunk_tag, + _trace_label, + zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, + ): + captured["alt_dest_col_name"] = alt_dest_col_name + captured["zone_layer"] = zone_layer + captured["active_maz_index"] = alternatives.index.copy() + captured["stable_alt_positions"] = stable_alt_positions.copy() + captured["n_total_alts"] = n_total_alts + return pd.DataFrame( + {"zone_id": [101]}, + index=pd.Index([1001], name="person_id"), + ) + + monkeypatch.setattr( + location_choice.shadow_pricing, + "load_shadow_price_calculator", + fake_load_shadow_price_calculator, + ) + monkeypatch.setattr(location_choice, "_location_sample", fake_location_sample) + + state = workflow.State().default_settings() + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "zone_id", + "SIMULATE_CHOOSER_COLUMNS": [location_choice.HOME_MAZ], + }, + )() + persons_merged = pd.DataFrame( + { + location_choice.HOME_MAZ: [9001], + }, + index=pd.Index([1001], name="person_id"), + ) + network_los = type( + "DummyNetworkLos", + (), + { + "get_default_skim_dict": lambda self: _DummySkimDict(), + }, + )() + + active_dest_size_terms = pd.DataFrame( + { + "size_term": [1.0, 2.0], + "shadow_price_size_term_adjustment": [1.0, 1.0], + "shadow_price_utility_adjustment": [0.0, 0.0], + }, + index=pd.Index([101, 103], name="zone_id"), + ) + full_dest_size_terms = pd.DataFrame( + { + "size_term": [1.0, 0.0, 2.0], + "shadow_price_size_term_adjustment": [1.0, 1.0, 1.0], + "shadow_price_utility_adjustment": [0.0, 0.0, 0.0], + }, + index=pd.Index([101, 102, 103], name="zone_id"), + ) + + out = location_choice.location_sample( + state, + "segment", + persons_merged, + network_los, + active_dest_size_terms, + full_dest_size_terms, + estimator=None, + model_settings=model_settings, + chunk_size=0, + chunk_tag="test_chunk", + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame({"zone_id": [101]}, index=pd.Index([1001], name="person_id")), + ) + pd.testing.assert_index_equal( + captured["active_maz_index"], + pd.Index([101, 103], name="zone_id"), + ) + assert captured["alt_dest_col_name"] == "zone_id" + assert captured["zone_layer"] is None + assert captured["n_total_alts"] == 3 + assert list(captured["stable_alt_positions"]) == [0, 2] From 480c8adb4c5da3adaabbffc340231dcd4066a932 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 18:25:36 +1000 Subject: [PATCH 244/298] no stable alts for MC --- activitysim/core/interaction_sample.py | 7 ++ .../core/test/test_interaction_sample.py | 84 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 6bf91b1822..9617e35640 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -1103,6 +1103,13 @@ def interaction_sample( sampling_method = _resolve_sample_method(state, compute_settings, use_eet) logger.debug(f" interaction_sample sample method = {sampling_method}") + if not use_eet: + # Do not support stable alt positions or tracking total alts when running with MC sampling + # to not introduce any additional changes while adding eet simulation support to ensure no + # regressions. We can add these features later if desired. + stable_alt_positions = None + n_total_alts = None + # FIXME - legacy logic - not sure this is needed or even correct? if sampling_method != "poisson": sample_size = min(sample_size, len(alternatives.index)) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 54669a6eb6..3b5225c602 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -18,6 +18,90 @@ def state() -> workflow.State: return state +def test_interaction_sample_ignores_stable_positions_without_global_eet( + state, monkeypatch +): + # Do not support stable alt positions or tracking total alts when running with MC sampling + # to not introduce any additional changes while adding eet simulation support to ensure no + # regressions. We can add these features later if desired. + captured = {} + + def fake_interaction_sample(_state, _choosers, _alternatives, **kwargs): + captured["stable_alt_positions"] = kwargs["stable_alt_positions"] + captured["n_total_alts"] = kwargs["n_total_alts"] + return pd.DataFrame( + {"alt_id": [10, 11], "prob": [1.0, 1.0], "pick_count": [1, 1]}, + index=pd.Index([1, 2], name="person_id"), + ) + + monkeypatch.setattr(interaction_sample, "_interaction_sample", fake_interaction_sample) + + state.settings.use_explicit_error_terms = False + choosers = pd.DataFrame(index=pd.Index([1, 2], name="person_id")) + alternatives = pd.DataFrame(index=pd.Index([10, 11, 12], name="alt_id")) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["1"], name="Expression"), + ) + + interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=1, + alt_col_name="alt_id", + stable_alt_positions=np.array([0, 2], dtype=np.int64), + n_total_alts=3, + ) + + assert captured["stable_alt_positions"] is None + assert captured["n_total_alts"] is None + + +def test_interaction_sample_preserves_stable_positions_with_global_eet( + state, monkeypatch +): + captured = {} + + def fake_interaction_sample(_state, _choosers, _alternatives, **kwargs): + captured["stable_alt_positions"] = kwargs["stable_alt_positions"] + captured["n_total_alts"] = kwargs["n_total_alts"] + return pd.DataFrame( + {"alt_id": [10, 11], "prob": [1.0, 1.0], "pick_count": [1, 1]}, + index=pd.Index([1, 2], name="person_id"), + ) + + monkeypatch.setattr(interaction_sample, "_interaction_sample", fake_interaction_sample) + + state.settings.use_explicit_error_terms = True + choosers = pd.DataFrame(index=pd.Index([1, 2], name="person_id")) + alternatives = pd.DataFrame(index=pd.Index([10, 11, 12], name="alt_id")) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["1"], name="Expression"), + ) + stable_alt_positions = np.array([0, 2], dtype=np.int64) + + interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=1, + alt_col_name="alt_id", + stable_alt_positions=stable_alt_positions, + n_total_alts=3, + compute_settings=ComputeSettings(sample_method="eet"), + ) + + np.testing.assert_array_equal( + captured["stable_alt_positions"], + stable_alt_positions, + ) + assert captured["n_total_alts"] == 3 + + def _weighted_shares(df: pd.DataFrame) -> pd.Series: counts = df.groupby("alt_id")["pick_count"].sum() return (counts / counts.sum()).sort_index() From c8470ecb860787313042c2bf8538389e46d6a490 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Tue, 12 May 2026 18:48:49 +1000 Subject: [PATCH 245/298] lint --- activitysim/core/test/test_interaction_sample.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 3b5225c602..c85a1649dd 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -34,7 +34,9 @@ def fake_interaction_sample(_state, _choosers, _alternatives, **kwargs): index=pd.Index([1, 2], name="person_id"), ) - monkeypatch.setattr(interaction_sample, "_interaction_sample", fake_interaction_sample) + monkeypatch.setattr( + interaction_sample, "_interaction_sample", fake_interaction_sample + ) state.settings.use_explicit_error_terms = False choosers = pd.DataFrame(index=pd.Index([1, 2], name="person_id")) @@ -72,7 +74,9 @@ def fake_interaction_sample(_state, _choosers, _alternatives, **kwargs): index=pd.Index([1, 2], name="person_id"), ) - monkeypatch.setattr(interaction_sample, "_interaction_sample", fake_interaction_sample) + monkeypatch.setattr( + interaction_sample, "_interaction_sample", fake_interaction_sample + ) state.settings.use_explicit_error_terms = True choosers = pd.DataFrame(index=pd.Index([1, 2], name="person_id")) From a77cbeca185563d5d49510ac89f2d0637e26def2 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 13 May 2026 13:15:28 +1000 Subject: [PATCH 246/298] stable two-zone maz_from_taz for Poisson --- activitysim/abm/models/location_choice.py | 28 ++- activitysim/abm/models/trip_destination.py | 50 +++- .../abm/models/util/tour_destination.py | 22 +- .../test_location_choice_sampling.py | 133 ++++++++++- .../test_tour_destination_sampling.py | 92 ++++++- .../test_trip_destination_sampling.py | 226 ++++++++++++++++++ 6 files changed, 538 insertions(+), 13 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 2d7826392d..7a1857cbaa 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -397,6 +397,27 @@ def location_presample( stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) assert (stable_alt_positions >= 0).all() + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + taz_sample_method = None + if sample_compute_settings is not None: + taz_sample_method = sample_compute_settings.sample_method + if taz_sample_method is None: + taz_sample_method = getattr(state.settings, "sample_method", None) + if taz_sample_method is None: + taz_sample_method = ( + "poisson" + if getattr(state.settings, "use_explicit_error_terms", False) + else "monte_carlo" + ) + use_stable_taz_index = ( + getattr(state.settings, "use_explicit_error_terms", False) + and taz_sample_method == "poisson" + ) + # convert MAZ zone_id to 'TAZ' in choosers (persons_merged) # persons_merged[HOME_TAZ] = persons_merged[HOME_MAZ].map(maz_to_taz) assert HOME_MAZ in persons_merged @@ -444,7 +465,12 @@ def location_presample( # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total maz_choices = tour_destination.choose_MAZ_for_TAZ( - state, taz_sample, MAZ_size_terms, trace_label, model_settings + state, + taz_sample, + MAZ_size_terms, + trace_label, + model_settings, + full_taz_index=full_taz_index if use_stable_taz_index else None, ) assert DEST_MAZ in maz_choices diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 3ce4867849..eecb46839a 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -30,13 +30,13 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LocationComponentSettings +from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.skim_dictionary import DataFrameMatrix from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, reindex -from activitysim.core.exceptions import InvalidTravelError, DuplicateWorkflowTableError logger = logging.getLogger(__name__) @@ -295,6 +295,7 @@ def choose_MAZ_for_TAZ( alt_dest_col_name, trace_label, model_settings, + full_taz_index=None, ): """ Convert taz_sample table with TAZ zone sample choices to a table with a MAZ zone chosen for each TAZ @@ -452,7 +453,19 @@ def choose_MAZ_for_TAZ( # prob array with one row TAZ_choice, one column per alternative row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - if uniform_taz_choice_counts: + if full_taz_index is not None: + full_taz_index = pd.Index(full_taz_index, name=DEST_TAZ) + taz_positions = full_taz_index.get_indexer(taz_choices[DEST_TAZ]) + assert (taz_positions >= 0).all() + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df(chooser_df, n=len(full_taz_index)) + ) + chooser_row_positions = np.repeat( + np.arange(len(chooser_df)), taz_choice_counts.to_numpy() + ) + rands = chooser_rands[chooser_row_positions, taz_positions].reshape(-1, 1) + assert len(rands) == len(taz_choices) + elif uniform_taz_choice_counts: assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) rands = ( state.get_rn_generator() @@ -644,6 +657,28 @@ def destination_presample( alternatives = alternatives.groupby( network_los.map_maz_to_taz(alternatives.index) ).sum() + full_taz_index = pd.Index(alternatives.index, name=f"{alt_dest_col_name}_TAZ") + + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + taz_sample_method = None + if sample_compute_settings is not None: + taz_sample_method = sample_compute_settings.sample_method + if taz_sample_method is None: + taz_sample_method = getattr(state.settings, "sample_method", None) + if taz_sample_method is None: + taz_sample_method = ( + "poisson" + if getattr(state.settings, "use_explicit_error_terms", False) + else "monte_carlo" + ) + use_stable_taz_index = ( + getattr(state.settings, "use_explicit_error_terms", False) + and taz_sample_method == "poisson" + ) # # i did this but after changing alt_dest_col_name to 'trip_dest' it # # shouldn't be needed anymore @@ -676,6 +711,7 @@ def destination_presample( alt_dest_col_name, trace_label, model_settings, + full_taz_index=full_taz_index if use_stable_taz_index else None, ) assert alt_dest_col_name in maz_sample @@ -1544,13 +1580,13 @@ def run_trip_destination( """ When using the trip destination model with sharrow, it is necessary - to set a value for `purpose_index_num` in the trip destination - annotate trips preprocessor. This allows for an optimized compiled + to set a value for `purpose_index_num` in the trip destination + annotate trips preprocessor. This allows for an optimized compiled lookup of the size term from the array of size terms. The value of - `purpose_index_num` should be the integer column position in the size - matrix, with usual zero-based numpy indexing semantics (i.e. the first + `purpose_index_num` should be the integer column position in the size + matrix, with usual zero-based numpy indexing semantics (i.e. the first column is zero). The preprocessor expression most likely needs to be - "size_terms.get_cols(df.purpose)" unless some unusual transform of + "size_terms.get_cols(df.purpose)" unless some unusual transform of size terms has been employed. """ diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 67aff1a455..9aa498d17c 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -276,7 +276,12 @@ def aggregate_size_terms(dest_size_terms, network_los): def choose_MAZ_for_TAZ( - state: workflow.State, taz_sample, MAZ_size_terms, trace_label, model_settings + state: workflow.State, + taz_sample, + MAZ_size_terms, + trace_label, + model_settings, + full_taz_index=None, ): """ Convert taz_sample table with TAZ zone sample choices to a table with a MAZ zone chosen for each TAZ @@ -419,7 +424,20 @@ def choose_MAZ_for_TAZ( # prob array with one row TAZ_choice, one column per alternative row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - if uniform_taz_choice_counts: + + if full_taz_index is not None: + full_taz_index = pd.Index(full_taz_index, name=DEST_TAZ) + taz_positions = full_taz_index.get_indexer(taz_choices[DEST_TAZ]) + assert (taz_positions >= 0).all() + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df(chooser_df, n=len(full_taz_index)) + ) + chooser_row_positions = np.repeat( + np.arange(len(chooser_df)), taz_choice_counts.to_numpy() + ) + rands = chooser_rands[chooser_row_positions, taz_positions].reshape(-1, 1) + assert len(rands) == len(taz_choices) + elif uniform_taz_choice_counts: assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) rands = state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) rands = rands.reshape(-1, 1) diff --git a/activitysim/abm/test/test_misc/test_location_choice_sampling.py b/activitysim/abm/test/test_misc/test_location_choice_sampling.py index dac2f670bc..7c41136160 100644 --- a/activitysim/abm/test/test_misc/test_location_choice_sampling.py +++ b/activitysim/abm/test/test_misc/test_location_choice_sampling.py @@ -62,8 +62,14 @@ def fake_location_sample( ) def fake_choose_maz_for_taz( - _state, _taz_sample, _maz_size_terms, _trace_label, _model_settings + _state, + _taz_sample, + _maz_size_terms, + _trace_label, + _model_settings, + full_taz_index=None, ): + captured["full_taz_index"] = full_taz_index return pd.DataFrame( {"dest_MAZ": [101]}, index=pd.Index([1001], name="person_id"), @@ -142,6 +148,131 @@ def fake_choose_maz_for_taz( assert captured["zone_layer"] == "taz" assert captured["n_total_alts"] == 3 assert list(captured["stable_alt_positions"]) == [0, 2] + assert captured["full_taz_index"] is None + + +def test_location_presample_passes_full_taz_index_for_eet_poisson(monkeypatch): + captured = {} + + def fake_load_shadow_price_calculator(_state, _model_settings): + return type( + "ShadowPriceCalculator", + (), + { + "use_shadow_pricing": False, + }, + )() + + def fake_location_sample( + _state, + _segment_name, + _choosers, + alternatives, + _skims, + _estimator, + _model_settings, + alt_dest_col_name, + _chunk_size, + _chunk_tag, + _trace_label, + zone_layer=None, + stable_alt_positions=None, + n_total_alts=None, + ): + captured["alt_dest_col_name"] = alt_dest_col_name + captured["zone_layer"] = zone_layer + captured["active_taz_index"] = alternatives.index.copy() + captured["stable_alt_positions"] = stable_alt_positions.copy() + captured["n_total_alts"] = n_total_alts + return pd.DataFrame( + {"dest_TAZ": [1]}, + index=pd.Index([1001], name="person_id"), + ) + + def fake_choose_maz_for_taz( + _state, + _taz_sample, + _maz_size_terms, + _trace_label, + _model_settings, + full_taz_index=None, + ): + captured["full_taz_index"] = full_taz_index + return pd.DataFrame( + {"dest_MAZ": [101]}, + index=pd.Index([1001], name="person_id"), + ) + + monkeypatch.setattr( + location_choice.shadow_pricing, + "load_shadow_price_calculator", + fake_load_shadow_price_calculator, + ) + monkeypatch.setattr(location_choice, "_location_sample", fake_location_sample) + monkeypatch.setattr( + location_choice.tour_destination, + "choose_MAZ_for_TAZ", + fake_choose_maz_for_taz, + ) + + state = workflow.State().default_settings() + state.settings.use_explicit_error_terms = True + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "zone_id", + "SIMULATE_CHOOSER_COLUMNS": [location_choice.HOME_MAZ], + }, + )() + persons_merged = pd.DataFrame( + { + location_choice.HOME_MAZ: [9001], + location_choice.HOME_TAZ: [90], + }, + index=pd.Index([1001], name="person_id"), + ) + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + active_dest_size_terms = pd.DataFrame( + { + "size_term": [1.0, 2.0], + "shadow_price_size_term_adjustment": [1.0, 1.0], + "shadow_price_utility_adjustment": [0.0, 0.0], + }, + index=pd.Index([101, 103], name="zone_id"), + ) + full_dest_size_terms = pd.DataFrame( + { + "size_term": [1.0, 0.0, 2.0], + "shadow_price_size_term_adjustment": [1.0, 1.0, 1.0], + "shadow_price_utility_adjustment": [0.0, 0.0, 0.0], + }, + index=pd.Index([101, 102, 103], name="zone_id"), + ) + + out = location_choice.location_presample( + state, + "segment", + persons_merged, + network_los, + active_dest_size_terms, + estimator=None, + model_settings=model_settings, + chunk_size=0, + chunk_tag="test_chunk", + trace_label="test_trace", + full_dest_size_terms=full_dest_size_terms, + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame({"zone_id": [101]}, index=pd.Index([1001], name="person_id")), + ) + pd.testing.assert_index_equal( + captured["full_taz_index"], + pd.Index([1, 2, 3], name=location_choice.DEST_TAZ), + ) def test_location_sample_uses_maz_stable_mapping(monkeypatch): diff --git a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py index 017c44b3ab..22c0d00b4b 100644 --- a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py @@ -34,15 +34,20 @@ def get_skim_dict(self, layer): class _DummyRng: def __init__(self, draws): self._draws = np.asarray(draws) + self.calls = [] def random_for_df(self, df, n): + self.calls.append(n) assert self._draws.shape == (len(df), n) return self._draws.copy() class _DummyState: - def __init__(self, draws): - self.settings = SimpleNamespace(trace_hh_id=None) + def __init__(self, draws, use_explicit_error_terms=False): + self.settings = SimpleNamespace( + trace_hh_id=None, + use_explicit_error_terms=use_explicit_error_terms, + ) self._rng = _DummyRng(draws) def get_rn_generator(self): @@ -222,6 +227,89 @@ def test_choose_maz_for_taz_preserves_fixed_width_path(): ) +def test_choose_maz_for_taz_eet_poisson_uses_full_taz_positions(): + state = _DummyState([[0.99, 0.2, 0.99, 0.99, 0.8]]) + + taz_sample = pd.DataFrame( + { + tour_destination.DEST_TAZ: [2, 5], + "prob": [0.5, 0.25], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ) + maz_size_terms = pd.DataFrame( + { + "zone_id": [201, 202, 501, 502], + tour_destination.DEST_TAZ: [2, 2, 5, 5], + "size_term": [3.0, 1.0, 3.0, 1.0], + } + ) + + out = tour_destination.choose_MAZ_for_TAZ( + state, + taz_sample, + maz_size_terms, + "test_trace", + SimpleNamespace(ESTIMATION_SAMPLE_SIZE=0, SAMPLE_SIZE=0), + full_taz_index=pd.Index([1, 2, 3, 4, 5], name=tour_destination.DEST_TAZ), + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + { + tour_destination.DEST_MAZ: [201, 502], + "prob": [0.375, 0.0625], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ), + ) + assert state.get_rn_generator().calls == [5] + + +def test_choose_maz_for_taz_uses_sample_width_when_full_taz_index_omitted(): + state = _DummyState([[0.2, 0.81]]) + + taz_sample = pd.DataFrame( + { + tour_destination.DEST_TAZ: [2, 5], + "prob": [0.5, 0.25], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ) + maz_size_terms = pd.DataFrame( + { + "zone_id": [201, 202, 501, 502], + tour_destination.DEST_TAZ: [2, 2, 5, 5], + "size_term": [3.0, 1.0, 3.0, 1.0], + } + ) + + out = tour_destination.choose_MAZ_for_TAZ( + state, + taz_sample, + maz_size_terms, + "test_trace", + SimpleNamespace(ESTIMATION_SAMPLE_SIZE=0, SAMPLE_SIZE=0), + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + { + tour_destination.DEST_MAZ: [201, 502], + "prob": [0.375, 0.0625], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ), + ) + assert state.get_rn_generator().calls == [2] + + def test_destination_sample_uses_maz_stable_mapping(monkeypatch): captured = {} diff --git a/activitysim/abm/test/test_misc/test_trip_destination_sampling.py b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py index f49bc08e5c..e8aae5164d 100644 --- a/activitysim/abm/test/test_misc/test_trip_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py @@ -1,5 +1,8 @@ from __future__ import annotations +from types import SimpleNamespace + +import numpy as np import pandas as pd from activitysim.abm.models import trip_destination @@ -21,6 +24,32 @@ def __init__(self, maz_to_taz): def map_maz_to_taz(self, maz_index): return pd.Index([self._maz_to_taz[maz] for maz in maz_index], name="zone_id") + def get_maz_to_taz_series(self, _state): + return pd.Series(self._maz_to_taz) + + +class _DummyRng: + def __init__(self, draws): + self._draws = np.asarray(draws) + self.calls = [] + + def random_for_df(self, df, n): + self.calls.append(n) + assert self._draws.shape == (len(df), n) + return self._draws.copy() + + +class _DummyState: + def __init__(self, draws, use_explicit_error_terms=False): + self.settings = SimpleNamespace( + trace_hh_id=None, + use_explicit_error_terms=use_explicit_error_terms, + ) + self._rng = _DummyRng(draws) + + def get_rn_generator(self): + return self._rng + def test_destination_sample_retains_full_maz_universe(monkeypatch): captured = {} @@ -129,7 +158,9 @@ def fake_choose_maz_for_taz( _alt_dest_col_name, _trace_label, _model_settings, + full_taz_index=None, ): + captured["full_taz_index"] = full_taz_index return pd.DataFrame( {"dest_taz": [101]}, index=pd.Index([7001], name="trip_id"), @@ -191,3 +222,198 @@ def fake_choose_maz_for_taz( assert captured["chunk_tag"] == "trip_destination.presample" assert captured["zone_layer"] == "taz" assert captured["presample"] is True + assert captured["full_taz_index"] is None + + +def test_destination_presample_passes_full_taz_index_for_eet_poisson(monkeypatch): + captured = {} + + def fake_destination_sample( + _state, + _primary_purpose, + _trips, + alternatives, + _model_settings, + size_term_matrix, + skims, + alt_dest_col_name, + _estimator, + chunk_tag, + trace_label, + zone_layer=None, + ): + captured["alternatives_index"] = alternatives.index.copy() + captured["size_term_index"] = size_term_matrix.df.index.copy() + captured["alt_dest_col_name"] = alt_dest_col_name + captured["chunk_tag"] = chunk_tag + captured["trace_label"] = trace_label + captured["zone_layer"] = zone_layer + captured["presample"] = skims["presample"] + return pd.DataFrame( + {"dest_taz": [1]}, + index=pd.Index([7001], name="trip_id"), + ) + + def fake_choose_maz_for_taz( + _state, + _taz_sample, + _maz_size_terms, + _trips, + _network_los, + _alt_dest_col_name, + _trace_label, + _model_settings, + full_taz_index=None, + ): + captured["full_taz_index"] = full_taz_index + return pd.DataFrame( + {"dest_taz": [101]}, + index=pd.Index([7001], name="trip_id"), + ) + + monkeypatch.setattr( + trip_destination, "_destination_sample", fake_destination_sample + ) + monkeypatch.setattr(trip_destination, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) + + state = workflow.State().default_settings() + state.settings.use_explicit_error_terms = True + trips = pd.DataFrame( + {"origin": [101], "tour_leg_dest": [103]}, + index=pd.Index([7001], name="trip_id"), + ) + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "dest_taz", + "TRIP_ORIGIN": "origin", + "PRIMARY_DEST": "tour_leg_dest", + }, + )() + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + alternatives = pd.DataFrame( + {"eatout": [1.0, 0.0, 2.0]}, + index=pd.Index([101, 102, 103], name="dest_taz"), + ) + size_term_matrix = DataFrameMatrix(alternatives) + + out = trip_destination.destination_presample( + state, + "eatout", + trips, + alternatives, + model_settings, + size_term_matrix, + _DummySkimHotel(), + network_los, + estimator=None, + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame({"dest_taz": [101]}, index=pd.Index([7001], name="trip_id")), + ) + pd.testing.assert_index_equal( + captured["full_taz_index"], + pd.Index([1, 2, 3], name="dest_taz_TAZ"), + ) + + +def test_choose_maz_for_taz_eet_poisson_uses_full_taz_positions(): + state = _DummyState([[0.99, 0.2, 0.99, 0.99, 0.8]]) + network_los = _DummyNetworkLos({201: 2, 202: 2, 501: 5, 502: 5}) + + taz_sample = pd.DataFrame( + { + "dest_taz": [2, 5], + "prob": [0.5, 0.25], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="trip_id"), + ) + maz_size_terms = DataFrameMatrix( + pd.DataFrame( + {"eatout": [3.0, 1.0, 3.0, 1.0]}, + index=pd.Index([201, 202, 501, 502], name="dest_taz"), + ) + ) + trips = pd.DataFrame( + {"purpose": ["eatout"]}, + index=pd.Index([7001], name="trip_id"), + ) + + out = trip_destination.choose_MAZ_for_TAZ( + state, + taz_sample, + maz_size_terms, + trips, + network_los, + "dest_taz", + "test_trace", + SimpleNamespace(ESTIMATION_SAMPLE_SIZE=0, SAMPLE_SIZE=0), + full_taz_index=pd.Index([1, 2, 3, 4, 5], name="dest_taz_TAZ"), + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + { + "dest_taz": [201, 502], + "prob": [0.375, 0.0625], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="trip_id"), + ), + ) + assert state.get_rn_generator().calls == [5] + + +def test_choose_maz_for_taz_uses_sample_width_when_full_taz_index_omitted(): + state = _DummyState([[0.2, 0.81]]) + network_los = _DummyNetworkLos({201: 2, 202: 2, 501: 5, 502: 5}) + + taz_sample = pd.DataFrame( + { + "dest_taz": [2, 5], + "prob": [0.5, 0.25], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="trip_id"), + ) + maz_size_terms = DataFrameMatrix( + pd.DataFrame( + {"eatout": [3.0, 1.0, 3.0, 1.0]}, + index=pd.Index([201, 202, 501, 502], name="dest_taz"), + ) + ) + trips = pd.DataFrame( + {"purpose": ["eatout"]}, + index=pd.Index([7001], name="trip_id"), + ) + + out = trip_destination.choose_MAZ_for_TAZ( + state, + taz_sample, + maz_size_terms, + trips, + network_los, + "dest_taz", + "test_trace", + SimpleNamespace(ESTIMATION_SAMPLE_SIZE=0, SAMPLE_SIZE=0), + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + { + "dest_taz": [201, 502], + "prob": [0.375, 0.0625], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="trip_id"), + ), + ) + assert state.get_rn_generator().calls == [2] From e91e5660b3874935d186e9e0599ce36954d79f99 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 13 May 2026 19:56:49 +1000 Subject: [PATCH 247/298] more doco --- .../core/interaction_sample_simulate.py | 1 + docs/core.rst | 5 +- docs/dev-guide/explicit-error-terms.md | 83 +++++++++++++++---- docs/users-guide/ways_to_run.rst | 5 +- 4 files changed, 73 insertions(+), 21 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 9c07fd6ef0..b9d6134471 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -538,6 +538,7 @@ def interaction_sample_simulate( trace_label = tracing.extend_trace_label(trace_label, "interaction_sample_simulate") chunk_tag = chunk_tag or trace_label + # TODO EET: Do we just want to warn here? Or better throw and be explicit? if state.settings.use_explicit_error_terms: choice_ids_are_int = pd.api.types.is_integer_dtype(alternatives[choice_column]) if alts_context is None and choice_ids_are_int: diff --git a/docs/core.rst b/docs/core.rst index 6b27a34914..ac0189c2a0 100644 --- a/docs/core.rst +++ b/docs/core.rst @@ -335,9 +335,8 @@ with the highest total utility. EET changes the final simulation step, not the utility expressions, availability logic, or nesting structure. In practice, it can reduce Monte Carlo noise in scenario comparisons and between demand and network assignment iterations. -For configuration guidance see :ref:`explicit_error_terms_ways_to_run`. For detailed implementation notes -see :doc:`/dev-guide/explicit-error-terms`. For interaction-sample-specific sampling behavior, -see :ref:`sampling_methods_ways_to_run` and :doc:`/dev-guide/sampling-methods`. +For configuration guidance see :ref:`explicit_error_terms_ways_to_run`. For detailed background and implementation notes +see :doc:`/dev-guide/explicit-error-terms`. API ^^^ diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index e77f722fdf..6fefe411e2 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -7,7 +7,7 @@ interpretation as the standard method, but changes how the final simulated choic drawn. For details, see [this ATRF paper](https://australasiantransportresearchforum.org.au/frozen-randomness-at-the-individual-utility-level/). -For user-facing guidance, see {ref}`explicit_error_terms_ways_to_run`. + ## Enabling EET @@ -52,21 +52,70 @@ to draw error terms of all fundamental alternatives. ### Comparisons and Simulation Noise For EET to reduce simulation noise, it is important that alternatives of a choice situation -keep the same unobserved error term in different scenario runs. This is intimately tied +keep the same unobserved error term in different scenario runs. If unchanged alternatives +keep the same unobserved draws, changes to choices between scenarios can only happen when +the observed utility of an alternative increases. This is not the case for the Monte Carlo +simulation method, where the draws are based on probabilities, which necessarily change for +all alternatives if any observed utility changes. This combined with sensitivity to small +differences in the final CDF draw when comparing nearby scenarios means that EET removes +noise from scenario comparisons. + +Note that the both MC and EET are simulating the same model, so individual runs with identical +inputs but varying global seed will lead to the same statistical results for individual +output metrics. EET's properties become apparent when comparing two model runs with different +inputs. Because error terms are aligned, the variance of the estimator of the indicator, e.g., +mode choice shift or VMT difference, is reduced. In other words, difference metrics are more +precise estimators under EET. + +In mathematical terms, for any two metrics $X$ (baseline) and $Y$ (scenario), the variance +of the difference $X - Y$ is + +$$\text{Var}(X - Y) = \text{Var}(X) + \text{Var}(Y) - 2,\text{Cov}(X, Y)$$ + +EET deliberately drives $\text{Cov}(X, Y)$ up by aligning error terms, so $\text{Var}(X-Y)$ +collapses even though $\text{Var}(X)$ and $\text{Var}(Y)$ individually are unchanged. + +In practice, models are often run once for each scenario. EET is still usefull because the +lower the noise of the estimator, the higher the chance that a single run is representative. +In other words, the noise level of comparison metrics is lower. Additionally, under MC small +but real benefits can show up as negative in a single run. Under EET, the sign of the effect +is far more trustworthy. + +Independent of any statistical argument, under EET, choice changes between two runs are +causally attributable to utility changes which can be helpful for model development, +sensitivity testing, and defending results to stakeholders. + +### Aligning error terms + +Aligning error terms between runs is essential. This is intimately tied to how random numbers are generated; see {ref}`random_in_detail` for the underlying -random-number stream design and the `activitysim.core.random` API. In essence, keeping the -global random number generator seed constant for comparison runs is essential. This also means -that it is advisable to use the same setting in all runs. Comparing a baseline -run with EET to a scenario run without EET mixes two simulation methods and can make differences -harder to interpret. Aggregate choice patterns should remain statistically the same -as for the default probability-based method. - -Because unchanged alternatives can keep the same unobserved draws, changes to choices between -scenarios can only happen when the observed utility of an alternative increases. This is not -the case for the Monte Carlo simulation method, where the draws are based on probabilities, -which necessarily change for all alternatives if any observed utility changes. This combined -with sensitivity to small differences in the final CDF draw when comparing nearby scenarios -means that EET is a good candidate to remove noise from scenario comparisons. +random-number stream design and the `activitysim.core.random` API. It boils down to +each chooser needing to have the same ID between scenarios, and all alternatives being +reproduciably ordered. + +For chooser alignment, it is necessary that person and household IDs are stable between runs. +When running a scenario with population changes, it is important to only change the IDs of +those households and persons that have changed, e.g., new households. + +For alternative alignment, it is important to know the universal choice set, i.e., all possible +alternatives, for each model. For example, when running scenarios where a new mode is introduced, +this new mode should also be in the specification of the run where it is not available, with +its utility specification such that it is never chosen. In case the model is nested logit, the +nesting structure also needs to be held constant across scenarios. +For location choice models, all alternatives need to be listed in the land use table and the +zone IDs need to be stable between scenarios. Additionally, for computational efficiency +EET requires +0-based, contiguous zone IDs. For models where this is not the case, ActivitySim can +automatically perform the conversion for internal calculations, see +{ref}`explicit_error_terms_zone_encoding` for how to set this up. + +For models that use sub-sampling of alternatives, it is important to keep the sampling scheme +identical between scenarios, otherwise the error terms for the choice from the sampled set are +not guaranteed to be aligned. + +Finally, it also important to keep the global random number generator seed constant for +comparison runs. + ### Runtime and memory usage EET draws one error term per chooser and alternative, which requires many more random numbers @@ -79,7 +128,8 @@ per demand model run. However, when run in combination with an assignment model system converges faster and can cancel out any runtime penalty completely. Precise numbers are hard to provide, but overall runtime and memory usage should not differ from runs with MC too much. -For location choice models, keeping error terms aligned to zone IDs also affects runtime and + + ## Implementation Details and Adding New Models diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 3a12b5169a..4c5eb5085d 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -301,7 +301,7 @@ noise when comparing scenarios and can make some comparisons easier to interpre selected alternative is the one with the highest total utility after adding the explicit error term, and if the explicit error term is consistent between a base and scenario run then only (relative) increases in the observed utility can lead to previously un-chosen alternatives -being chosen. For more details, see :doc:`/dev-guide/explicit-error-terms`. +being chosen. To enable EET for a model run, set the global switch in ``settings.yaml``: @@ -309,4 +309,5 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: use_explicit_error_terms: True -Enable or disable this setting consistently across all runs being compared. +Enable or disable this setting consistently across all runs being compared. For more details, including +scenario comparison considerations and implementation notes, see :doc:`/dev-guide/explicit-error-terms`. From a6ff4591df1fafa8a3633e8352440b3f5e238ef5 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 13 May 2026 21:59:18 +1000 Subject: [PATCH 248/298] no op if no alts_context --- .../core/interaction_sample_simulate.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index b9d6134471..25752994a5 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -263,29 +263,34 @@ def _interaction_sample_simulate( # insert the zero-prob utilities to pad each alternative set to same size padded_utilities = np.insert(interaction_utilities.utility.values, inserts, -999) - padded_alt_nrs = np.insert(interaction_df[choice_column], inserts, -999) chunk_sizer.log_df(trace_label, "padded_utilities", padded_utilities) + # reshape to array with one row per chooser, one column per alternative + padded_utilities = padded_utilities.reshape(-1, max_sample_count) + + if alts_context is not None: + padded_alt_nrs = np.insert(interaction_df[choice_column], inserts, -999) + chunk_sizer.log_df(trace_label, "padded_alt_nrs", padded_alt_nrs) + padded_alt_nrs = padded_alt_nrs.reshape(-1, max_sample_count) + # alt_nrs_df has columns for each alt in the choice set, with values indicating which alt_id + # they correspond to (as opposed to the 0-n index implied by the column number). + alt_nrs_df = pd.DataFrame(padded_alt_nrs, index=choosers.index) + chunk_sizer.log_df(trace_label, "alt_nrs_df", alt_nrs_df) + + del padded_alt_nrs + chunk_sizer.log_df(trace_label, "padded_alt_nrs", None) + else: + alt_nrs_df = None # if we don't provide the number of dense alternatives, assume that we'll use the old approach + del interaction_df chunk_sizer.log_df(trace_label, "interaction_df", None) del inserts - # reshape to array with one row per chooser, one column per alternative - padded_utilities = padded_utilities.reshape(-1, max_sample_count) - padded_alt_nrs = padded_alt_nrs.reshape(-1, max_sample_count) - # convert to a dataframe with one row per chooser and one column per alternative utilities_df = pd.DataFrame(padded_utilities, index=choosers.index) chunk_sizer.log_df(trace_label, "utilities_df", utilities_df) - # alt_nrs_df has columns for each alt in the choice set, with values indicating which alt_id - # they correspond to (as opposed to the 0-n index implied by the column number). - if alts_context is not None: - alt_nrs_df = pd.DataFrame(padded_alt_nrs, index=choosers.index) - else: - alt_nrs_df = None # if we don't provide the number of dense alternatives, assume that we'll use the old approach - del padded_utilities chunk_sizer.log_df(trace_label, "padded_utilities", None) @@ -339,6 +344,10 @@ def _interaction_sample_simulate( del utilities_df chunk_sizer.log_df(trace_label, "utilities_df", None) + + if alt_nrs_df is not None: + del alt_nrs_df + chunk_sizer.log_df(trace_label, "alt_nrs_df", None) else: # convert to probabilities (utilities exponentiated and normalized to probs) # probs is same shape as utilities, one row per chooser and one column for alternative From 7aaeb1bf4bc9b5935445a531707a40be40cf7d1e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 13 May 2026 22:30:16 +1000 Subject: [PATCH 249/298] do not throw on interaction_sample_simulate w/o alts_context --- activitysim/abm/models/location_choice.py | 11 +++++++---- .../abm/models/util/vectorize_tour_scheduling.py | 12 ++++++++---- activitysim/core/interaction_sample_simulate.py | 7 ++++--- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 7a1857cbaa..ff6e8f1324 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -790,7 +790,6 @@ def run_location_choice( chunk_tag, trace_label, skip_choice=False, - alts_context: AltsContext | None = None, ): """ Run the three-part location choice algorithm to generate a location choice for each chooser @@ -844,9 +843,13 @@ def run_location_choice( if choosers.shape[0] == 0: logger.info(f"{trace_label} skipping segment {segment_name}: no choosers") continue - # dest_size_terms contains 0-attraction zones so using this directly here, important for stable error terms - # when a zone goes from 0 base -> nonzero project - alts_context = AltsContext.from_series(dest_size_terms.index) + + if state.settings.use_explicit_error_terms: + # dest_size_terms contains 0-attraction zones so using this directly here, important for stable error terms + # when a zone goes from 0 base -> nonzero project + alts_context = AltsContext.from_series(dest_size_terms.index) + else: + alts_context = None # - location_sample location_sample_df = run_location_sample( diff --git a/activitysim/abm/models/util/vectorize_tour_scheduling.py b/activitysim/abm/models/util/vectorize_tour_scheduling.py index 0666bf2c8c..2d5ec5c455 100644 --- a/activitysim/abm/models/util/vectorize_tour_scheduling.py +++ b/activitysim/abm/models/util/vectorize_tour_scheduling.py @@ -850,9 +850,13 @@ def _schedule_tours( estimator.write_interaction_sample_alternatives(alt_tdd) log_alt_losers = state.settings.log_alt_losers - # use full TDD alternatives index to ensure AltsContext spans full range of potential slots - tdd_alts = state.get_injectable("tdd_alts") - alts_context = AltsContext.from_series(tdd_alts.index) + + if state.settings.use_explicit_error_terms: + # use full TDD alternatives index to ensure AltsContext spans full range of potential slots + tdd_alts = state.get_injectable("tdd_alts") + alts_context = AltsContext.from_series(tdd_alts.index) + else: + alts_context = None choices = interaction_sample_simulate( state, @@ -973,7 +977,7 @@ def schedule_tours( if len(result_list) > 1: choices = pd.concat(result_list) - assert len(choices.index == len(tours.index)) + assert len(choices.index) == len(tours.index) return choices diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 25752994a5..0202fc986f 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -551,9 +551,10 @@ def interaction_sample_simulate( if state.settings.use_explicit_error_terms: choice_ids_are_int = pd.api.types.is_integer_dtype(alternatives[choice_column]) if alts_context is None and choice_ids_are_int: - raise ValueError( - "alts_context is required for interaction_sample_simulate when " - "use_explicit_error_terms is True and choice_column is integer-coded" + logger.warning( + "Using integer-coded choice_column values without alts_context when use_explicit_error_terms is true." + + " Ensure this is desired, when running on a sample it should be provided to ensure consistent random" + + " numbers across the whole alternative set." ) if alts_context is not None and not choice_ids_are_int: raise ValueError( From e9a281323ba1731aabc0b228615cf8260fa33240 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 13 May 2026 23:25:19 +1000 Subject: [PATCH 250/298] eet special casing --- .../abm/models/util/tour_destination.py | 25 +++++++++++++------ .../test_tour_destination_sampling.py | 12 ++++++++- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 9aa498d17c..424da8c147 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -618,12 +618,16 @@ def destination_presample( MAZ_size_terms, TAZ_size_terms = aggregate_size_terms( destination_size_terms, network_los ) - full_taz_index = pd.Index( - network_los.map_maz_to_taz(full_destination_size_terms.index), name=DEST_TAZ - ) - full_taz_index = full_taz_index[~full_taz_index.duplicated()] - stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) - assert (stable_alt_positions >= 0).all() + if state.settings.use_explicit_error_terms: + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_destination_size_terms.index), name=DEST_TAZ + ) + full_taz_index = full_taz_index[~full_taz_index.duplicated()] + stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) + assert (stable_alt_positions >= 0).all() + else: + full_taz_index = None + stable_alt_positions = None orig_maz = model_settings.CHOOSER_ORIG_COL_NAME assert orig_maz in choosers @@ -649,12 +653,17 @@ def destination_presample( trace_label=trace_label, zone_layer="taz", stable_alt_positions=stable_alt_positions, - n_total_alts=len(full_taz_index), + n_total_alts=len(full_taz_index) if full_taz_index is not None else 0, ) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total maz_choices = choose_MAZ_for_TAZ( - state, taz_sample, MAZ_size_terms, trace_label, model_settings + state, + taz_sample, + MAZ_size_terms, + trace_label, + model_settings, + full_taz_index=full_taz_index, ) assert DEST_MAZ in maz_choices diff --git a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py index 22c0d00b4b..2b8a513dd8 100644 --- a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py @@ -85,8 +85,14 @@ def fake_destination_sample( ) def fake_choose_maz_for_taz( - _state, _taz_sample, _maz_size_terms, _trace_label, _model_settings + _state, + _taz_sample, + _maz_size_terms, + _trace_label, + _model_settings, + full_taz_index=None, ): + captured["full_taz_index"] = full_taz_index return pd.DataFrame( {tour_destination.DEST_MAZ: [101]}, index=pd.Index([7001], name="tour_id"), @@ -145,6 +151,10 @@ def fake_choose_maz_for_taz( assert captured["zone_layer"] == "taz" assert captured["n_total_alts"] == 3 assert list(captured["stable_alt_positions"]) == [0, 2] + pd.testing.assert_index_equal( + captured["full_taz_index"], + pd.Index([1, 2, 3], name=tour_destination.DEST_TAZ), + ) def test_choose_maz_for_taz_supports_variable_taz_counts(): From 6d0a24e87c90b2aaac1d733212f3c3a537d5eb4c Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 13 May 2026 23:48:27 +1000 Subject: [PATCH 251/298] tour_od with maz_taz stable, clean up --- .../abm/models/util/tour_destination.py | 12 +- activitysim/abm/models/util/tour_od.py | 72 ++++-- .../test/test_misc/test_tour_od_sampling.py | 212 ++++++++++++++++++ 3 files changed, 279 insertions(+), 17 deletions(-) create mode 100644 activitysim/abm/test/test_misc/test_tour_od_sampling.py diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 424da8c147..e227044a99 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -941,10 +941,14 @@ def run_destination_simulate( state.tracing.dump_df(DUMP, choosers, trace_label, "choosers") log_alt_losers = state.settings.log_alt_losers - # use full land_use index to ensure AltsContext spans full range of potential destinations - # (maintains stable random number generation even if zones flip zero/non-zero size) - land_use = state.get_dataframe("land_use") - alts_context = AltsContext.from_series(land_use.index) + + if state.settings.use_explicit_error_terms: + # use full land_use index to ensure AltsContext spans full range of potential destinations + # (maintains stable random number generation even if zones flip zero/non-zero size) + land_use = state.get_dataframe("land_use") + alts_context = AltsContext.from_series(land_use.index) + else: + alts_context = None choices = interaction_sample_simulate( state, diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 5ec9dd493f..5892c9ea1b 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -345,6 +345,7 @@ def choose_MAZ_for_TAZ( trace_label, addtl_col_for_unique_key=None, dest_maz_id_col=DEST_MAZ, + full_taz_index=None, ): """ Convert taz_sample table with TAZ zone sample choices to a table with a MAZ zone chosen for each TAZ @@ -423,17 +424,24 @@ def choose_MAZ_for_TAZ( # for random_for_df, we need df with de-duplicated chooser canonical index chooser_df = pd.DataFrame(index=taz_sample.index[~taz_sample.index.duplicated()]) - num_choosers = len(chooser_df) assert chooser_df.index.name == chooser_id_col - # to make choices, rands for each chooser (one rand for each sampled TAZ) - # taz_sample_size will be model_settings['SAMPLE_SIZE'] samples, except if we are estimating - taz_sample_size = taz_choices.groupby(chooser_id_col)[DEST_TAZ].count().max() + # to make choices, draw enough rands for the chooser with the largest TAZ sample, + # then keep only the draws corresponding to actual TAZ rows for each chooser. + taz_choice_counts = ( + taz_choices.groupby(chooser_id_col)[DEST_TAZ] + .count() + .reindex(chooser_df.index) + .astype(np.int64) + ) + taz_sample_size = taz_choice_counts.max() + uniform_taz_choice_counts = (taz_choice_counts == taz_sample_size).all() - # taz_choices index values should be contiguous - assert ( - taz_choices[chooser_id_col] == np.repeat(chooser_df.index, taz_sample_size) - ).all() + # taz_choices rows should remain grouped by chooser in chooser_df order + expected_chooser_ids = np.repeat( + chooser_df.index.to_numpy(), taz_choice_counts.to_numpy() + ) + assert (taz_choices[chooser_id_col].to_numpy() == expected_chooser_ids).all() # we need to choose a MAZ for each DEST_TAZ choice # probability of choosing MAZ based on MAZ size_term fraction of TAZ total @@ -493,11 +501,33 @@ def choose_MAZ_for_TAZ( # prob array with one row TAZ_choice, one column per alternative row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - assert maz_probs.shape == (num_choosers * taz_sample_size, max_maz_count) - - rands = state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) - rands = rands.reshape(-1, 1) - assert len(rands) == num_choosers * taz_sample_size + if full_taz_index is not None: + full_taz_index = pd.Index(full_taz_index, name=DEST_TAZ) + taz_positions = full_taz_index.get_indexer(taz_choices[DEST_TAZ]) + assert (taz_positions >= 0).all() + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df(chooser_df, n=len(full_taz_index)) + ) + chooser_row_positions = np.repeat( + np.arange(len(chooser_df)), taz_choice_counts.to_numpy() + ) + rands = chooser_rands[chooser_row_positions, taz_positions].reshape(-1, 1) + assert len(rands) == len(taz_choices) + elif uniform_taz_choice_counts: + assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) + rands = state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) + rands = rands.reshape(-1, 1) + assert len(rands) == len(chooser_df) * taz_sample_size + else: + assert maz_probs.shape == (len(taz_choices), max_maz_count) + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) + ) + chooser_rand_mask = ( + np.arange(taz_sample_size) < taz_choice_counts.to_numpy()[:, np.newaxis] + ) + rands = chooser_rands[chooser_rand_mask].reshape(-1, 1) + assert len(rands) == len(taz_choices) assert len(rands) == maz_probs.shape[0] # make choices @@ -600,6 +630,7 @@ def od_presample( model_settings: TourODSettings, network_los, destination_size_terms, + full_destination_size_terms, estimator, chunk_size, trace_label, @@ -614,6 +645,13 @@ def od_presample( MAZ_size_terms, TAZ_size_terms = aggregate_size_terms( destination_size_terms, network_los ) + if state.settings.use_explicit_error_terms: + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_destination_size_terms.index), name=DEST_TAZ + ) + full_taz_index = full_taz_index[~full_taz_index.duplicated()] + else: + full_taz_index = None # create wrapper with keys for this lookup - in this case there is a ORIG_TAZ # in the choosers and a DEST_TAZ in the alternatives which get merged during @@ -654,6 +692,7 @@ def od_presample( MAZ_size_terms, trace_label, addtl_col_for_unique_key=ORIG_MAZ, + full_taz_index=full_taz_index, ) # outputs @@ -675,6 +714,7 @@ def run_od_sample( model_settings: TourODSettings, network_los, destination_size_terms, + full_destination_size_terms, estimator, chunk_size, trace_label, @@ -722,6 +762,7 @@ def run_od_sample( model_settings, network_los, destination_size_terms, + full_destination_size_terms, estimator, chunk_size, trace_label, @@ -1093,6 +1134,10 @@ def run_tour_od( segment_destination_size_terms = size_term_calculator.dest_size_terms_df( segment_name, trace_label ) + full_segment_destination_size_terms = ( + size_term_calculator.destination_size_terms[[segment_name]].copy() + ) + full_segment_destination_size_terms.columns = ["size_term"] if choosers.shape[0] == 0: logger.info( @@ -1110,6 +1155,7 @@ def run_tour_od( model_settings, network_los, segment_destination_size_terms, + full_segment_destination_size_terms, estimator, chunk_size=chunk_size, trace_label=tracing.extend_trace_label( diff --git a/activitysim/abm/test/test_misc/test_tour_od_sampling.py b/activitysim/abm/test/test_misc/test_tour_od_sampling.py new file mode 100644 index 0000000000..7a9c293b20 --- /dev/null +++ b/activitysim/abm/test/test_misc/test_tour_od_sampling.py @@ -0,0 +1,212 @@ +from __future__ import annotations + +from types import SimpleNamespace + +import numpy as np +import pandas as pd + +from activitysim.abm.models.util import tour_od +from activitysim.core import workflow + + +class _DummySkimDict: + def wrap(self, orig_key, dest_key): + return type("WrappedSkims", (), {"orig_key": orig_key, "dest_key": dest_key})() + + +class _DummyNetworkLos: + zone_system = 2 + + def __init__(self, maz_to_taz): + self._maz_to_taz = maz_to_taz + + def map_maz_to_taz(self, maz_index): + return pd.Index([self._maz_to_taz[maz] for maz in maz_index], name="TAZ") + + def get_skim_dict(self, layer): + assert layer == "taz" + return _DummySkimDict() + + +class _DummyRng: + def __init__(self, draws): + self._draws = np.asarray(draws) + self.calls = [] + + def random_for_df(self, df, n): + self.calls.append(n) + assert self._draws.shape == (len(df), n) + return self._draws.copy() + + +class _DummyState: + @staticmethod + def make(draws, use_explicit_error_terms=False): + state = workflow.State().default_settings() + state.settings.trace_hh_id = None + state.settings.use_explicit_error_terms = use_explicit_error_terms + rng = _DummyRng(draws) + state._dummy_rng = rng + state.get_rn_generator = lambda: rng + return state + + +def test_od_presample_passes_full_taz_index_for_eet(monkeypatch): + captured = {} + + def fake_od_sample( + _state, + _spec_segment_name, + _choosers, + _network_los, + destination_size_terms, + _origin_id_col, + _dest_id_col, + _skims, + _estimator, + _model_settings, + alt_od_col_name, + _chunk_size, + chunk_tag, + trace_label, + ): + captured["active_taz_index"] = destination_size_terms.index.copy() + captured["alt_od_col_name"] = alt_od_col_name + captured["chunk_tag"] = chunk_tag + captured["trace_label"] = trace_label + return pd.DataFrame( + { + alt_od_col_name: ["101_1", "101_3"], + "prob": [0.5, 0.25], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ) + + def fake_choose_maz_for_taz( + _state, + _taz_sample, + _maz_size_terms, + _trace_label, + addtl_col_for_unique_key=None, + dest_maz_id_col=tour_od.DEST_MAZ, + full_taz_index=None, + ): + captured["addtl_col_for_unique_key"] = addtl_col_for_unique_key + captured["dest_maz_id_col"] = dest_maz_id_col + captured["full_taz_index"] = full_taz_index + return pd.DataFrame( + { + dest_maz_id_col: [101], + tour_od.ORIG_MAZ: [101], + "prob": [0.5], + "pick_count": [1], + }, + index=pd.Index([7001], name="tour_id"), + ) + + monkeypatch.setattr(tour_od, "_od_sample", fake_od_sample) + monkeypatch.setattr(tour_od, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) + + state = workflow.State().default_settings() + state.settings.use_explicit_error_terms = True + choosers = pd.DataFrame( + {tour_od.ORIG_TAZ: [1]}, + index=pd.Index([7001], name="tour_id"), + ) + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "alt_dest", + "CHOOSER_ORIG_COL_NAME": "origin", + }, + )() + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + active_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 2.0]}, + index=pd.Index([101, 103], name="zone_id"), + ) + full_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 0.0, 2.0]}, + index=pd.Index([101, 102, 103], name="zone_id"), + ) + + out = tour_od.od_presample( + state, + "segment", + choosers, + model_settings, + network_los, + active_destination_size_terms, + full_destination_size_terms, + estimator=None, + chunk_size=0, + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + {"alt_dest": [101], "origin": [101], "prob": [0.5], "pick_count": [1]}, + index=pd.Index([7001], name="tour_id"), + ), + ) + pd.testing.assert_index_equal( + captured["active_taz_index"], + pd.Index([1, 3], name=tour_od.DEST_TAZ), + ) + assert captured["alt_od_col_name"] == tour_od.get_od_id_col( + tour_od.ORIG_MAZ, tour_od.DEST_TAZ + ) + assert captured["chunk_tag"] == "tour_od.presample" + assert captured["addtl_col_for_unique_key"] == tour_od.ORIG_MAZ + pd.testing.assert_index_equal( + captured["full_taz_index"], + pd.Index([1, 2, 3], name=tour_od.DEST_TAZ), + ) + + +def test_choose_maz_for_taz_eet_uses_full_taz_positions_with_origin_key(): + state = _DummyState.make([[0.99, 0.2, 0.99, 0.99, 0.8]]) + + taz_sample = pd.DataFrame( + { + tour_od.DEST_TAZ: [2, 5], + "prob": [0.5, 0.25], + "pick_count": [1, 1], + tour_od.ORIG_MAZ: [9001, 9001], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ) + maz_size_terms = pd.DataFrame( + { + "zone_id": [201, 202, 501, 502], + tour_od.DEST_TAZ: [2, 2, 5, 5], + "size_term": [3.0, 1.0, 3.0, 1.0], + } + ) + + out = tour_od.choose_MAZ_for_TAZ( + state, + taz_sample, + maz_size_terms, + "test_trace", + addtl_col_for_unique_key=tour_od.ORIG_MAZ, + full_taz_index=pd.Index([1, 2, 3, 4, 5], name=tour_od.DEST_TAZ), + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + { + tour_od.DEST_MAZ: [201, 502], + tour_od.ORIG_MAZ: [9001, 9001], + "prob": [0.375, 0.0625], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ), + ) + assert state.get_rn_generator().calls == [5] From 5b0c79eb08ddc07a50aa5a27b5952e2c4596f33a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 13 May 2026 23:57:06 +1000 Subject: [PATCH 252/298] arc eet golden trips --- .../test/regress/final_trips_eet.csv | 149 ++++++++---------- 1 file changed, 67 insertions(+), 82 deletions(-) diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv b/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv index ca0a19fb4c..629cd63276 100644 --- a/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv +++ b/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv @@ -1,84 +1,69 @@ "person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","depart","trip_mode","mode_choice_logsum","trip_id" -113762,42730,"work",1,true,2,102,103,4664281,"escort",11.620562271430003,11,"SHARED2FREE",0.1984450162064936,37314249 -113762,42730,"work",2,true,2,106,102,4664281,"work",,12,"DRIVEALONEFREE",0.1174194651741796,37314250 -113762,42730,"work",1,false,2,101,106,4664281,"shopping",11.71157893264538,31,"SHARED3FREE",0.16362355684245977,37314253 -113762,42730,"work",2,false,2,103,101,4664281,"home",,32,"DRIVEALONEFREE",-0.02897760267828356,37314254 -116448,43843,"work",1,true,2,105,103,4774407,"othmaint",9.341245249202924,10,"DRIVEALONEFREE",-0.657585671377182,38195257 -116448,43843,"work",2,true,2,101,105,4774407,"work",,15,"DRIVEALONEFREE",-0.5883466045856476,38195258 -116448,43843,"work",1,false,2,123,101,4774407,"othmaint",9.123561456324088,25,"DRIVEALONEFREE",-0.9421370327949524,38195261 -116448,43843,"work",2,false,2,103,123,4774407,"home",,29,"DRIVEALONEFREE",-0.7305148571968079,38195262 -116450,43843,"school",1,true,2,106,103,4774481,"escort",11.70931236960696,10,"SHARED3FREE",0.06412222350024642,38195849 -116450,43843,"school",2,true,2,106,106,4774481,"school",,10,"WALK",0.1346124978975689,38195850 -116450,43843,"school",1,false,1,103,106,4774481,"home",,26,"SHARED3FREE",0.06400167836417262,38195853 -120774,45311,"atwork",1,true,2,122,123,4951738,"work",11.450943959940382,18,"DRIVEALONEFREE",-0.481071140050888,39613905 -120774,45311,"atwork",2,true,2,122,122,4951738,"atwork",,18,"DRIVEALONEFREE",-0.3282438698291779,39613906 -120774,45311,"atwork",1,false,1,123,122,4951738,"work",,21,"DRIVEALONEFREE",-0.47753682303428663,39613909 -120774,45311,"work",1,true,1,123,105,4951773,"work",,8,"DRIVEALONEFREE",-0.5730820206642151,39614185 -120774,45311,"work",1,false,1,105,123,4951773,"home",,29,"DRIVEALONEFREE",-0.5845674780845644,39614189 -120775,45311,"shopping",1,true,1,104,105,4951808,"shopping",,19,"DRIVEALONEFREE",-0.5170302743911744,39614465 -120775,45311,"shopping",1,false,1,105,104,4951808,"home",,26,"DRIVEALONEFREE",-0.526026701450348,39614469 -120775,45311,"work",1,true,1,106,105,4951814,"work",,11,"DRIVEALONEFREE",-0.4328329442501069,39614513 -120775,45311,"work",1,false,1,105,106,4951814,"home",,19,"DRIVEALONEFREE",-0.4224589346408844,39614517 -123132,46056,"atwork",1,true,2,106,106,5048416,"othdiscr",5.736908684681538,20,"WALK",-1.0410120487213135,40387329 -123132,46056,"atwork",2,true,2,101,106,5048416,"atwork",,21,"WALK",-1.4049548953771591,40387330 -123132,46056,"atwork",1,false,1,106,101,5048416,"work",,22,"WALK",-1.4049548953771591,40387333 -123132,46056,"work",1,true,2,100,106,5048451,"social",12.992372530395093,11,"DRIVEALONEFREE",-0.056856693416576155,40387609 -123132,46056,"work",2,true,2,106,100,5048451,"work",,11,"SHARED2FREE",-0.06978704502587849,40387610 -123132,46056,"work",1,false,4,106,106,5048451,"othmaint",11.571615647229482,30,"DRIVEALONEFREE",0.2365447098181202,40387613 -123132,46056,"work",2,false,4,106,106,5048451,"othmaint",12.148331580258795,30,"SHARED2FREE",0.2365447098181202,40387614 -123132,46056,"work",3,false,4,106,106,5048451,"eatout",11.338764994103533,30,"DRIVEALONEFREE",0.2365447098181202,40387615 -123132,46056,"work",4,false,4,106,106,5048451,"home",,35,"SHARED2FREE",0.23932365335602357,40387616 -136983,50912,"atwork",1,true,1,123,123,5616307,"atwork",,19,"WALK",-1.127763032913208,44930457 -136983,50912,"atwork",1,false,2,123,123,5616307,"othmaint",6.824969769883364,19,"WALK",-1.127763032913208,44930461 -136983,50912,"atwork",2,false,2,123,123,5616307,"work",,21,"WALK",-1.127763032913208,44930462 -136983,50912,"work",1,true,1,123,112,5616342,"work",,16,"SHARED3FREE",-0.020760706766682024,44930737 -136983,50912,"work",1,false,4,104,123,5616342,"eatout",11.284613956027043,23,"SHARED3FREE",-0.07389233569315362,44930741 -136983,50912,"work",2,false,4,103,104,5616342,"work",12.6148435779502,23,"SHARED3FREE",0.05220751420566755,44930742 -136983,50912,"work",3,false,4,104,103,5616342,"eatout",10.989757771316299,23,"DRIVEALONEFREE",0.053315758831313835,44930743 -136983,50912,"work",4,false,4,112,104,5616342,"home",,30,"DRIVEALONEFREE",-0.03395123851246563,44930744 -136984,50912,"atwork",1,true,1,111,114,5616344,"atwork",,19,"DRIVEALONEFREE",-0.5068167327404024,44930753 -136984,50912,"atwork",1,false,1,114,111,5616344,"work",,20,"DRIVEALONEFREE",-0.50456764087677,44930757 -136984,50912,"atwork",1,true,1,114,114,5616348,"atwork",,20,"DRIVEALONEFREE",-0.4243787014007569,44930785 -136984,50912,"atwork",1,false,1,114,114,5616348,"work",,21,"DRIVEALONEFREE",-0.4243787014007569,44930789 -136984,50912,"work",1,true,1,114,112,5616383,"work",,17,"DRIVEALONEFREE",-0.5196855528831482,44931065 -136984,50912,"work",1,false,1,112,114,5616383,"home",,32,"DRIVEALONEFREE",-0.524872527885437,44931069 -145188,53716,"othdiscr",1,true,2,103,116,5952733,"eatout",8.095380549568242,26,"SHARED2FREE",-0.656344788392591,47621865 -145188,53716,"othdiscr",2,true,2,106,103,5952733,"othdiscr",,28,"SHARED3FREE",0.01085078798134025,47621866 -145188,53716,"othdiscr",1,false,1,116,106,5952733,"home",,39,"SHARED3FREE",-0.5586695841155525,47621869 -145188,53716,"shopping",1,true,2,121,116,5952741,"escort",10.284349421053356,24,"DRIVEALONEFREE",0.007844892145818317,47621929 -145188,53716,"shopping",2,true,2,122,121,5952741,"shopping",,24,"DRIVEALONEFREE",-0.09430664622810597,47621930 +116448,43843,"work",1,true,2,103,103,4774407,"othmaint",3.5265435605629167,10,"WALK",-0.9040200710296631,38195257 +116448,43843,"work",2,true,2,103,103,4774407,"work",,15,"WALK",-0.9040200710296631,38195258 +116448,43843,"work",1,false,2,103,103,4774407,"othmaint",3.537335211563138,25,"WALK",-0.9040200710296631,38195261 +116448,43843,"work",2,false,2,103,103,4774407,"home",,29,"WALK",-0.9040200710296631,38195262 +116449,43843,"work",1,true,1,119,103,4774448,"work",,10,"SHARED2FREE",-0.6957122303793248,38195585 +116449,43843,"work",1,false,1,103,119,4774448,"home",,32,"DRIVEALONEFREE",-0.7307911775419529,38195589 +116450,43843,"school",1,true,2,124,103,4774481,"escort",7.408917184315112,10,"SHARED3FREE",-0.18503856868335017,38195849 +116450,43843,"school",2,true,2,124,124,4774481,"school",,10,"SHARED2FREE",0.13215486408443475,38195850 +116450,43843,"school",1,false,1,103,124,4774481,"home",,26,"SHARED3FREE",-0.19368817121573698,38195853 +120774,45311,"atwork",1,true,2,107,111,4951738,"work",7.856506518743053,18,"SHARED2FREE",-0.3133887920340869,39613905 +120774,45311,"atwork",2,true,2,101,107,4951738,"atwork",,18,"SHARED2FREE",-0.4146300033565298,39613906 +120774,45311,"atwork",1,false,1,111,101,4951738,"work",,21,"SHARED2FREE",-0.47849444235549743,39613909 +120774,45311,"work",1,true,1,111,105,4951773,"work",,8,"SHARED3FREE",0.15709575819640942,39614185 +120774,45311,"work",1,false,1,105,111,4951773,"home",,29,"DRIVEALONEFREE",0.15635853653623902,39614189 +120775,45311,"shopping",1,true,1,101,105,4951808,"shopping",,19,"DRIVEALONEFREE",-0.5635545263290407,39614465 +120775,45311,"shopping",1,false,1,105,101,4951808,"home",,26,"DRIVEALONEFREE",-0.5825112173557282,39614469 +120775,45311,"work",1,true,1,122,105,4951814,"work",,11,"DRIVEALONEFREE",-0.6927164334297179,39614513 +120775,45311,"work",1,false,1,105,122,4951814,"home",,19,"DRIVEALONEFREE",-0.6686710088729859,39614517 +123132,46056,"atwork",1,true,2,101,101,5048416,"othdiscr",6.864256935993293,20,"SHARED2FREE",-0.1819504659032103,40387329 +123132,46056,"atwork",2,true,2,101,101,5048416,"atwork",,21,"SHARED2FREE",-0.1819504659032103,40387330 +123132,46056,"atwork",1,false,1,101,101,5048416,"work",,22,"SHARED2FREE",-0.1819504659032103,40387333 +123132,46056,"work",1,true,2,101,106,5048451,"social",9.575912097245201,11,"DRIVEALONEFREE",0.06386075688245799,40387609 +123132,46056,"work",2,true,2,101,101,5048451,"work",,11,"SHARED2FREE",0.07747391487776138,40387610 +123132,46056,"work",1,false,4,101,101,5048451,"othmaint",7.912927519210828,30,"DRIVEALONEFREE",0.08966218523375064,40387613 +123132,46056,"work",2,false,4,101,101,5048451,"othmaint",7.8924110313009,30,"SHARED2FREE",0.08966218523375064,40387614 +123132,46056,"work",3,false,4,101,101,5048451,"eatout",7.830041437016489,30,"SHARED2FREE",0.08966218523375064,40387615 +123132,46056,"work",4,false,4,106,101,5048451,"home",,35,"SHARED2FREE",0.06386075688245799,40387616 +136983,50912,"atwork",1,true,1,132,128,5616307,"atwork",,19,"DRIVEALONEFREE",-0.5008556391239165,44930457 +136983,50912,"atwork",1,false,1,128,132,5616307,"work",,21,"DRIVEALONEFREE",-0.5011769677639006,44930461 +136983,50912,"work",1,true,1,128,112,5616342,"work",,8,"DRIVEALONEFREE",-1.3108586753845215,44930737 +136983,50912,"work",1,false,4,104,128,5616342,"eatout",4.376898428790465,26,"DRIVEALONEFREE",-1.4459948724746707,44930741 +136983,50912,"work",2,false,4,114,104,5616342,"work",7.30416619728054,26,"DRIVEALONEFREE",-0.645403600692749,44930742 +136983,50912,"work",3,false,4,107,114,5616342,"eatout",5.801076923906916,26,"DRIVEALONEFREE",-0.7406740243911744,44930743 +136983,50912,"work",4,false,4,112,107,5616342,"home",,31,"DRIVEALONEFREE",-0.4455707152366638,44930744 +136984,50912,"atwork",1,true,1,100,104,5616344,"atwork",,19,"DRIVEALONEFREE",-0.5630687888145448,44930753 +136984,50912,"atwork",1,false,1,104,100,5616344,"work",,20,"DRIVEALONEFREE",-0.569428646659851,44930757 +136984,50912,"atwork",1,true,1,122,104,5616348,"atwork",,20,"DRIVEALONEFREE",-0.7160756511688232,44930785 +136984,50912,"atwork",1,false,1,104,122,5616348,"work",,21,"DRIVEALONEFREE",-0.7157543225288391,44930789 +136984,50912,"work",1,true,1,104,112,5616383,"work",,17,"DRIVEALONEFREE",-0.5361950384140016,44931065 +136984,50912,"work",1,false,1,112,104,5616383,"home",,32,"DRIVEALONEFREE",-0.5499035404682159,44931069 +145188,53716,"othdiscr",1,true,1,121,116,5952733,"othdiscr",,26,"WALK",-1.9421684741973877,47621865 +145188,53716,"othdiscr",1,false,1,116,121,5952733,"home",,39,"WALK",-1.9421684741973877,47621869 +145188,53716,"shopping",1,true,2,104,116,5952741,"escort",6.936564611277708,24,"DRIVEALONEFREE",-0.35927561314773726,47621929 +145188,53716,"shopping",2,true,2,122,104,5952741,"shopping",,24,"DRIVEALONEFREE",-0.2772136905975358,47621930 145188,53716,"shopping",1,false,1,116,122,5952741,"home",,25,"DRIVEALONEFREE",-0.18333346470683046,47621933 -147129,54342,"atwork",1,true,2,118,118,6032293,"work",11.591261619486666,24,"DRIVEALONEFREE",-0.277168506860733,48258345 -147129,54342,"atwork",2,true,2,118,118,6032293,"atwork",,25,"DRIVEALONEFREE",-0.27748977589607243,48258346 -147129,54342,"atwork",1,false,1,118,118,6032293,"work",,27,"DRIVEALONEFREE",-0.27748977589607243,48258349 -147129,54342,"work",1,true,1,118,117,6032328,"work",,24,"DRIVEALONEFREE",-0.3512043696403504,48258625 -147129,54342,"work",1,false,2,117,118,6032328,"othmaint",9.222307568325903,45,"DRIVEALONEFREE",-0.3512043696403504,48258629 -147129,54342,"work",2,false,2,117,117,6032328,"home",,46,"DRIVEALONEFREE",-0.3278332107067109,48258630 -168909,62701,"othmaint",1,true,1,135,131,6925297,"othmaint",,25,"DRIVEALONEFREE",-0.1509559426724583,55402377 -168909,62701,"othmaint",1,false,1,131,135,6925297,"home",,28,"SHARED3FREE",-0.14873576359938484,55402381 -171822,63802,"atwork",1,true,1,128,130,7044702,"atwork",,17,"DRIVEALONEFREE",-0.5419102621078492,56357617 -171822,63802,"atwork",1,false,1,130,128,7044702,"work",,20,"DRIVEALONEFREE",-0.5419102621078492,56357621 -171822,63802,"shopping",1,true,1,135,135,7044721,"shopping",,29,"WALK",-0.7460586428642273,56357769 -171822,63802,"shopping",1,false,3,135,135,7044721,"othmaint",8.365401255841729,30,"WALK",-0.7460586428642273,56357773 -171822,63802,"shopping",2,false,3,135,135,7044721,"othmaint",8.36277534519151,30,"WALK",-0.7460586428642273,56357774 -171822,63802,"shopping",3,false,3,135,135,7044721,"home",,31,"WALK",-0.7460586428642273,56357775 -171822,63802,"work",1,true,1,130,135,7044741,"work",,12,"DRIVEALONEFREE",-0.4857847907543183,56357929 -171822,63802,"work",1,false,2,130,130,7044741,"shopping",9.965446400011412,24,"DRIVEALONEFREE",-0.45575206193923956,56357933 -171822,63802,"work",2,false,2,135,130,7044741,"home",,27,"DRIVEALONEFREE",-0.486525795698166,56357934 -171823,63802,"escort",1,true,1,135,135,7044752,"escort",,27,"SHARED2FREE",0.07706324792840326,56358017 -171823,63802,"escort",1,false,2,135,135,7044752,"othmaint",11.334171843971035,27,"DRIVEALONEFREE",0.07706324792840326,56358021 -171823,63802,"escort",2,false,2,135,135,7044752,"home",,27,"SHARED3FREE",0.07706324792840326,56358022 -171823,63802,"escort",1,true,1,135,135,7044753,"escort",,10,"SHARED2FREE",0.07768184479155238,56358025 -171823,63802,"escort",1,false,3,135,135,7044753,"escort",11.396653773741745,10,"SHARED3FREE",0.07768184479155238,56358029 -171823,63802,"escort",2,false,3,135,135,7044753,"eatout",11.163952601101887,10,"WALK",0.07768184479155238,56358030 -171823,63802,"escort",3,false,3,135,135,7044753,"home",,11,"SHARED2FREE",0.07768184479155238,56358031 -171824,63802,"school",1,true,1,135,135,7044815,"school",,10,"SHARED2FREE",-0.04192782886512184,56358521 -171824,63802,"school",1,false,1,135,135,7044815,"home",,25,"SHARED2FREE",-0.0421655910777971,56358525 -171825,63802,"school",1,true,2,135,135,7044856,"school",14.150222701978416,10,"SHARED3FREE",0.10569338295856193,56358849 -171825,63802,"school",2,true,2,135,135,7044856,"school",,11,"SHARED3FREE",0.10569338295856193,56358850 -171825,63802,"school",1,false,1,135,135,7044856,"home",,26,"SHARED3FREE",0.10545807870230886,56358853 -171826,63802,"school",1,true,3,135,135,7044897,"othmaint",11.076463602650021,8,"SHARED2FREE",-0.04192782886512184,56359177 -171826,63802,"school",2,true,3,135,135,7044897,"escort",11.412423645394568,9,"SHARED2FREE",-0.04192782886512184,56359178 -171826,63802,"school",3,true,3,135,135,7044897,"school",,9,"SHARED2FREE",-0.04192782886512184,56359179 -171826,63802,"school",1,false,2,135,135,7044897,"school",13.560897172707032,22,"SHARED2FREE",-0.04192782886512184,56359181 -171826,63802,"school",2,false,2,135,135,7044897,"home",,24,"SHARED2FREE",-0.04192782886512184,56359182 +147129,54342,"atwork",1,true,2,118,118,6032293,"work",8.812717247468496,24,"SHARED3FREE",-0.145853673602041,48258345 +147129,54342,"atwork",2,true,2,118,118,6032293,"atwork",,25,"WALK",-0.14614682859354086,48258346 +147129,54342,"atwork",1,false,1,118,118,6032293,"work",,27,"SHARED3FREE",-0.14614682859354086,48258349 +147129,54342,"work",1,true,1,118,117,6032328,"work",,24,"DRIVEALONEFREE",0.20514075467216628,48258625 +147129,54342,"work",1,false,2,121,118,6032328,"othmaint",7.591255086332629,45,"DRIVEALONEFREE",-0.05268563765149335,48258629 +147129,54342,"work",2,false,2,117,121,6032328,"home",,46,"SHARED3FREE",-0.02237034338193105,48258630 +168909,62701,"othmaint",1,true,1,131,131,6925297,"othmaint",,25,"WALK",-0.5552063584327697,55402377 +168909,62701,"othmaint",1,false,1,131,131,6925297,"home",,28,"WALK",-0.5552063584327697,55402381 +171822,63802,"shopping",1,true,1,130,135,7044721,"shopping",,29,"SHARED3FREE",-0.3938452305023584,56357769 +171822,63802,"shopping",1,false,3,135,130,7044721,"othmaint",5.98668616675203,30,"SHARED3FREE",-0.39446175334787803,56357773 +171822,63802,"shopping",2,false,3,135,135,7044721,"othmaint",6.756477914457603,30,"SHARED3FREE",-0.2052835117202649,56357774 +171822,63802,"shopping",3,false,3,135,135,7044721,"home",,31,"SHARED3FREE",-0.2052835117202649,56357775 +171823,63802,"escort",1,true,1,135,135,7044752,"escort",,27,"SHARED2FREE",0.19489390061694092,56358017 +171823,63802,"escort",1,false,2,128,135,7044752,"othmaint",8.083601093614876,27,"DRIVEALONEFREE",-0.1812426477593491,56358021 +171823,63802,"escort",2,false,2,135,128,7044752,"home",,27,"DRIVEALONEFREE",-0.18540377166841351,56358022 +171823,63802,"escort",1,true,1,135,135,7044753,"escort",,10,"DRIVEALONEFREE",-0.27614978880882257,56358025 +171823,63802,"escort",1,false,3,135,135,7044753,"escort",6.748884636203162,10,"DRIVEALONEFREE",-0.27614978880882257,56358029 +171823,63802,"escort",2,false,3,135,135,7044753,"eatout",6.425456017109895,10,"DRIVEALONEFREE",-0.27614978880882257,56358030 +171823,63802,"escort",3,false,3,135,135,7044753,"home",,11,"DRIVEALONEFREE",-0.27614978880882257,56358031 +171824,63802,"school",1,true,1,135,135,7044815,"school",,10,"SCHOOL_BUS",4.381191198921203,56358521 +171824,63802,"school",1,false,1,135,135,7044815,"home",,25,"SCHOOL_BUS",4.381191198921203,56358525 +171826,63802,"school",1,true,1,135,135,7044897,"school",,8,"SCHOOL_BUS",4.381191198921203,56359177 +171826,63802,"school",1,false,1,135,135,7044897,"home",,24,"SCHOOL_BUS",4.381191198921203,56359181 From 5c961b7fc36e2e1daad10f5ac7b4fac3bec716ab Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 07:06:11 +1000 Subject: [PATCH 253/298] switch base seed to avoid trip_scheduling (probabilistic) to come up with edge cases for small sample test. note this also happens for non-eet, e.g. with base seed 1 --- .../examples/production_semcog/test/configs_eet/settings.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/examples/production_semcog/test/configs_eet/settings.yaml b/activitysim/examples/production_semcog/test/configs_eet/settings.yaml index dcff83f5a1..6f6c702453 100644 --- a/activitysim/examples/production_semcog/test/configs_eet/settings.yaml +++ b/activitysim/examples/production_semcog/test/configs_eet/settings.yaml @@ -2,4 +2,4 @@ inherit_settings: True use_explicit_error_terms: True -rng_base_seed: 42 +rng_base_seed: 999 From a325f457a013c97d411cff23bb121b5787c8f253 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 07:07:13 +1000 Subject: [PATCH 254/298] forgot the corresponding golden trips --- .../test/regress/final_eet_trips.csv | 256 ++++++++++-------- 1 file changed, 141 insertions(+), 115 deletions(-) diff --git a/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv index 9826e1d681..31bb814b35 100644 --- a/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv +++ b/activitysim/examples/production_semcog/test/regress/final_eet_trips.csv @@ -1,116 +1,142 @@ "person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","original_school_zone_id","parked_at_university","depart","tour_includes_parking","trip_id_pre_parking","trip_mode","mode_choice_logsum","trip_id" -2632461,1066212,"eatout",1,true,1,22677,22687,107930907,"eatout",,,false,31,0,863447257,"WALK",1.2747067090732285,1726894513 -2632461,1066212,"eatout",1,false,1,22687,22677,107930907,"home",,,false,36,0,863447261,"WALK",1.2528040978215553,1726894521 -2632461,1066212,"social",1,true,1,22688,22687,107930937,"social",,,false,27,0,863447497,"WALK",0.22160552915226453,1726894993 -2632461,1066212,"social",1,false,1,22687,22688,107930937,"home",,,false,30,0,863447501,"WALK",0.22160552915226453,1726895001 -2632461,1066212,"work",1,true,1,22659,22687,107930940,"work",,,false,11,0,863447521,"DRIVEALONE",-0.2764049012484984,1726895041 -2632461,1066212,"work",1,false,1,22687,22659,107930940,"home",,,false,23,0,863447525,"DRIVEALONE",-0.26321709957638273,1726895049 -2632746,1066390,"school",1,true,2,22689,22688,107942617,"shopping",10.30120931810444,,false,10,0,863540937,"WALK",0.41173295672864374,1727081873 -2632746,1066390,"school",2,true,2,22716,22689,107942617,"school",,,false,10,0,863540938,"SHARED3",-0.12093048344357989,1727081874 -2632746,1066390,"school",1,false,1,22688,22716,107942617,"home",,,false,21,0,863540941,"SHARED3",-0.12094657865851986,1727081881 -2632746,1066390,"work",1,true,1,22688,22688,107942625,"work",,,false,21,0,863541001,"WALK",0.22160552915226453,1727082001 -2632746,1066390,"work",1,false,1,22688,22688,107942625,"home",,,false,26,0,863541005,"WALK",0.22160552915226453,1727082009 -2643231,1070862,"work",1,true,2,22795,22701,108372510,"parking",,,false,12,1,866980081,"DRIVEALONE",-0.8231318335063801,1733960161 -2643231,1070862,"work",2,true,2,22795,22795,108372510,"work",,,true,12,1,866980081,"WALK",2.0056875567057055,1733960162 -2643231,1070862,"work",1,false,2,22795,22795,108372510,"parking",,,true,27,1,866980085,"WALK",2.005696956562539,1733960169 -2643231,1070862,"work",2,false,2,22701,22795,108372510,"home",,,false,27,1,866980085,"DRIVEALONE",-0.856858292126302,1733960170 -2851663,1151807,"work",1,true,1,22743,22768,116918222,"work",,,false,8,0,935345777,"DRIVEALONE",0.32319821567472595,1870691553 -2851663,1151807,"work",1,false,1,22768,22743,116918222,"home",,,false,23,0,935345781,"SHARED2",0.31914343414862156,1870691561 -2851664,1151807,"atwork",1,true,1,22755,22783,116918247,"atwork",,,false,9,0,935345977,"WALK",-0.4285387553231248,1870691953 -2851664,1151807,"atwork",1,false,2,22767,22755,116918247,"eatout",13.620552291309844,,false,9,0,935345981,"WALK",-0.3360993920023427,1870691961 -2851664,1151807,"atwork",2,false,2,22783,22767,116918247,"work",,,false,9,0,935345982,"WALK",2.0196212107066467,1870691962 -2851664,1151807,"work",1,true,2,22783,22768,116918263,"parking",,,false,8,1,935346105,"DRIVEALONE",-0.101763675139766,1870692209 -2851664,1151807,"work",2,true,2,22783,22783,116918263,"work",,,true,8,1,935346105,"WALK",2.394893602362858,1870692210 -2851664,1151807,"work",1,false,2,22783,22783,116918263,"parking",,,true,9,1,935346109,"WALK",2.394893602362858,1870692217 -2851664,1151807,"work",2,false,2,22768,22783,116918263,"home",,,false,9,1,935346109,"DRIVEALONE",-0.14128432072913716,1870692218 -2851664,1151807,"work",1,true,2,22783,22768,116918264,"parking",,,false,20,1,935346113,"SHARED2",0.3036422340987692,1870692225 -2851664,1151807,"work",2,true,2,22783,22783,116918264,"work",,,true,20,1,935346113,"WALK",3.455230939742648,1870692226 -2851664,1151807,"work",1,false,3,22783,22783,116918264,"parking",,,true,22,1,935346117,"WALK",3.455230939742648,1870692233 -2851664,1151807,"work",2,false,3,22743,22783,116918264,"eatout",13.694190728203349,,false,22,1,935346117,"SHARED2",0.12796410019608423,1870692234 -2851664,1151807,"work",3,false,3,22768,22743,116918264,"home",,,false,22,1,935346118,"DRIVEALONE",0.28506746953977524,1870692235 -2851665,1151807,"school",1,true,1,22738,22768,116918296,"school",,,false,9,0,935346369,"WALK",-0.3380929737459932,1870692737 -2851665,1151807,"school",1,false,1,22768,22738,116918296,"home",,,false,25,0,935346373,"WALK",-0.3380929737459932,1870692745 -2851666,1151807,"school",1,true,1,22738,22768,116918337,"school",,,false,9,1,935346697,"WALK",-0.23394837977299351,1870693393 -2851666,1151807,"school",1,false,4,22766,22738,116918337,"parking",,,false,26,1,935346701,"WALK",-0.12281263655431907,1870693401 -2851666,1151807,"school",2,false,4,22767,22766,116918337,"eatout",12.976839556161908,,true,26,1,935346701,"WALK",-0.452052425913061,1870693402 -2851666,1151807,"school",3,false,4,22766,22767,116918337,"parking",,,true,26,1,935346702,"WALK",2.053743433341529,1870693403 -2851666,1151807,"school",4,false,4,22768,22766,116918337,"home",,,false,26,1,935346702,"SHARED3",-0.16257826107609574,1870693404 -2853258,1152693,"work",1,true,1,22738,22767,116983617,"work",,,false,20,0,935868937,"WALK",-0.22675750604679695,1871737873 -2853258,1152693,"work",1,false,1,22767,22738,116983617,"home",,,false,42,0,935868941,"WALK",-0.22675750604679695,1871737881 -2864033,1157863,"work",1,true,1,22801,22818,117425392,"work",,,false,22,0,939403137,"WALK",3.73570922575177,1878806273 -2864033,1157863,"work",1,false,3,22771,22801,117425392,"othmaint",26.926672191384228,,false,43,0,939403141,"WALK",4.180094740047142,1878806281 -2864033,1157863,"work",2,false,3,22767,22771,117425392,"othmaint",27.815811398797507,,false,43,0,939403142,"WALK",4.6038065658867176,1878806282 -2864033,1157863,"work",3,false,3,22818,22767,117425392,"home",,,false,44,0,939403143,"WALK",4.817999024372856,1878806283 -2867650,1159450,"work",1,true,2,22800,22791,117573689,"parking",,,false,12,1,940589513,"DRIVEALONE",-0.06025969651174059,1881179025 -2867650,1159450,"work",2,true,2,22800,22800,117573689,"work",,,true,12,1,940589513,"WALK",3.0701275509879262,1881179026 -2867650,1159450,"work",1,false,2,22800,22800,117573689,"parking",,,true,37,1,940589517,"WALK",3.070138980004933,1881179033 -2867650,1159450,"work",2,false,2,22791,22800,117573689,"home",,,false,37,1,940589517,"WALK",0.2738110299731082,1881179034 -2867652,1159450,"school",1,true,1,22798,22791,117573763,"school",,,false,11,0,940590105,"WALK",-0.1419702876491479,1881180209 -2867652,1159450,"school",1,false,2,22807,22798,117573763,"escort",12.102989575726829,,false,26,0,940590109,"WALK",0.30995293909650445,1881180217 -2867652,1159450,"school",2,false,2,22791,22807,117573763,"home",,,false,27,0,940590110,"WALK",1.1921458680932127,1881180218 -2867653,1159450,"school",1,true,1,22738,22791,117573804,"school",,,false,9,0,940590433,"BIKE",-0.6921067330756006,1881180865 -2867653,1159450,"school",1,false,1,22791,22738,117573804,"home",,,false,23,0,940590437,"BIKE",-0.6921067330756006,1881180873 -2867653,1159450,"school",1,true,1,22738,22791,117573805,"school",,,false,8,0,940590441,"SCHOOLBUS",-1.3378817936541838,1881180881 -2867653,1159450,"school",1,false,1,22791,22738,117573805,"home",,,false,8,0,940590445,"SHARED2",-9.871239947524709,1881180889 -2869308,1160345,"escort",1,true,1,22814,22788,117641637,"escort",,,false,10,0,941133097,"WALK",-0.2948412350381067,1882266193 -2869308,1160345,"escort",1,false,1,22788,22814,117641637,"home",,,false,10,0,941133101,"WALK",-0.2948412350381067,1882266201 -2869308,1160345,"work",1,true,1,22640,22788,117641667,"work",,,false,11,1,941133337,"SHARED2",-0.6362720979256549,1882266673 -2869308,1160345,"work",1,false,6,22761,22640,117641667,"parking",,,false,27,1,941133341,"SHARED3",-0.5969499608294799,1882266681 -2869308,1160345,"work",2,false,6,22767,22761,117641667,"othmaint",11.470504256383801,,true,27,1,941133341,"WALK",3.0222067430139012,1882266682 -2869308,1160345,"work",3,false,6,22761,22767,117641667,"parking",,,true,28,1,941133342,"WALK",4.036268963328661,1882266683 -2869308,1160345,"work",4,false,6,22769,22761,117641667,"shopping",13.481404724590321,,false,28,1,941133342,"WALK",0.4697934390352235,1882266684 -2869308,1160345,"work",5,false,6,22769,22769,117641667,"escort",13.817298583185659,,false,29,1,941133343,"DRIVEALONE",0.5088371121647843,1882266685 -2869308,1160345,"work",6,false,6,22788,22769,117641667,"home",,,false,30,1,941133344,"DRIVEALONE",0.38196512409824096,1882266686 -2869309,1160345,"univ",1,true,2,22795,22788,117641700,"parking",,,false,13,1,941133601,"DRIVEALONE",-0.15235107523409816,1882267201 -2869309,1160345,"univ",2,true,2,22766,22795,117641700,"univ",,,true,13,1,941133601,"WALK_LOC",1.202786557349171,1882267202 -2869309,1160345,"univ",1,false,3,22795,22766,117641700,"parking",,,true,24,1,941133605,"WALK_LOC",1.142188272503556,1882267209 -2869309,1160345,"univ",2,false,3,22769,22795,117641700,"othdiscr",12.456311079956105,,false,24,1,941133605,"WALK",-1.6313849658981006,1882267210 -2869309,1160345,"univ",3,false,3,22788,22769,117641700,"home",,,false,24,1,941133606,"DRIVEALONE",-0.3087846946902839,1882267211 -2869392,1160408,"shopping",1,true,2,22797,22784,117645105,"parking",,,false,26,1,941160841,"DRIVEALONE",-0.0973453493011812,1882321681 -2869392,1160408,"shopping",2,true,2,22767,22797,117645105,"shopping",,,true,26,1,941160841,"WALK",3.635953706352258,1882321682 -2869392,1160408,"shopping",1,false,3,22797,22767,117645105,"parking",,,true,36,1,941160845,"WALK",3.2319057420708,1882321689 -2869392,1160408,"shopping",2,false,3,22778,22797,117645105,"othmaint",13.169263985923191,,false,36,1,941160845,"DRIVEALONE",-0.8388301157218331,1882321690 -2869392,1160408,"shopping",3,false,3,22784,22778,117645105,"home",,,false,37,1,941160846,"DRIVEALONE",-0.21796488394191485,1882321691 -2871041,1161101,"work",1,true,1,22801,22747,117712720,"work",,,false,10,0,941701761,"PNR_LOC",0.001366053793971812,1883403521 -2871041,1161101,"work",1,false,1,22747,22801,117712720,"home",,,false,30,0,941701765,"PNR_LOC",-0.0004743815570571668,1883403529 -2871042,1161101,"work",1,true,2,22802,22747,117712761,"parking",,,false,6,1,941702089,"DRIVEALONE",0.31437493739186884,1883404177 -2871042,1161101,"work",2,true,2,22802,22802,117712761,"work",,,true,6,1,941702089,"WALK",3.98103278438962,1883404178 -2871042,1161101,"work",1,false,2,22802,22802,117712761,"parking",,,true,31,1,941702093,"WALK",3.9810287626204213,1883404185 -2871042,1161101,"work",2,false,2,22747,22802,117712761,"home",,,false,31,1,941702093,"WALK",0.29964022247838484,1883404186 -4717826,1936565,"univ",1,true,1,22809,22808,193430897,"univ",,,false,25,0,1547447177,"WALK",2.48948699138067,3094894353 -4717826,1936565,"univ",1,false,4,22809,22809,193430897,"univ",10.85837416878764,22809,false,42,0,1547447181,"WALK",3.0000160707611045,3094894361 -4717826,1936565,"univ",2,false,4,22767,22809,193430897,"social",14.420134553925665,,false,43,0,1547447182,"WALK",2.7860651296874943,3094894362 -4717826,1936565,"univ",3,false,4,22767,22767,193430897,"eatout",18.783329870271647,,false,44,0,1547447183,"WALK",5.6282528531399505,3094894363 -4717826,1936565,"univ",4,false,4,22808,22767,193430897,"home",,,false,44,0,1547447184,"WALK",5.305674253948064,3094894364 -4718747,1937486,"univ",1,true,3,22771,22765,193468658,"eatout",25.835053255003054,,false,14,0,1547749265,"WALK_LOC",4.433464410699681,3095498529 -4718747,1937486,"univ",2,true,3,22767,22771,193468658,"social",25.54589732725773,,false,16,0,1547749266,"WALK",5.362458425962315,3095498530 -4718747,1937486,"univ",3,true,3,22809,22767,193468658,"univ",,,false,19,0,1547749267,"WALK",2.7945694548961417,3095498531 -4718747,1937486,"univ",1,false,1,22765,22809,193468658,"home",,,false,42,0,1547749269,"WALK",2.48457681340577,3095498537 -4718747,1937486,"shopping",1,true,2,22767,22765,193468660,"shopping",30.83670381348187,,false,13,0,1547749281,"WALK",6.4385685368034355,3095498561 -4718747,1937486,"shopping",2,true,2,22770,22767,193468660,"shopping",,,false,13,0,1547749282,"WALK",5.192455869479483,3095498562 -4718747,1937486,"shopping",1,false,1,22765,22770,193468660,"home",,,false,14,0,1547749285,"WALK",4.807792080345957,3095498569 -4720352,1939091,"univ",1,true,1,22766,22765,193534463,"univ",,,false,9,0,1548275705,"WALK",-0.6239793637995562,3096551409 -4720352,1939091,"univ",1,false,3,22759,22766,193534463,"shopping",11.15021662330538,,false,9,0,1548275709,"WALK",-0.5391508363377113,3096551417 -4720352,1939091,"univ",2,false,3,22760,22759,193534463,"othdiscr",17.448616740575964,,false,9,0,1548275710,"WALK",1.2201965059296072,3096551418 -4720352,1939091,"univ",3,false,3,22765,22760,193534463,"home",,,false,9,0,1548275711,"WALK",2.8041844809824235,3096551419 -4720352,1939091,"univ",1,true,1,22766,22765,193534464,"univ",,,false,13,0,1548275713,"WALK",2.715317834267571,3096551425 -4720352,1939091,"univ",1,false,2,22764,22766,193534464,"univ",11.320277288258279,,false,20,0,1548275717,"WALK",2.6563322165532184,3096551433 -4720352,1939091,"univ",2,false,2,22765,22764,193534464,"home",,,false,21,0,1548275718,"WALK",2.683225159417532,3096551434 -4722297,1942003,"univ",1,true,1,22809,22810,193614208,"univ",,,false,11,0,1548913665,"WALK",2.4667125356379236,3097827329 -4722297,1942003,"univ",1,false,1,22810,22809,193614208,"home",,,false,37,0,1548913669,"WALK",2.4563973988486754,3097827337 -4726458,1946164,"eatout",1,true,1,22762,22808,193784784,"eatout",,,false,21,0,1550278273,"WALK",-1.0299557646373856,3100556545 -4726458,1946164,"eatout",1,false,1,22808,22762,193784784,"home",,,false,22,0,1550278277,"WALK",-1.0299556501964702,3100556553 -4726458,1946164,"eatout",1,true,1,22773,22808,193784785,"eatout",,,false,28,0,1550278281,"WALK",-0.5777209461821046,3100556561 -4726458,1946164,"eatout",1,false,1,22808,22773,193784785,"home",,,false,29,0,1550278285,"WALK",-0.5777209461821046,3100556569 -4726458,1946164,"shopping",1,true,1,22770,22808,193784811,"shopping",,,false,14,0,1550278489,"WALK",0.3756438367025996,3100556977 -4726458,1946164,"shopping",1,false,1,22808,22770,193784811,"home",,,false,17,0,1550278493,"WALK",0.3756438367025996,3100556985 -4727363,1947069,"univ",1,true,1,22766,22765,193821914,"univ",,,false,14,0,1550575313,"WALK",-0.6239793637995562,3101150625 -4727363,1947069,"univ",1,false,3,22767,22766,193821914,"escort",13.043891235923125,,false,26,0,1550575317,"WALK",-0.9673991559282129,3101150633 -4727363,1947069,"univ",2,false,3,22767,22767,193821914,"shopping",18.14486120913688,,false,26,0,1550575318,"WALK",2.62825193059268,3101150634 -4727363,1947069,"univ",3,false,3,22765,22767,193821914,"home",,,false,27,0,1550575319,"WALK",2.1708672114306493,3101150635 -4729458,1949164,"univ",1,true,2,22767,22745,193907809,"eatout",25.639413512184284,,false,11,0,1551262473,"WALK_LOC",5.44204000187347,3102524945 -4729458,1949164,"univ",2,true,2,22809,22767,193907809,"univ",,,false,11,0,1551262474,"WALK",2.7976141090172613,3102524946 -4729458,1949164,"univ",1,false,2,22802,22809,193907809,"othdiscr",26.8381285605357,,false,27,0,1551262477,"WALK",2.9113281848126373,3102524953 -4729458,1949164,"univ",2,false,2,22745,22802,193907809,"home",,,false,28,0,1551262478,"WALK",5.509333658704657,3102524954 -4729679,1949385,"eatout",1,true,1,22748,22745,193916845,"eatout",,,false,26,0,1551334761,"WALK",0.10355029016646346,3102669521 -4729679,1949385,"eatout",1,false,1,22745,22748,193916845,"home",,,false,27,0,1551334765,"WALK",0.10355029016646346,3102669529 +2641718,1070360,"othmaint",1,true,1,22770,22699,108310466,"othmaint",,,false,22,0,866483729,"DRIVEALONE",-1.140203187748608,1732967457 +2641718,1070360,"othmaint",1,false,1,22699,22770,108310466,"home",,,false,27,0,866483733,"DRIVEALONE",-0.96489702194308,1732967465 +2641719,1070360,"eatout",1,true,1,22679,22699,108310485,"eatout",,,false,33,0,866483881,"WALK",0.060023062248146825,1732967761 +2641719,1070360,"eatout",1,false,1,22699,22679,108310485,"home",,,false,36,0,866483885,"DRIVEALONE",0.060023062248146825,1732967769 +2645285,1071806,"escort",1,true,1,22731,22724,108456694,"escort",,,false,30,0,867653553,"SHARED2",0.20596021066115075,1735307105 +2645285,1071806,"escort",1,false,1,22724,22731,108456694,"home",,,false,30,0,867653557,"SHARED2",0.20410476178627868,1735307113 +2645285,1071806,"escort",1,true,1,22724,22724,108456695,"escort",,,false,24,0,867653561,"SHARED3",0.391143445482773,1735307121 +2645285,1071806,"escort",1,false,2,22684,22724,108456695,"othmaint",8.215798131436362,,false,26,0,867653565,"DRIVEALONE",0.22788834489799226,1735307129 +2645285,1071806,"escort",2,false,2,22724,22684,108456695,"home",,,false,26,0,867653566,"SHARED2",0.22409547209460748,1735307130 +2645287,1071806,"school",1,true,2,22724,22724,108456798,"escort",8.50615870894261,,false,8,0,867654385,"SHARED3",0.4507317505936183,1735308769 +2645287,1071806,"school",2,true,2,22694,22724,108456798,"school",,,false,9,0,867654386,"SHARED3",0.04674717158735702,1735308770 +2645287,1071806,"school",1,false,2,22712,22694,108456798,"eatout",6.599013334372048,,false,25,0,867654389,"SHARED3",0.09035145895224501,1735308777 +2645287,1071806,"school",2,false,2,22724,22712,108456798,"home",,,false,25,0,867654390,"SHARED3",0.2357823544266528,1735308778 +2645287,1071806,"social",1,true,1,22675,22724,108456803,"social",,,false,25,0,867654425,"TNC_SHARED",0.2570159849833101,1735308849 +2645287,1071806,"social",1,false,2,22703,22675,108456803,"eatout",10.213663071613274,,false,37,0,867654429,"WALK",1.432913480161543,1735308857 +2645287,1071806,"social",2,false,2,22724,22703,108456803,"home",,,false,38,0,867654430,"WALK",1.1381875189174673,1735308858 +2671497,1083208,"social",1,true,1,22640,22636,109531357,"social",,,false,30,0,876250857,"SHARED2",-0.11752455779854772,1752501713 +2671497,1083208,"social",1,false,2,22647,22640,109531357,"othmaint",6.525155775915072,,false,35,0,876250861,"SHARED2",0.020891366046659268,1752501721 +2671497,1083208,"social",2,false,2,22636,22647,109531357,"home",,,false,35,0,876250862,"SHARED2",-0.1132362569393691,1752501722 +2671496,1083208,"work",1,true,3,22638,22636,109531375,"escort",9.124449650865401,,false,12,0,876251001,"SHARED3",0.6621622445387698,1752502001 +2671496,1083208,"work",2,true,3,22648,22638,109531375,"social",8.793105919676185,,false,12,0,876251002,"SHARED3",0.5269874496262971,1752502002 +2671496,1083208,"work",3,true,3,22640,22648,109531375,"work",,,false,12,0,876251003,"SHARED2",0.7000230371946444,1752502003 +2671496,1083208,"work",1,false,3,22651,22640,109531375,"othmaint",8.913860316975576,,false,34,0,876251005,"SHARED3",0.6099414926017465,1752502009 +2671496,1083208,"work",2,false,3,22654,22651,109531375,"othdiscr",9.246998716689566,,false,34,0,876251006,"DRIVEALONE",0.7396157840186334,1752502010 +2671496,1083208,"work",3,false,3,22636,22654,109531375,"home",,,false,35,0,876251007,"DRIVEALONE",0.6341748065967693,1752502011 +2671497,1083208,"escort",1,true,1,22676,22636,109531386,"escort",,,false,25,0,876251089,"DRIVEALONE",-0.32375601792570874,1752502177 +2671497,1083208,"escort",1,false,1,22636,22676,109531386,"home",,,false,28,0,876251093,"SHARED2",-0.39043824860460646,1752502185 +2671497,1083208,"escort",1,true,2,22640,22636,109531387,"escort",7.644412661552843,,false,10,0,876251097,"DRIVEALONE",0.09152200624156465,1752502193 +2671497,1083208,"escort",2,true,2,22646,22640,109531387,"escort",,,false,10,0,876251098,"SHARED2",0.25088781147753203,1752502194 +2671497,1083208,"escort",1,false,4,22659,22646,109531387,"eatout",6.241284873239872,,false,10,0,876251101,"DRIVEALONE",0.17080762729492935,1752502201 +2671497,1083208,"escort",2,false,4,22640,22659,109531387,"othmaint",6.923469348469137,,false,10,0,876251102,"DRIVEALONE",0.16895449265832232,1752502202 +2671497,1083208,"escort",3,false,4,22640,22640,109531387,"escort",7.326257858963803,,false,10,0,876251103,"SHARED2",0.2613961736397559,1752502203 +2671497,1083208,"escort",4,false,4,22636,22640,109531387,"home",,,false,11,0,876251104,"SHARED2",0.08402190585631253,1752502204 +2671498,1083208,"eatout",1,true,1,22637,22636,109531424,"eatout",,,false,27,0,876251393,"SHARED2",0.33534179514695717,1752502785 +2671498,1083208,"eatout",1,false,1,22636,22637,109531424,"home",,,false,28,0,876251397,"WALK",0.33534179514695717,1752502793 +2671498,1083208,"school",1,true,2,22640,22636,109531449,"othdiscr",-27.23581357235839,,false,13,0,876251593,"WALK",-9.620387872938311,1752503185 +2671498,1083208,"school",2,true,2,22639,22640,109531449,"school",,,false,14,0,876251594,"SCHOOLBUS",-1.2411331661037288,1752503186 +2671498,1083208,"school",1,false,1,22636,22639,109531449,"home",,,false,19,0,876251597,"SHARED3",-9.616074820484757,1752503193 +2671499,1083208,"othmaint",1,true,1,22669,22636,109531487,"othmaint",,,false,27,0,876251897,"SHARED2",-0.2790832908867949,1752503793 +2671499,1083208,"othmaint",1,false,2,22640,22669,109531487,"shopping",5.4285998302697065,,false,29,0,876251901,"SHARED2",-0.09063064588297244,1752503801 +2671499,1083208,"othmaint",2,false,2,22636,22640,109531487,"home",,,false,29,0,876251902,"WALK",-0.11496439975863353,1752503802 +2671499,1083208,"school",1,true,1,22639,22636,109531490,"school",,,false,10,0,876251921,"SHARED2",-0.1392604230329243,1752503841 +2671499,1083208,"school",1,false,1,22636,22639,109531490,"home",,,false,25,0,876251925,"SHARED2",-0.13926563615004,1752503849 +2671499,1083208,"shopping",1,true,1,22636,22636,109531492,"shopping",,,false,29,0,876251937,"WALK",0.3055555487258568,1752503873 +2671499,1083208,"shopping",1,false,1,22636,22636,109531492,"home",,,false,30,0,876251941,"WALK",0.3055555487258568,1752503881 +2671726,1083286,"escort",1,true,1,22676,22638,109540775,"escort",,,false,8,0,876326201,"SHARED2",-0.8036183526914108,1752652401 +2671726,1083286,"escort",1,false,1,22638,22676,109540775,"home",,,false,9,0,876326205,"SHARED2",-0.7709951428237803,1752652409 +2671726,1083286,"escort",1,true,1,22640,22638,109540776,"escort",,,false,25,0,876326209,"SHARED2",-0.06868349722217731,1752652417 +2671726,1083286,"escort",1,false,1,22638,22640,109540776,"home",,,false,27,0,876326213,"SHARED2",-0.008307952019422048,1752652425 +2671793,1083314,"work",1,true,1,22640,22638,109543552,"work",,,false,8,0,876348417,"DRIVEALONE",-0.19870458640890007,1752696833 +2671793,1083314,"work",1,false,1,22638,22640,109543552,"home",,,false,25,0,876348421,"DRIVEALONE",-0.1987082096816311,1752696841 +2852451,1152145,"othdiscr",1,true,1,22771,22767,116950516,"othdiscr",,,false,29,0,935604129,"WALK",1.1686758211930381,1871208257 +2852451,1152145,"othdiscr",1,false,1,22767,22771,116950516,"home",,,false,32,0,935604133,"WALK",1.1686758211930381,1871208265 +2852452,1152145,"othdiscr",1,true,2,22797,22767,116950557,"parking",,,false,12,1,935604457,"DRIVEALONE",-0.41298992250405697,1871208913 +2852452,1152145,"othdiscr",2,true,2,22808,22797,116950557,"othdiscr",,,true,12,1,935604457,"WALK",5.705347033607136,1871208914 +2852452,1152145,"othdiscr",1,false,1,22767,22808,116950557,"home",,,true,12,1,935604461,"WALK",5.971358671517957,1871208921 +2864392,1158099,"work",1,true,1,22808,22812,117440111,"work",,,false,23,0,939520889,"WALK",4.247342050332629,1879041777 +2864392,1158099,"work",1,false,2,22763,22808,117440111,"othmaint",23.966292625273155,,false,35,0,939520893,"WALK",4.10943031561864,1879041785 +2864392,1158099,"work",2,false,2,22812,22763,117440111,"home",,,false,35,0,939520894,"WALK",4.63116285056561,1879041786 +2871260,1161186,"othdiscr",1,true,1,22766,22737,117721685,"othdiscr",,,false,38,0,941773481,"WALK",-1.244393392405061,1883546961 +2871260,1161186,"othdiscr",1,false,1,22737,22766,117721685,"home",,,false,40,0,941773485,"WALK",-1.2443930347772003,1883546969 +2871260,1161186,"work",1,true,2,22795,22737,117721699,"parking",,,false,8,1,941773593,"SHARED3",0.3864085294818619,1883547185 +2871260,1161186,"work",2,true,2,22795,22795,117721699,"work",,,true,8,1,941773593,"WALK",2.7645040587099827,1883547186 +2871260,1161186,"work",1,false,2,22795,22795,117721699,"parking",,,true,36,1,941773597,"WALK",2.764619810010999,1883547193 +2871260,1161186,"work",2,false,2,22737,22795,117721699,"home",,,false,36,1,941773597,"DRIVEALONE",0.4271626724836496,1883547194 +2871261,1161186,"shopping",1,true,1,22808,22737,117721734,"shopping",,,false,10,0,941773873,"WALK",-0.014414695528787559,1883547745 +2871261,1161186,"shopping",1,false,1,22737,22808,117721734,"home",,,false,14,0,941773877,"WALK",-0.013567832754747558,1883547753 +2871261,1161186,"work",1,true,4,22766,22737,117721740,"parking",,,false,15,1,941773921,"DRIVEALONE",-0.13865483896283062,1883547841 +2871261,1161186,"work",2,true,4,22760,22766,117721740,"escort",7.493345665849475,,true,15,1,941773921,"SHARED2",3.5298489757380187,1883547842 +2871261,1161186,"work",3,true,4,22766,22760,117721740,"parking",,,true,24,1,941773922,"WALK",1.544964668523534,1883547843 +2871261,1161186,"work",4,true,4,22769,22766,117721740,"work",,,false,24,1,941773922,"DRIVEALONE",-0.6305890985632596,1883547844 +2871261,1161186,"work",1,false,1,22737,22769,117721740,"home",,,false,30,1,941773925,"DRIVEALONE",-0.5039360613382525,1883547849 +2871261,1161186,"work",1,true,1,22769,22737,117721741,"work",,,false,33,0,941773929,"DRIVEALONE",-0.1355725881640941,1883547857 +2871261,1161186,"work",1,false,1,22737,22769,117721741,"home",,,false,34,0,941773933,"DRIVEALONE",-0.1378339185228722,1883547865 +2873206,1162124,"univ",1,true,1,22764,22755,117801477,"univ",,,false,13,0,942411817,"WALK",2.354545772649213,1884823633 +2873206,1162124,"univ",1,false,2,22766,22764,117801477,"univ",18.390760252235403,22766,false,36,0,942411821,"WALK",2.6581997132469217,1884823641 +2873206,1162124,"univ",2,false,2,22755,22766,117801477,"home",,,false,37,0,942411822,"WALK_LOC",2.0012557110881506,1884823642 +2875351,1163196,"shopping",1,true,1,22767,22760,117889424,"shopping",,,false,11,0,943115393,"WALK",2.07708617142782,1886230785 +2875351,1163196,"shopping",1,false,1,22760,22767,117889424,"home",,,false,18,0,943115397,"WALK",2.07708617142782,1886230793 +2875352,1163196,"escort",1,true,1,22757,22760,117889441,"escort",,,false,11,1,943115529,"DRIVEALONE",-0.10256130147625434,1886231057 +2875352,1163196,"escort",1,false,2,22806,22757,117889441,"parking",,,false,14,1,943115533,"SHARED2",-0.2751703037379774,1886231065 +2875352,1163196,"escort",2,false,2,22760,22806,117889441,"home",,,true,14,1,943115533,"WALK_LOC",1.3403997582477214,1886231066 +2875352,1163196,"shopping",1,true,1,22738,22760,117889465,"shopping",,,false,19,0,943115721,"WALK",3.8858778353665366,1886231441 +2875352,1163196,"shopping",1,false,1,22760,22738,117889465,"home",,,false,19,0,943115725,"WALK",3.885954403741643,1886231449 +2875353,1163196,"shopping",1,true,2,22758,22760,117889506,"othdiscr",9.51576353086904,,false,16,1,943116049,"DRIVEALONE",-0.0677741165736649,1886232097 +2875353,1163196,"shopping",2,true,2,22748,22758,117889506,"shopping",,,false,16,1,943116050,"DRIVEALONE",-0.44315960970425583,1886232098 +2875353,1163196,"shopping",1,false,4,22771,22748,117889506,"othdiscr",8.908743808799336,,false,16,1,943116053,"WALK",-0.39817769201091624,1886232105 +2875353,1163196,"shopping",2,false,4,22796,22771,117889506,"parking",,,false,16,1,943116054,"DRIVEALONE",-0.33754982759871427,1886232106 +2875353,1163196,"shopping",3,false,4,22767,22796,117889506,"eatout",8.839874802018125,,true,16,1,943116054,"WALK",1.1862747408061989,1886232107 +2875353,1163196,"shopping",4,false,4,22760,22767,117889506,"home",,,true,17,1,943116055,"WALK",4.641814432099863,1886232108 +4719663,1938402,"work",1,true,1,22736,22808,193506222,"work",,,false,19,0,1548049777,"WALK_LOC",-2.0359710074337936,3096099553 +4719663,1938402,"work",1,false,3,22810,22736,193506222,"escort",11.259724128977593,,false,43,0,1548049781,"WALK_LOC",-2.2558423520011077,3096099561 +4719663,1938402,"work",2,false,3,22802,22810,193506222,"eatout",23.054434766048672,,false,43,0,1548049782,"WALK",3.6621112648324488,3096099562 +4719663,1938402,"work",3,false,3,22808,22802,193506222,"home",,,false,48,0,1548049783,"WALK",5.515121383512515,3096099563 +4720278,1939017,"eatout",1,true,1,22813,22806,193531404,"eatout",,,false,20,0,1548251233,"WALK",0.6597888893535038,3096502465 +4720278,1939017,"eatout",1,false,1,22806,22813,193531404,"home",,,false,21,0,1548251237,"WALK",0.6597888893535038,3096502473 +4720278,1939017,"othmaint",1,true,3,22763,22806,193531426,"othmaint",28.633259490347385,,false,22,0,1548251409,"WALK",6.507689465729672,3096502817 +4720278,1939017,"othmaint",2,true,3,22771,22763,193531426,"escort",29.07359900863869,,false,22,0,1548251410,"WALK",6.163423552844951,3096502818 +4720278,1939017,"othmaint",3,true,3,22749,22771,193531426,"othmaint",,,false,22,0,1548251411,"WALK",5.185202759440683,3096502819 +4720278,1939017,"othmaint",1,false,1,22806,22749,193531426,"home",,,false,23,0,1548251413,"WALK",4.596120061006968,3096502825 +4720278,1939017,"univ",1,true,1,22766,22806,193531429,"univ",,,false,23,0,1548251433,"WALK",-0.7025935206727638,3096502865 +4720278,1939017,"univ",1,false,4,22807,22766,193531429,"social",9.78076496454064,,false,47,0,1548251437,"WALK",-0.7025935206727638,3096502873 +4720278,1939017,"univ",2,false,4,22767,22807,193531429,"eatout",15.48110690117488,,false,47,0,1548251438,"WALK",2.329516762852266,3096502874 +4720278,1939017,"univ",3,false,4,22767,22767,193531429,"othdiscr",16.053671837422566,,false,47,0,1548251439,"WALK",2.62825193059268,3096502875 +4720278,1939017,"univ",4,false,4,22806,22767,193531429,"home",,,false,47,0,1548251440,"WALK",1.9818127360573472,3096502876 +4721502,1940241,"shopping",1,true,1,22769,22808,193581615,"shopping",,,false,9,0,1548652921,"WALK",3.83496243140644,3097305841 +4721502,1940241,"shopping",1,false,1,22808,22769,193581615,"home",,,false,10,0,1548652925,"WALK",3.8353544296935214,3097305849 +4721502,1940241,"work",1,true,1,22808,22808,193581621,"work",,,false,11,0,1548652969,"WALK",0.8529159329083985,3097305937 +4721502,1940241,"work",1,false,1,22808,22808,193581621,"home",,,false,32,0,1548652973,"WALK",0.8529159329083985,3097305945 +4722626,1942332,"eatout",1,true,1,22770,22808,193627672,"eatout",,,false,33,0,1549021377,"WALK",0.3756438367025996,3098042753 +4722626,1942332,"eatout",1,false,1,22808,22770,193627672,"home",,,false,33,0,1549021381,"WALK",0.3756438367025996,3098042761 +4722626,1942332,"univ",1,true,1,22766,22808,193627697,"univ",,,false,13,0,1549021577,"WALK_LOC",2.5777493433773446,3098043153 +4722626,1942332,"univ",1,false,2,22766,22766,193627697,"univ",19.19415745805419,22766,false,19,0,1549021581,"WALK",3.0032756156279,3098043161 +4722626,1942332,"univ",2,false,2,22808,22766,193627697,"home",,,false,19,0,1549021582,"WALK_LOC",2.5753563683300635,3098043162 +4722626,1942332,"univ",1,true,1,22766,22808,193627698,"univ",,,false,21,0,1549021585,"WALK_LOC",2.5777493433773446,3098043169 +4722626,1942332,"univ",1,false,2,22802,22766,193627698,"work",22.78494970140799,,false,31,0,1549021589,"SHARED2",2.334876597361351,3098043177 +4722626,1942332,"univ",2,false,2,22808,22802,193627698,"home",,,false,33,0,1549021590,"WALK",5.827872427744417,3098043178 +4722725,1942431,"univ",1,true,1,22809,22808,193631756,"univ",,,false,14,0,1549054049,"WALK",-0.5105450706807133,3098108097 +4722725,1942431,"univ",1,false,2,22808,22809,193631756,"othmaint",9.947787633937537,,false,35,0,1549054053,"WALK",-0.5105450706807133,3098108105 +4722725,1942431,"univ",2,false,2,22808,22808,193631756,"home",,,false,36,0,1549054054,"WALK",1.2793738993625978,3098108106 +4723873,1943579,"othdiscr",1,true,1,22758,22806,193678818,"othdiscr",,,false,5,0,1549430545,"WALK",-0.4674549419542701,3098861089 +4723873,1943579,"othdiscr",1,false,1,22806,22758,193678818,"home",,,false,11,0,1549430549,"WALK",-0.4674549419542701,3098861097 +4723873,1943579,"univ",1,true,1,22809,22806,193678824,"univ",,,false,11,0,1549430593,"WALK",2.8702590300943704,3098861185 +4723873,1943579,"univ",1,false,3,22766,22809,193678824,"univ",19.287475866389496,22766,false,30,0,1549430597,"WALK_LOC",2.55414420312753,3098861193 +4723873,1943579,"univ",2,false,3,22809,22766,193678824,"univ",19.315901069750137,22809,false,31,0,1549430598,"WALK_LOC",2.5493305710297736,3098861194 +4723873,1943579,"univ",3,false,3,22806,22809,193678824,"home",,,false,31,0,1549430599,"WALK",2.868772523286865,3098861195 +4723873,1943579,"shopping",1,true,1,22767,22806,193678826,"shopping",,,false,35,0,1549430609,"WALK",6.246940717427892,3098861217 +4723873,1943579,"shopping",1,false,2,22807,22767,193678826,"escort",31.1402779873001,,false,36,0,1549430613,"WALK",6.455271888629144,3098861225 +4723873,1943579,"shopping",2,false,2,22806,22807,193678826,"home",,,false,36,0,1549430614,"WALK",6.639307174934361,3098861226 +4724336,1944042,"univ",1,true,2,22767,22808,193697807,"shopping",9.734777696880263,,false,14,0,1549582457,"BIKE",2.2953691999197856,3099164913 +4724336,1944042,"univ",2,true,2,22809,22767,193697807,"univ",,,false,16,0,1549582458,"BIKE",-0.25259210021960504,3099164914 +4724336,1944042,"univ",1,false,4,22809,22809,193697807,"univ",7.985417484365299,22809,false,27,0,1549582461,"BIKE",-0.005181884649800808,3099164921 +4724336,1944042,"univ",2,false,4,22767,22809,193697807,"escort",10.731901264359964,,false,27,0,1549582462,"BIKE",-0.25259210021960504,3099164922 +4724336,1944042,"univ",3,false,4,22767,22767,193697807,"othmaint",15.899413242262586,,false,27,0,1549582463,"BIKE",2.6046496361983573,3099164923 +4724336,1944042,"univ",4,false,4,22808,22767,193697807,"home",,,false,34,0,1549582464,"BIKE",2.2953691999197856,3099164924 +4728329,1948035,"othdiscr",1,true,1,22765,22808,193861514,"othdiscr",,,false,22,0,1550892113,"WALK",-0.9271455939007041,3101784225 +4728329,1948035,"othdiscr",1,false,1,22808,22765,193861514,"home",,,false,23,0,1550892117,"WALK",-0.9271455939007041,3101784233 +4728329,1948035,"shopping",1,true,1,22767,22808,193861522,"shopping",,,false,28,0,1550892177,"TNC_SHARED",0.5008844483419488,3101784353 +4728329,1948035,"shopping",1,false,1,22808,22767,193861522,"home",,,false,32,0,1550892181,"WALK",0.5015771816517407,3101784361 +4728331,1948037,"univ",1,true,1,22766,22765,193861602,"univ",,,false,13,0,1550892817,"WALK_LOC",2.7152529370724956,3101785633 +4728331,1948037,"univ",1,false,1,22765,22766,193861602,"home",,,false,17,0,1550892821,"WALK",2.714396912840273,3101785641 From 4fcd23997eb4b2c7fa8ab260bada0f513c600b21 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 14:55:45 +1000 Subject: [PATCH 255/298] test multiple_zone golden --- .../test/reference_pipeline_2_zone_eet.zip | Bin 285194 -> 261561 bytes .../test/regress/final_eet_tours_2_zone.csv | 204 +++---- .../test/regress/final_eet_trips_2_zone.csv | 500 +++++++++--------- 3 files changed, 353 insertions(+), 351 deletions(-) diff --git a/activitysim/examples/placeholder_multiple_zone/test/reference_pipeline_2_zone_eet.zip b/activitysim/examples/placeholder_multiple_zone/test/reference_pipeline_2_zone_eet.zip index 8e3abdfff6beb2d8c8e9fa0d20aad9280e7345ab..da3ab8af1631f6d8b663a59e9c18ab48e9256b26 100644 GIT binary patch delta 245541 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%0ACjVp zH+1<44o7LU`RlqQlgfvpj-yxvcGdsb;zFldvfQ}hVq5^Omvt|H>UGu<5v>LWl9 z{J39Zw!d8n=U6D>>ZkhW6LT?ZKal=uw+_A%3KiT@h6MZ%X0iML(zUQNOHGv=i?J+csPS)hG|rs__+foKv@_HJjIjd2 z@(*XUVj2$sy+}f@_-H~=4i9u5ZV5~B99EI=KA5Hx`KK#l&Ir~IF;rpa6`WGRwd@uS zl+tAVVyst@{y)K%xB5%!cq$EW&q1y@h5f$QB8%Q88KQRnt-*E(vxIPy9&O$k6D-D{ zW&ZdlXx8dnSw7`I{=}Kl(N>0rZ#a|3xfZo#FXyNO`!NCLCr}R(QMV6VSu~?)kjW-< z@~6NWgmOO?Tr+9&AXFRA~*w$$_q(^^%vw+3#fT4rUIw_27z+L3cgX3fLmv zpPF#Zg$pcke*^u9>s|*c_o%v2qgxIP1l1^(wbycMi_X|KcnjQ*&f1S+WSWb&FG|1P zKSRv{&J?3HZpaT34}tHD6p#8q51;<+N$!h@Dc@Lx4Q~IFr&JcIf!qiQ#za6Sn>g+0R-AmS3V4Z@8r({DN=7IG z4%fv7x6gdQ%$nZ6G>!*NZ_=n$6kJ>vuo0tdmWD?#mT==EvH63jn5$`dLylmsT%2Fe z5NHxeAQ?ITP&C)(Z;i1`YK9^8cltbFWq3Ao08w1Jz{?(@jhSzvc{hCIR{Wfc` zp)iFj&&kx2qQUO;2$ph*A3&nrj?|t7XUG zOUyZ^Aj^WKzqwvdAu@LxW9BKV1Y@OL)z*}Y&{LAS9

Cs(^QD!e0}Py3QA0>{>(- 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 literal 285194 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

SgtnX+F#R*VryE*6 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 diff --git a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv index 00aa4c0fc7..86d744b362 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv +++ b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_tours_2_zone.csv @@ -1,106 +1,106 @@ "person_id","tour_type","tour_type_count","tour_type_num","tour_num","tour_count","tour_category","number_of_participants","destination","origin","household_id","tdd","start","end","duration","composition","destination_logsum","tour_mode","mode_choice_logsum","atwork_subtour_frequency","parent_tour_id","stop_frequency","primary_purpose","tour_id" -26686,"shopping",1,1,1,1,"non_mandatory",1,23000,8000,26686,113,12,13,1,"",13.652449170814883,"WALK_LRF",1.0501470508061868,"",,"0out_0in","shopping",1094159 -26844,"othmaint",1,1,2,2,"non_mandatory",1,5000,8000,26844,159,16,21,5,"",15.525049977674522,"WALK_LOC",2.760890821002089,"",,"0out_0in","othmaint",1100632 -26844,"shopping",1,1,1,2,"non_mandatory",1,1000,8000,26844,75,9,14,5,"",14.275587915746392,"TNC_SINGLE",2.305056815108128,"",,"1out_0in","shopping",1100637 -27726,"eatout",1,1,1,1,"non_mandatory",1,10000,10000,27726,140,14,19,5,"",15.203007240171102,"WALK",4.342645736471603,"",,"0out_0in","eatout",1136772 -110675,"work",1,1,1,1,"mandatory",1,16000,16000,110675,13,5,18,13,"",,"WALK",-0.2730591306116382,"no_subtours",,"0out_0in","work",4537714 -112064,"work",1,1,1,1,"mandatory",1,24000,16000,112064,131,13,20,7,"",,"WALK_LOC",5.176494664984331,"no_subtours",,"1out_1in","work",4594663 -264108,"eatout",1,1,1,1,"non_mandatory",1,8000,9000,226869,135,14,14,0,"",13.203967734929993,"WALK",1.0687903457949945,"",,"0out_0in","eatout",10828434 -323689,"work",1,1,1,1,"mandatory",1,2000,10000,256660,151,15,21,6,"",,"WALK_LRF",5.943591391097562,"no_subtours",,"1out_0in","work",13271288 +26686,"shopping",1,1,1,1,"non_mandatory",1,23000,8000,26686,113,12,13,1,"",10.610858661241041,"BIKE",1.0501470508061868,"",,"0out_0in","shopping",1094159 +26844,"othmaint",1,1,2,2,"non_mandatory",1,9000,8000,26844,159,16,21,5,"",12.381682576294725,"WALK_LOC",2.7924555127017814,"",,"0out_0in","othmaint",1100632 +26844,"shopping",1,1,1,2,"non_mandatory",1,5000,8000,26844,75,9,14,5,"",10.98605657139575,"TNC_SINGLE",2.732845477677904,"",,"1out_0in","shopping",1100637 +27726,"eatout",1,1,1,1,"non_mandatory",1,7000,10000,27726,140,14,19,5,"",12.223057521030135,"WALK_LOC",3.9327906069918876,"",,"0out_0in","eatout",1136772 +110675,"work",1,1,1,1,"mandatory",1,9000,16000,110675,13,5,18,13,"",,"WALK_LOC",-0.23735933616868604,"no_subtours",,"0out_0in","work",4537714 +112064,"work",1,1,1,1,"mandatory",1,4000,16000,112064,131,13,20,7,"",,"WALK",5.320176808287765,"no_subtours",,"0out_0in","work",4594663 +264108,"eatout",1,1,1,1,"non_mandatory",1,22000,9000,226869,135,14,14,0,"",10.289097224175714,"WALK_LRF",0.1018338420222794,"",,"0out_0in","eatout",10828434 +323689,"work",1,1,1,1,"mandatory",1,13000,10000,256660,151,15,21,6,"",,"WALK_LRF",5.829417670036404,"no_subtours",,"1out_0in","work",13271288 323690,"work",1,1,1,1,"mandatory",1,9000,10000,256660,117,12,17,5,"",,"WALK",5.3957995007835535,"no_subtours",,"0out_1in","work",13271329 -325431,"othdiscr",1,1,1,1,"non_mandatory",1,22000,16000,257531,102,11,14,3,"",15.268405514775877,"WALK",2.165017948111244,"",,"0out_2in","othdiscr",13342696 -325431,"work",1,1,1,1,"mandatory",1,14000,16000,257531,157,16,19,3,"",,"WALK_LOC",5.979950151025447,"no_subtours",,"0out_0in","work",13342710 -325432,"work",1,1,1,1,"mandatory",1,15000,16000,257531,45,7,15,8,"",,"WALK_LOC",5.9577256558570015,"no_subtours",,"0out_0in","work",13342751 -595684,"escort",1,1,1,1,"non_mandatory",1,5000,21000,370497,38,7,8,1,"",12.420811407080112,"SHARED3FREE",-1.0951775798823786,"",,"0out_0in","escort",24423053 -595684,"work",1,1,1,1,"mandatory",1,19000,21000,370497,167,17,22,5,"",,"SHARED2FREE",-0.14535197835485364,"no_subtours",,"3out_0in","work",24423083 +325431,"othdiscr",1,1,1,1,"non_mandatory",1,23000,16000,257531,102,11,14,3,"",12.111981032421705,"WALK_LRF",2.449514696123423,"",,"0out_2in","othdiscr",13342696 +325431,"work",1,1,1,1,"mandatory",1,1000,16000,257531,157,16,19,3,"",,"TNC_SINGLE",5.8294985824993155,"no_subtours",,"0out_0in","work",13342710 +325432,"work",1,1,1,1,"mandatory",1,17000,16000,257531,45,7,15,8,"",,"BIKE",6.349736430311182,"no_subtours",,"0out_0in","work",13342751 +595684,"escort",1,1,1,1,"non_mandatory",1,5000,21000,370497,38,7,8,1,"",9.512682218523492,"WALK_LOC",-1.0951775798823786,"",,"0out_0in","escort",24423053 +595684,"work",1,1,1,1,"mandatory",1,13000,21000,370497,167,17,22,5,"",,"WALK",-0.4857821845242076,"no_subtours",,"0out_0in","work",24423083 595685,"school",1,1,1,1,"mandatory",1,13000,21000,370497,61,8,15,7,"",,"WALK_LOC",-0.9348277771487143,"",,"0out_0in","school",24423116 -595686,"school",1,1,1,1,"mandatory",1,21000,21000,370497,41,7,11,4,"",,"WALK",-0.41324468118526936,"",,"0out_0in","school",24423157 -644292,"school",1,1,1,1,"mandatory",1,2000,7000,386699,9,5,14,9,"",,"WALK",18.86976579102885,"",,"0out_0in","school",26416003 -644476,"work",1,1,1,1,"mandatory",1,7000,16000,386761,47,7,17,10,"",,"WALK_LOC",5.51666670545706,"no_subtours",,"0out_0in","work",26423555 -644477,"work",1,1,1,1,"mandatory",1,4000,16000,386761,64,8,18,10,"",,"WALK_LOC",5.662108100914558,"no_subtours",,"0out_2in","work",26423596 -644478,"school",1,1,1,1,"mandatory",1,25000,16000,386761,45,7,15,8,"",,"WALK_LOC",17.88652386604347,"",,"0out_0in","school",26423629 -1267567,"eatout",1,1,1,1,"non_mandatory",1,11000,21000,570454,99,11,11,0,"",15.277431294707508,"WALK",4.004645419133042,"",,"0out_0in","eatout",51970253 -1427193,"shopping",1,1,1,1,"non_mandatory",1,16000,25000,703381,151,15,21,6,"",13.171561237606278,"BIKE",0.8981575547418779,"",,"0out_0in","shopping",58514946 -1427194,"othmaint",3,1,1,3,"non_mandatory",1,9000,25000,703381,74,9,13,4,"",14.416957607852858,"BIKE",0.6613874806788961,"",,"0out_0in","othmaint",58514982 -1427194,"othmaint",3,2,2,3,"non_mandatory",1,8000,25000,703381,156,16,18,2,"",14.365283875141941,"BIKE",0.6990093306817798,"",,"0out_0in","othmaint",58514983 -1427194,"othmaint",3,3,3,3,"non_mandatory",1,7000,25000,703381,172,18,21,3,"",14.373474385937751,"BIKE",1.1745535385516204,"",,"0out_0in","othmaint",58514984 -1572659,"othdiscr",1,1,1,1,"non_mandatory",1,7000,6000,763879,8,5,13,8,"",15.269403956266437,"WALK",3.1705302835155984,"",,"0out_0in","othdiscr",64479044 -1572930,"eatout",1,1,1,1,"non_mandatory",1,12000,9000,764150,46,7,16,9,"",15.42100489856711,"WALK",3.628422928001604,"",,"0out_0in","eatout",64490136 -1632206,"work",1,1,1,1,"mandatory",1,12000,11000,823426,48,7,18,11,"",,"WALK",-0.0229685389951619,"no_subtours",,"0out_0in","work",66920485 -1632281,"work",1,1,1,1,"mandatory",1,1000,12000,823501,64,8,18,10,"",,"WALK",-0.4832251215332097,"no_subtours",,"0out_0in","work",66923560 -1632987,"eat",1,1,1,1,"atwork",1,13000,22000,824207,100,11,12,1,"",15.37146933803088,"TNC_SINGLE",5.527382364048134,"",66952506,"0out_0in","atwork",66952471 -1632987,"work",1,1,1,1,"mandatory",1,22000,18000,824207,50,7,20,13,"",,"WALK_LRF",5.930592339624059,"eat",,"0out_0in","work",66952506 -1875721,"work",1,1,1,1,"mandatory",1,4000,16000,982875,49,7,19,12,"",,"SHARED3FREE",1.731505679041429,"no_subtours",,"0out_0in","work",76904600 -1875722,"work",1,1,1,1,"mandatory",1,7000,16000,982875,48,7,18,11,"",,"WALK",1.5259948396881422,"no_subtours",,"0out_0in","work",76904641 -2159057,"work",1,1,1,1,"mandatory",1,11000,20000,1099626,47,7,17,10,"",,"BIKE",0.08239658913473709,"no_subtours",,"0out_0in","work",88521376 -2159058,"school",1,1,1,1,"mandatory",1,12000,20000,1099626,44,7,14,7,"",,"WALK_LOC",-0.08092637357224697,"",,"0out_0in","univ",88521409 -2159059,"school",1,1,1,1,"mandatory",1,17000,20000,1099626,61,8,15,7,"",,"SHARED2FREE",-0.5795062930092348,"",,"0out_0in","school",88521450 -2458500,"othdiscr",1,1,1,1,"non_mandatory",1,2000,8000,1173905,126,13,15,2,"",15.003025752404032,"TNC_SINGLE",2.1345972053076645,"",,"0out_0in","othdiscr",100798525 -2458502,"school",1,1,1,1,"mandatory",1,9000,8000,1173905,76,9,15,6,"",,"WALK_LOC",19.061076416939844,"",,"0out_0in","school",100798613 -2458503,"school",1,1,1,1,"mandatory",1,25000,8000,1173905,63,8,17,9,"",,"WALK",17.394445009270893,"",,"0out_0in","school",100798654 -2566698,"othmaint",1,1,1,1,"non_mandatory",1,12000,25000,1196298,146,15,16,1,"",13.904676146693486,"WALK",-0.08705033093047729,"",,"0out_0in","othmaint",105234646 -2566698,"work",1,1,1,1,"mandatory",1,9000,25000,1196298,42,7,12,5,"",,"SHARED3FREE",-0.12492841393683855,"no_subtours",,"1out_2in","work",105234657 -2566699,"escort",2,1,1,4,"non_mandatory",1,9000,25000,1196298,55,8,9,1,"",12.487156714808382,"SHARED3FREE",-1.224321337819968,"",,"0out_0in","escort",105234668 -2566699,"escort",2,2,2,4,"non_mandatory",1,2000,25000,1196298,112,12,12,0,"",12.473008939270755,"SHARED2FREE",-0.5133849848564047,"",,"0out_0in","escort",105234669 -2566699,"othdiscr",1,1,4,4,"non_mandatory",1,5000,25000,1196298,172,18,21,3,"",13.96308058011918,"WALK",0.6917454521182999,"",,"0out_0in","othdiscr",105234684 -2566699,"shopping",1,1,3,4,"non_mandatory",1,17000,25000,1196298,71,9,10,1,"",12.746190439180568,"TNC_SINGLE",-0.323622964459322,"",,"0out_0in","shopping",105234692 -2566700,"school",1,1,1,1,"mandatory",1,19000,25000,1196298,61,8,15,7,"",,"BIKE",-1.7110584511799807,"",,"0out_0in","school",105234731 -2566701,"escort",1,1,1,1,"non_mandatory",1,13000,25000,1196298,124,13,13,0,"",12.587432709712925,"SHARED2FREE",0.06051195773469012,"",,"0out_0in","escort",105234750 -2566701,"school",1,1,1,1,"mandatory",1,11000,25000,1196298,43,7,13,6,"",,"SHARED2FREE",-1.0766765571584742,"",,"0out_0in","school",105234772 -2566702,"othdiscr",1,1,1,1,"non_mandatory",1,9000,25000,1196298,171,18,20,2,"",14.120222605326392,"SHARED2FREE",0.3431222104372727,"",,"0out_2in","othdiscr",105234807 -2936848,"eatout",1,1,3,3,"non_mandatory",1,22000,11000,1286557,128,13,17,4,"",15.413580157739286,"WALK",3.337722927621502,"",,"0out_0in","eatout",120410774 -2936848,"othmaint",1,1,2,3,"non_mandatory",1,5000,11000,1286557,59,8,13,5,"",14.936437315067716,"WALK_LOC",1.9387725623593302,"",,"2out_1in","othmaint",120410796 -2936848,"shopping",1,1,1,3,"non_mandatory",1,11000,11000,1286557,170,18,19,1,"",13.737502885247409,"WALK",1.7463445938625892,"",,"0out_2in","shopping",120410801 -3061894,"othmaint",1,1,2,2,"non_mandatory",1,5000,24000,1363467,63,8,17,9,"",15.189637319752025,"WALK_LOC",2.2612689227267926,"",,"0out_0in","othmaint",125537682 -3061894,"shopping",1,1,1,2,"non_mandatory",1,5000,24000,1363467,54,8,8,0,"",13.926378681444465,"TNC_SINGLE",2.195783573785629,"",,"1out_1in","shopping",125537687 -3061895,"othmaint",1,1,2,2,"non_mandatory",1,22000,24000,1363467,180,20,20,0,"",15.281994100444798,"WALK_LOC",2.3241680009556887,"",,"0out_0in","othmaint",125537723 -3061895,"shopping",1,1,1,2,"non_mandatory",1,4000,24000,1363467,66,8,20,12,"",13.894949372888025,"TNC_SINGLE",2.349852517486852,"",,"1out_0in","shopping",125537728 -3188483,"othmaint",1,1,2,2,"non_mandatory",1,3000,25000,1402945,112,12,12,0,"",14.231882110325735,"WALK",1.3527690062199011,"",,"0out_0in","othmaint",130727831 -3188483,"shopping",1,1,1,2,"non_mandatory",1,14000,25000,1402945,136,14,15,1,"",13.406265391553307,"BIKE",0.9749240595315471,"",,"0out_0in","shopping",130727836 -3188484,"work",1,1,1,1,"mandatory",1,21000,25000,1402945,147,15,17,2,"",,"SHARED2FREE",1.5045736701182255,"no_subtours",,"0out_0in","work",130727883 -3188485,"work",1,1,1,1,"mandatory",1,5000,25000,1402945,64,8,18,10,"",,"WALK",2.034000565768078,"no_subtours",,"0out_0in","work",130727924 -3232955,"escort",1,1,1,1,"non_mandatory",1,7000,14000,1444715,164,17,19,2,"",12.435690330338216,"WALK",-2.0590794826886896,"",,"0out_0in","escort",132551164 -3232955,"work",2,1,1,2,"mandatory",1,22000,14000,1444715,24,6,11,5,"",,"WALK",-0.40159974247061203,"no_subtours",,"0out_0in","work",132551194 -3232955,"work",2,2,2,2,"mandatory",1,22000,14000,1444715,127,13,16,3,"",,"WALK_LOC",-0.20548633109849337,"no_subtours",,"0out_0in","work",132551195 -3233462,"eat",1,1,1,1,"atwork",1,5000,19000,1445222,70,9,9,0,"",19.63149392560022,"SHARED3FREE",-0.34159841664366153,"",132571981,"0out_1in","atwork",132571946 -3233462,"work",1,1,1,1,"mandatory",1,19000,17000,1445222,81,9,20,11,"",,"DRIVEALONEFREE",0.16481970561443188,"eat",,"0out_3in","work",132571981 -3328568,"work",1,1,1,1,"mandatory",1,22000,8000,1511234,46,7,16,9,"",,"WALK_LRF",6.0835517322893065,"no_subtours",,"0out_0in","work",136471327 +595686,"school",1,1,1,1,"mandatory",1,16000,21000,370497,41,7,11,4,"",,"WALK_LOC",-0.800449800958596,"",,"0out_0in","school",24423157 +644292,"school",1,1,1,1,"mandatory",1,10000,7000,386699,9,5,14,9,"",,"WALK_HVY",19.587712717782818,"",,"0out_0in","school",26416003 +644476,"work",1,1,1,1,"mandatory",1,16000,16000,386761,47,7,17,10,"",,"WALK",5.723748258371387,"no_subtours",,"0out_0in","work",26423555 +644477,"work",1,1,1,1,"mandatory",1,2000,16000,386761,64,8,18,10,"",,"WALK_LOC",5.746364420193509,"no_subtours",,"0out_2in","work",26423596 +644478,"school",1,1,1,1,"mandatory",1,16000,16000,386761,45,7,15,8,"",,"WALK",17.692979333328697,"",,"0out_0in","school",26423629 +1267567,"eatout",1,1,1,1,"non_mandatory",1,5000,21000,570454,99,11,11,0,"",12.198336821342862,"WALK",4.12367021630192,"",,"0out_0in","eatout",51970253 +1427193,"shopping",1,1,1,1,"non_mandatory",1,11000,25000,703381,151,15,21,6,"",10.194065391642615,"WALK",0.5303425623640113,"",,"0out_0in","shopping",58514946 +1427194,"othmaint",3,1,1,3,"non_mandatory",1,2000,25000,703381,74,9,13,4,"",11.397149558879462,"BIKE",1.6921646836542774,"",,"0out_0in","othmaint",58514982 +1427194,"othmaint",3,2,2,3,"non_mandatory",1,9000,25000,703381,156,16,18,2,"",11.446286395441202,"BIKE",0.673847031461692,"",,"0out_0in","othmaint",58514983 +1427194,"othmaint",3,3,3,3,"non_mandatory",1,4000,25000,703381,172,18,21,3,"",11.409006574951876,"WALK",1.123057934220693,"",,"0out_0in","othmaint",58514984 +1572659,"othdiscr",1,1,1,1,"non_mandatory",1,5000,6000,763879,8,5,13,8,"",12.08921613172822,"WALK",2.968451508911268,"",,"0out_0in","othdiscr",64479044 +1572930,"eatout",1,1,1,1,"non_mandatory",1,7000,9000,764150,46,7,16,9,"",12.211469568104635,"WALK",4.184842505817334,"",,"0out_0in","eatout",64490136 +1632206,"work",1,1,1,1,"mandatory",1,2000,11000,823426,48,7,18,11,"",,"BIKE",-0.15558251192441164,"no_subtours",,"0out_0in","work",66920485 +1632281,"work",1,1,1,1,"mandatory",1,5000,12000,823501,64,8,18,10,"",,"WALK_LOC",-0.014522397979818477,"no_subtours",,"0out_0in","work",66923560 +1632987,"eat",1,1,1,1,"atwork",1,1000,2000,824207,100,11,12,1,"",12.497268604346223,"WALK",6.066084912412463,"",66952506,"0out_0in","atwork",66952471 +1632987,"work",1,1,1,1,"mandatory",1,2000,18000,824207,50,7,20,13,"",,"WALK",6.16313911347326,"eat",,"0out_0in","work",66952506 +1875721,"work",1,1,1,1,"mandatory",1,16000,16000,982875,49,7,19,12,"",,"WALK",2.052824858646257,"no_subtours",,"0out_0in","work",76904600 +1875722,"work",1,1,1,1,"mandatory",1,2000,16000,982875,48,7,18,11,"",,"WALK_LOC",1.8646274143957895,"no_subtours",,"0out_0in","work",76904641 +2159057,"work",1,1,1,1,"mandatory",1,15000,20000,1099626,47,7,17,10,"",,"WALK_LOC",-0.6269133124234691,"no_subtours",,"0out_0in","work",88521376 +2159058,"school",1,1,1,1,"mandatory",1,10000,20000,1099626,44,7,14,7,"",,"DRIVEALONEFREE",0.5331366792772473,"",,"0out_0in","univ",88521409 +2159059,"school",1,1,1,1,"mandatory",1,10000,20000,1099626,61,8,15,7,"",,"WALK",-0.4467165728053401,"",,"0out_0in","school",88521450 +2458500,"othdiscr",1,1,1,1,"non_mandatory",1,1000,8000,1173905,126,13,15,2,"",12.144175831465427,"WALK",1.7166041926739541,"",,"0out_0in","othdiscr",100798525 +2458502,"school",1,1,1,1,"mandatory",1,7000,8000,1173905,76,9,15,6,"",,"WALK",19.233607768914855,"",,"0out_0in","school",100798613 +2458503,"school",1,1,1,1,"mandatory",1,18000,8000,1173905,63,8,17,9,"",,"WALK_LOC",16.82632652412361,"",,"1out_2in","school",100798654 +2566698,"othmaint",1,1,1,1,"non_mandatory",1,2000,25000,1196298,146,15,16,1,"",11.063210428915374,"WALK",0.5633209455900281,"",,"0out_0in","othmaint",105234646 +2566698,"work",1,1,1,1,"mandatory",1,13000,25000,1196298,42,7,12,5,"",,"WALK",-0.0331723343613139,"no_subtours",,"0out_0in","work",105234657 +2566699,"escort",2,1,1,4,"non_mandatory",1,9000,25000,1196298,55,8,9,1,"",9.441924310089995,"SHARED2FREE",-1.224321337819968,"",,"0out_0in","escort",105234668 +2566699,"escort",2,2,2,4,"non_mandatory",1,11000,25000,1196298,112,12,12,0,"",9.41277389564782,"TNC_SINGLE",-0.9864057562279634,"",,"0out_0in","escort",105234669 +2566699,"othdiscr",1,1,4,4,"non_mandatory",1,2000,25000,1196298,172,18,21,3,"",10.848788280363689,"DRIVEALONEFREE",0.8198275600883485,"",,"0out_0in","othdiscr",105234684 +2566699,"shopping",1,1,3,4,"non_mandatory",1,2000,25000,1196298,71,9,10,1,"",9.543720666670257,"WALK",0.5009281837597124,"",,"0out_0in","shopping",105234692 +2566700,"school",1,1,1,1,"mandatory",1,7000,25000,1196298,61,8,15,7,"",,"WALK_LOC",-0.570159779269892,"",,"0out_0in","school",105234731 +2566701,"escort",1,1,1,1,"non_mandatory",1,22000,25000,1196298,124,13,13,0,"",9.553554280932733,"SHARED3FREE",0.15849324834776704,"",,"0out_0in","escort",105234750 +2566701,"school",1,1,1,1,"mandatory",1,21000,25000,1196298,43,7,13,6,"",,"SHARED3FREE",-1.0571220991682664,"",,"0out_0in","school",105234772 +2566702,"othdiscr",1,1,1,1,"non_mandatory",1,18000,25000,1196298,171,18,20,2,"",11.263577911809062,"SHARED2FREE",0.3038595355453006,"",,"0out_2in","othdiscr",105234807 +2936848,"eatout",1,1,3,3,"non_mandatory",1,15000,11000,1286557,128,13,17,4,"",12.266413600249491,"WALK",3.3115671555185893,"",,"0out_0in","eatout",120410774 +2936848,"othmaint",1,1,2,3,"non_mandatory",1,7000,11000,1286557,59,8,13,5,"",11.909324784764634,"BIKE",2.060670730997444,"",,"2out_1in","othmaint",120410796 +2936848,"shopping",1,1,1,3,"non_mandatory",1,13000,11000,1286557,170,18,19,1,"",10.780532073110708,"TNC_SINGLE",1.3488000773608126,"",,"0out_2in","shopping",120410801 +3061894,"othmaint",1,1,2,2,"non_mandatory",1,14000,24000,1363467,63,8,17,9,"",12.063680143654953,"TAXI",2.148310089033856,"",,"0out_1in","othmaint",125537682 +3061894,"shopping",1,1,1,2,"non_mandatory",1,22000,24000,1363467,54,8,8,0,"",10.881747022526751,"WALK",2.252180541904756,"",,"0out_0in","shopping",125537687 +3061895,"othmaint",1,1,2,2,"non_mandatory",1,9000,24000,1363467,180,20,20,0,"",12.42242239445194,"WALK_LRF",2.8803108561136073,"",,"0out_0in","othmaint",125537723 +3061895,"shopping",1,1,1,2,"non_mandatory",1,16000,24000,1363467,66,8,20,12,"",11.055276581731171,"WALK",2.001468036993353,"",,"1out_0in","shopping",125537728 +3188483,"othmaint",1,1,2,2,"non_mandatory",1,9000,25000,1402945,112,12,12,0,"",11.134991283596351,"DRIVEALONEFREE",0.3189527807943001,"",,"0out_3in","othmaint",130727831 +3188483,"shopping",1,1,1,2,"non_mandatory",1,12000,25000,1402945,136,14,15,1,"",10.486110215551667,"WALK",1.034228125459323,"",,"0out_0in","shopping",130727836 +3188484,"work",1,1,1,1,"mandatory",1,11000,25000,1402945,147,15,17,2,"",,"WALK",1.514717460133151,"no_subtours",,"0out_0in","work",130727883 +3188485,"work",1,1,1,1,"mandatory",1,7000,25000,1402945,64,8,18,10,"",,"WALK",1.8611739232870421,"no_subtours",,"0out_0in","work",130727924 +3232955,"escort",1,1,1,1,"non_mandatory",1,8000,14000,1444715,164,17,19,2,"",9.490739620053459,"SHARED2FREE",-2.001570831213811,"",,"0out_0in","escort",132551164 +3232955,"work",2,1,1,2,"mandatory",1,1000,14000,1444715,24,6,11,5,"",,"WALK",-0.07386262451458166,"no_subtours",,"0out_0in","work",132551194 +3232955,"work",2,2,2,2,"mandatory",1,1000,14000,1444715,127,13,16,3,"",,"WALK",-0.19789203372593106,"no_subtours",,"0out_0in","work",132551195 +3233462,"eat",1,1,1,1,"atwork",1,15000,16000,1445222,70,9,9,0,"",17.335573611488165,"WALK",0.01158493251705316,"",132571981,"0out_1in","atwork",132571946 +3233462,"work",1,1,1,1,"mandatory",1,16000,17000,1445222,81,9,20,11,"",,"SHARED3FREE",0.6704720594037347,"eat",,"0out_3in","work",132571981 +3328568,"work",1,1,1,1,"mandatory",1,13000,8000,1511234,68,8,22,14,"",,"WALK_LRF",5.81180830136589,"no_subtours",,"0out_1in","work",136471327 3328569,"school",1,1,1,1,"mandatory",1,9000,8000,1511234,62,8,16,8,"",,"WALK_LOC",7.510086352530541,"",,"0out_0in","univ",136471360 -3495342,"eat",1,1,1,1,"atwork",1,9000,11000,1594621,85,10,10,0,"",15.668488627428035,"WALK",5.895907678328029,"",143309061,"3out_0in","atwork",143309026 -3495342,"work",1,1,1,1,"mandatory",1,11000,10000,1594621,63,8,17,9,"",,"TNC_SINGLE",6.106308966698332,"eat",,"0out_0in","work",143309061 -3495343,"shopping",1,1,1,1,"non_mandatory",1,21000,10000,1594621,146,15,16,1,"",14.147994174455755,"TAXI",2.140539157970799,"",,"1out_1in","shopping",143309096 -3596364,"school",1,1,1,1,"mandatory",1,9000,9000,1645132,99,11,11,0,"",,"WALK",0.9922761728862803,"",,"0out_0in","univ",147450955 -3596364,"shopping",1,1,1,1,"non_mandatory",1,2000,9000,1645132,130,13,19,6,"",12.702108843408501,"WALK_LRF",-0.6357173968922101,"",,"0out_0in","shopping",147450957 -3596365,"school",1,1,1,1,"mandatory",1,9000,9000,1645132,92,10,17,7,"",,"WALK",0.13078470986223545,"",,"0out_2in","school",147450996 -3891102,"eat",1,1,1,1,"atwork",1,4000,1000,1747467,88,10,13,3,"",12.863034889266403,"WALK",0.48410709631297644,"",159535221,"0out_1in","atwork",159535186 -3891102,"work",1,1,1,1,"mandatory",1,1000,16000,1747467,67,8,21,13,"",,"WALK_LOC",1.7047589669241154,"eat",,"1out_1in","work",159535221 -3891104,"othdiscr",1,1,1,1,"non_mandatory",1,17000,16000,1747467,52,7,22,15,"",14.783602512881732,"WALK",1.8681070245632654,"",,"0out_0in","othdiscr",159535289 -4171615,"school",1,1,1,1,"mandatory",1,13000,16000,1810015,169,18,18,0,"",,"TAXI",3.339440236713284,"",,"0out_0in","univ",171036246 -4171616,"shopping",1,1,1,1,"non_mandatory",1,14000,16000,1810015,89,10,14,4,"",13.351914976059247,"WALK",1.2336467654702536,"",,"0out_0in","shopping",171036289 -4171617,"eat",1,1,1,1,"atwork",1,5000,11000,1810015,85,10,10,0,"",12.806283314422718,"WALK",0.1414193868413481,"",171036336,"0out_1in","atwork",171036301 -4171617,"work",1,1,1,1,"mandatory",1,11000,16000,1810015,62,8,16,8,"",,"WALK",1.2491275426711392,"eat",,"0out_0in","work",171036336 -4171619,"othdiscr",1,1,1,1,"non_mandatory",1,16000,16000,1810015,80,9,19,10,"",14.427951929207534,"WALK",1.7126466601147134,"",,"0out_0in","othdiscr",171036404 -4171622,"othmaint",1,1,1,1,"non_mandatory",1,2000,16000,1810015,100,11,12,1,"",14.02155021495475,"TNC_SINGLE",0.47701959308489095,"",,"0out_0in","othmaint",171036530 -4823797,"work",1,1,1,1,"mandatory",1,2000,14000,1952792,93,10,18,8,"",,"WALK",5.550859155970048,"no_subtours",,"0out_0in","work",197775716 -5057160,"work",1,1,1,1,"mandatory",1,5000,5000,2048204,30,6,17,11,"",,"BIKE",-0.09630302951387847,"no_subtours",,"0out_0in","work",207343599 -5057338,"work",1,1,1,1,"mandatory",1,17000,7000,2048382,50,7,20,13,"",,"WALK_LOC",5.537496365437239,"no_subtours",,"0out_0in","work",207350897 -5387762,"work",1,1,1,1,"mandatory",1,10000,9000,2223027,28,6,15,9,"",,"WALK",1.9810584290306386,"no_subtours",,"0out_0in","work",220898281 -5387763,"eatout",1,1,2,2,"non_mandatory",1,10000,9000,2223027,154,16,16,0,"",14.015959650256292,"WALK",2.3358373327911104,"",,"0out_0in","eatout",220898289 -5387763,"othdiscr",1,1,1,2,"non_mandatory",1,15000,9000,2223027,169,18,18,0,"",14.599617247497788,"WALK_LRF",1.465646852694868,"",,"0out_0in","othdiscr",220898308 -5389226,"work",1,1,1,1,"mandatory",1,14000,16000,2223759,63,8,17,9,"",,"WALK",1.8883570091287778,"no_subtours",,"0out_0in","work",220958305 -5389227,"eat",1,1,1,1,"atwork",1,16000,16000,2223759,99,11,11,0,"",12.858228927386488,"WALK",0.6660642166270728,"",220958346,"0out_0in","atwork",220958311 -5389227,"escort",1,1,1,1,"non_mandatory",1,5000,16000,2223759,162,17,17,0,"",12.681412371417782,"WALK",-0.2552610926701476,"",,"0out_0in","escort",220958316 -5389227,"work",1,1,1,1,"mandatory",1,16000,16000,2223759,28,6,15,9,"",,"WALK",2.079861874799809,"eat",,"0out_0in","work",220958346 -7305540,"social",2,1,1,2,"non_mandatory",1,21000,20000,2727273,37,7,7,0,"",14.1996409773108,"WALK",1.8494027479889232,"",,"0out_0in","social",299527176 -7305540,"social",2,2,2,2,"non_mandatory",1,9000,20000,2727273,86,10,11,1,"",14.120642597671408,"WALK",1.9956840012673436,"",,"0out_1in","social",299527177 -7305540,"work",1,1,1,1,"mandatory",1,24000,20000,2727273,127,13,16,3,"",,"BIKE",0.9398542864235182,"no_subtours",,"0out_0in","work",299527179 -7305541,"shopping",1,1,1,2,"non_mandatory",1,16000,20000,2727273,171,18,20,2,"",13.28794064279901,"WALK_LOC",0.9142001643337417,"",,"0out_0in","shopping",299527214 -7305541,"social",1,1,2,2,"non_mandatory",1,21000,20000,2727273,162,17,17,0,"",14.151071365958824,"WALK_LOC",1.8493576999144647,"",,"0out_0in","social",299527217 -7305541,"work",1,1,1,1,"mandatory",1,4000,20000,2727273,45,7,15,8,"",,"WALK_LRF",1.6475450149587834,"no_subtours",,"0out_0in","work",299527220 -7453413,"othmaint",1,1,1,1,"non_mandatory",1,8000,20000,2762078,102,11,14,3,"",14.985169483093204,"WALK_LOC",2.1409730693117166,"",,"0out_0in","othmaint",305589961 -7511873,"work",1,1,1,1,"mandatory",1,1000,8000,2820538,45,7,15,8,"",,"WALK_LOC",-0.8702887383817772,"no_subtours",,"0out_0in","work",307986832 -7512109,"work",1,1,1,1,"mandatory",1,14000,8000,2820774,48,7,18,11,"",,"WALK_LOC",4.688080695799631,"no_subtours",,"0out_0in","work",307996508 -7512514,"work",1,1,1,1,"mandatory",1,9000,8000,2821179,172,18,21,3,"",,"WALK",5.16970893935428,"no_subtours",,"0out_0in","work",308013113 -7513432,"social",1,1,1,1,"non_mandatory",1,11000,8000,2822097,77,9,16,7,"",14.426345007668951,"WALK_LOC",1.828284029776891,"",,"0out_1in","social",308050748 -7513554,"work",1,1,1,1,"mandatory",1,9000,8000,2822219,96,10,21,11,"",,"TNC_SINGLE",5.634094342054246,"no_subtours",,"1out_0in","work",308055753 -7523517,"shopping",1,1,1,1,"non_mandatory",1,13000,7000,2832182,145,15,15,0,"",13.532091345687146,"WALK_LOC",1.2623216832302828,"",,"0out_0in","shopping",308464230 +3495342,"eat",1,1,1,1,"atwork",1,8000,8000,1594621,85,10,10,0,"",12.431032160366323,"WALK",6.584685335491401,"",143309061,"3out_0in","atwork",143309026 +3495342,"work",1,1,1,1,"mandatory",1,8000,10000,1594621,63,8,17,9,"",,"TNC_SINGLE",6.1798059555254525,"eat",,"0out_0in","work",143309061 +3495343,"shopping",1,1,1,1,"non_mandatory",1,11000,10000,1594621,146,15,16,1,"",11.21833943175268,"WALK",2.424167164896485,"",,"1out_1in","shopping",143309096 +3596364,"school",1,1,1,1,"mandatory",1,10000,9000,1645132,99,11,11,0,"",,"WALK",0.9033125989041192,"",,"0out_0in","univ",147450955 +3596364,"shopping",1,1,1,1,"non_mandatory",1,11000,9000,1645132,130,13,19,6,"",9.67513287780121,"WALK",-0.0777538798333834,"",,"0out_0in","shopping",147450957 +3596365,"school",1,1,1,1,"mandatory",1,8000,9000,1645132,92,10,17,7,"",,"WALK_LOC",0.3427235785809102,"",,"0out_2in","school",147450996 +3891102,"eat",1,1,1,1,"atwork",1,23000,15000,1747467,88,10,13,3,"",9.994441288395246,"WALK",-0.27371617134347487,"",159535221,"0out_1in","atwork",159535186 +3891102,"work",1,1,1,1,"mandatory",1,15000,16000,1747467,67,8,21,13,"",,"WALK",1.8965148103543004,"eat",,"1out_1in","work",159535221 +3891104,"othdiscr",1,1,1,1,"non_mandatory",1,21000,16000,1747467,52,7,22,15,"",11.782399737864871,"WALK",0.9497659766033424,"",,"0out_0in","othdiscr",159535289 +4171615,"school",1,1,1,1,"mandatory",1,14000,16000,1810015,169,18,18,0,"",,"WALK",3.386100724122899,"",,"0out_0in","univ",171036246 +4171616,"shopping",1,1,1,1,"non_mandatory",1,4000,16000,1810015,89,10,14,4,"",10.392717072129102,"WALK",1.0648124818146192,"",,"0out_0in","shopping",171036289 +4171617,"eat",1,1,1,1,"atwork",1,10000,13000,1810015,85,10,10,0,"",10.082153546316052,"WALK",-0.9480501387648468,"",171036336,"0out_1in","atwork",171036301 +4171617,"work",1,1,1,1,"mandatory",1,13000,16000,1810015,62,8,16,8,"",,"WALK",1.7563407832077071,"eat",,"0out_0in","work",171036336 +4171619,"othdiscr",1,1,1,1,"non_mandatory",1,15000,16000,1810015,80,9,19,10,"",11.487947423205323,"WALK",1.221353422877656,"",,"0out_0in","othdiscr",171036404 +4171622,"othmaint",1,1,1,1,"non_mandatory",1,9000,16000,1810015,100,11,12,1,"",11.122012604358106,"DRIVEALONEFREE",-0.8082104120689105,"",,"0out_0in","othmaint",171036530 +4823797,"work",1,1,1,1,"mandatory",1,2000,14000,1952792,93,10,18,8,"",,"WALK_LOC",5.550859155970048,"no_subtours",,"1out_0in","work",197775716 +5057160,"work",1,1,1,1,"mandatory",1,2000,5000,2048204,30,6,17,11,"",,"BIKE",-0.1471793698941122,"no_subtours",,"0out_0in","work",207343599 +5057338,"work",1,1,1,1,"mandatory",1,9000,7000,2048382,50,7,20,13,"",,"TNC_SINGLE",5.991736244240239,"no_subtours",,"0out_0in","work",207350897 +5387762,"work",1,1,1,1,"mandatory",1,2000,9000,2223027,28,6,15,9,"",,"WALK_LRF",1.9325923275483154,"no_subtours",,"0out_0in","work",220898281 +5387763,"eatout",1,1,2,2,"non_mandatory",1,5000,9000,2223027,154,16,16,0,"",11.013946837999834,"WALK",2.023230426729334,"",,"0out_0in","eatout",220898289 +5387763,"othdiscr",1,1,1,2,"non_mandatory",1,8000,9000,2223027,169,18,18,0,"",11.555366035758373,"WALK",1.7649215651163581,"",,"0out_0in","othdiscr",220898308 +5389226,"work",1,1,1,1,"mandatory",1,19000,16000,2223759,63,8,17,9,"",,"WALK",0.8926753131932247,"no_subtours",,"0out_0in","work",220958305 +5389227,"eat",1,1,1,1,"atwork",1,11000,2000,2223759,99,11,11,0,"",9.9093875692158,"WALK_LOC",-0.5906726326809321,"",220958346,"0out_0in","atwork",220958311 +5389227,"escort",1,1,1,1,"non_mandatory",1,4000,16000,2223759,162,17,17,0,"",9.642271576043063,"TNC_SINGLE",-0.13431708575512935,"",,"1out_0in","escort",220958316 +5389227,"work",1,1,1,1,"mandatory",1,2000,16000,2223759,28,6,15,9,"",,"WALK",1.8986167197326385,"eat",,"0out_0in","work",220958346 +7305540,"social",2,1,1,2,"non_mandatory",1,6000,20000,2727273,37,7,7,0,"",11.212441697992773,"DRIVEALONEFREE",1.6822348171556354,"",,"0out_0in","social",299527176 +7305540,"social",2,2,2,2,"non_mandatory",1,2000,20000,2727273,86,10,11,1,"",11.154124375407706,"DRIVEALONEFREE",1.5224456104374362,"",,"0out_1in","social",299527177 +7305540,"work",1,1,1,1,"mandatory",1,9000,20000,2727273,127,13,16,3,"",,"WALK_LOC",1.9458435557160327,"no_subtours",,"0out_3in","work",299527179 +7305541,"shopping",1,1,1,2,"non_mandatory",1,20000,20000,2727273,171,18,20,2,"",10.34361998244629,"TNC_SINGLE",1.7098607961042567,"",,"0out_0in","shopping",299527214 +7305541,"social",1,1,2,2,"non_mandatory",1,6000,20000,2727273,162,17,17,0,"",11.003513701202511,"WALK",1.6842447993162537,"",,"0out_0in","social",299527217 +7305541,"work",1,1,1,1,"mandatory",1,2000,20000,2727273,45,7,15,8,"",,"WALK_LRF",1.6733546844528127,"no_subtours",,"0out_0in","work",299527220 +7453413,"othmaint",1,1,1,1,"non_mandatory",1,9000,20000,2762078,102,11,14,3,"",12.130126308949702,"TAXI",2.107670623291728,"",,"0out_0in","othmaint",305589961 +7511873,"work",1,1,1,1,"mandatory",1,13000,8000,2820538,45,7,15,8,"",,"WALK",-0.9180280942803767,"no_subtours",,"0out_0in","work",307986832 +7512109,"work",1,1,1,1,"mandatory",1,16000,8000,2820774,48,7,18,11,"",,"WALK_LOC",4.682541648215299,"no_subtours",,"0out_0in","work",307996508 +7512514,"work",1,1,1,1,"mandatory",1,5000,8000,2821179,172,18,21,3,"",,"WALK",5.281889150266914,"no_subtours",,"0out_0in","work",308013113 +7513432,"social",1,1,1,1,"non_mandatory",1,4000,8000,2822097,77,9,16,7,"",11.505225455396152,"WALK_LOC",1.5488509589493435,"",,"0out_1in","social",308050748 +7513554,"work",1,1,1,1,"mandatory",1,5000,8000,2822219,96,10,21,11,"",,"WALK",5.6727002115033285,"no_subtours",,"0out_0in","work",308055753 +7523517,"shopping",1,1,1,1,"non_mandatory",1,11000,7000,2832182,145,15,15,0,"",10.679884744302576,"SHARED2FREE",1.4715068169852683,"",,"0out_0in","shopping",308464230 diff --git a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv index bd9250049b..d23ca7523b 100644 --- a/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv +++ b/activitysim/examples/placeholder_multiple_zone/test/regress/final_eet_trips_2_zone.csv @@ -1,254 +1,256 @@ "person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","depart","trip_mode","mode_choice_logsum","trip_id" -26686,26686,"shopping",1,true,1,23000,8000,1094159,"shopping",,12,"WALK_LOC",1.9827280669801086,8753273 -26686,26686,"shopping",1,false,1,8000,23000,1094159,"home",,13,"WALK",1.9037282242699007,8753277 -26844,26844,"othmaint",1,true,1,5000,8000,1100632,"othmaint",,16,"WALK",3.9354602248477737,8805057 -26844,26844,"othmaint",1,false,1,8000,5000,1100632,"home",,21,"WALK",3.9189147972940885,8805061 -26844,26844,"shopping",1,true,2,9000,8000,1100637,"shopping",31.37877712789407,9,"WALK_LOC",10.984274105698468,8805097 -26844,26844,"shopping",2,true,2,1000,9000,1100637,"shopping",,9,"TNC_SINGLE",1.1046643197551034,8805098 -26844,26844,"shopping",1,false,1,8000,1000,1100637,"home",,14,"TNC_SINGLE",0.9942441548836948,8805101 -27726,27726,"eatout",1,true,1,10000,10000,1136772,"eatout",,14,"WALK",10.041713156072243,9094177 -27726,27726,"eatout",1,false,1,10000,10000,1136772,"home",,19,"WALK",10.041713156072243,9094181 -110675,110675,"work",1,true,1,16000,16000,4537714,"work",,5,"WALK",5.450623197234638,36301713 -110675,110675,"work",1,false,1,16000,16000,4537714,"home",,18,"WALK",5.450623197234638,36301717 -112064,112064,"work",1,true,2,25000,16000,4594663,"work",35.8782468879873,13,"WALK_LOC",10.001126639886184,36757305 -112064,112064,"work",2,true,2,24000,25000,4594663,"work",,13,"WALK_LOC",2.809733442795069,36757306 -112064,112064,"work",1,false,2,7000,24000,4594663,"social",34.01849775944516,20,"WALK",2.147976347182729,36757309 -112064,112064,"work",2,false,2,16000,7000,4594663,"home",,20,"WALK_LOC",10.657184142644127,36757310 -264108,226869,"eatout",1,true,1,8000,9000,10828434,"eatout",,14,"WALK",12.045414087419827,86627473 -264108,226869,"eatout",1,false,1,9000,8000,10828434,"home",,14,"WALK",12.045414087420168,86627477 -323689,256660,"work",1,true,2,7000,10000,13271288,"work",32.636430904267925,15,"WALK",10.992779238372787,106170305 -323689,256660,"work",2,true,2,2000,7000,13271288,"work",,16,"WALK",0.07082651725982507,106170306 -323689,256660,"work",1,false,1,10000,2000,13271288,"home",,21,"WALK_LRF",0.4297270030393883,106170309 +26686,26686,"shopping",1,true,1,23000,8000,1094159,"shopping",,12,"BIKE",1.9330760825577051,8753273 +26686,26686,"shopping",1,false,1,8000,23000,1094159,"home",,13,"BIKE",1.8982724783359772,8753277 +26844,26844,"othmaint",1,true,1,9000,8000,1100632,"othmaint",,16,"WALK_LOC",7.835312649097291,8805057 +26844,26844,"othmaint",1,false,1,8000,9000,1100632,"home",,21,"WALK",7.81653044999465,8805061 +26844,26844,"shopping",1,true,2,25000,8000,1100637,"shopping",38.026340763211664,9,"WALK_LOC",12.899241414130254,8805097 +26844,26844,"shopping",2,true,2,5000,25000,1100637,"shopping",,9,"WALK_LOC",4.5422109121136645,8805098 +26844,26844,"shopping",1,false,1,8000,5000,1100637,"home",,14,"WALK_LOC",4.56041276868394,8805101 +27726,27726,"eatout",1,true,1,7000,10000,1136772,"eatout",,14,"WALK",15.095889363425728,9094177 +27726,27726,"eatout",1,false,1,10000,7000,1136772,"home",,19,"WALK_LOC",15.128642080173528,9094181 +110675,110675,"work",1,true,1,9000,16000,4537714,"work",,5,"WALK_LOC",7.767335299437604,36301713 +110675,110675,"work",1,false,1,16000,9000,4537714,"home",,18,"WALK",7.890352026555583,36301717 +112064,112064,"work",1,true,1,4000,16000,4594663,"work",,13,"WALK",-0.37789093286786735,36757305 +112064,112064,"work",1,false,1,16000,4000,4594663,"home",,20,"WALK",-0.4796293342858078,36757309 +264108,226869,"eatout",1,true,1,22000,9000,10828434,"eatout",,14,"WALK_LRF",2.721417809460901,86627473 +264108,226869,"eatout",1,false,1,9000,22000,10828434,"home",,14,"WALK_LRF",2.7335986783997748,86627477 +323689,256660,"work",1,true,2,9000,10000,13271288,"work",26.71162202422962,15,"WALK",8.691754255656912,106170305 +323689,256660,"work",2,true,2,13000,9000,13271288,"work",,16,"WALK_LRF",0.20306465808635524,106170306 +323689,256660,"work",1,false,1,10000,13000,13271288,"home",,21,"WALK_LRF",0.20307051532099407,106170309 323690,256660,"work",1,true,1,9000,10000,13271329,"work",,12,"WALK",8.030042498532831,106170633 -323690,256660,"work",1,false,2,9000,9000,13271329,"escort",41.699169508847675,16,"WALK",8.311642517531904,106170637 -323690,256660,"work",2,false,2,10000,9000,13271329,"home",,17,"WALK",8.170842512701038,106170638 -325431,257531,"othdiscr",1,true,1,22000,16000,13342696,"othdiscr",,11,"WALK",0.8780862049084773,106741569 -325431,257531,"othdiscr",1,false,3,8000,22000,13342696,"social",31.597406256890405,14,"WALK",-0.20976456497029897,106741573 -325431,257531,"othdiscr",2,false,3,7000,8000,13342696,"escort",55.615835217285294,14,"WALK",12.357894047797622,106741574 -325431,257531,"othdiscr",3,false,3,16000,7000,13342696,"home",,14,"WALK",12.573466152605935,106741575 -325431,257531,"work",1,true,1,14000,16000,13342710,"work",,16,"WALK_LOC",1.566089725814676,106741681 -325431,257531,"work",1,false,1,16000,14000,13342710,"home",,19,"WALK",1.5222029212145018,106741685 -325432,257531,"work",1,true,1,15000,16000,13342751,"work",,7,"WALK_LOC",1.5021356653376314,106742009 -325432,257531,"work",1,false,1,16000,15000,13342751,"home",,15,"WALK",1.4521916317367,106742013 -595684,370497,"escort",1,true,1,5000,21000,24423053,"escort",,7,"WALK",3.330906385141443,195384425 -595684,370497,"escort",1,false,1,21000,5000,24423053,"home",,8,"WALK",3.406854952912418,195384429 -595684,370497,"work",1,true,4,8000,21000,24423083,"shopping",26.255350098408414,17,"WALK",9.135886955872488,195384665 -595684,370497,"work",2,true,4,9000,8000,24423083,"work",28.05855070184786,18,"WALK",8.121041430209859,195384666 -595684,370497,"work",3,true,4,7000,9000,24423083,"work",31.847339393333378,18,"WALK",10.660973323175552,195384667 -595684,370497,"work",4,true,4,19000,7000,24423083,"work",,19,"WALK",-0.2788507765426208,195384668 -595684,370497,"work",1,false,1,21000,19000,24423083,"home",,22,"SHARED2FREE",-0.1573937557477881,195384669 -595685,370497,"school",1,true,1,13000,21000,24423116,"school",,8,"WALK",-0.8229268584487489,195384929 +323690,256660,"work",1,false,2,7000,9000,13271329,"escort",40.07647772051534,16,"WALK",7.8276424819654,106170637 +323690,256660,"work",2,false,2,10000,7000,13271329,"home",,17,"WALK",10.341361395870509,106170638 +325431,257531,"othdiscr",1,true,1,23000,16000,13342696,"othdiscr",,11,"WALK_LOC",2.7598523724663617,106741569 +325431,257531,"othdiscr",1,false,3,7000,23000,13342696,"social",37.393285866288224,14,"WALK_LOC",2.5300279098941387,106741573 +325431,257531,"othdiscr",2,false,3,6000,7000,13342696,"escort",57.570625507650206,14,"WALK_LOC",14.408109959489884,106741574 +325431,257531,"othdiscr",3,false,3,16000,6000,13342696,"home",,14,"WALK_LOC",12.327190588058823,106741575 +325431,257531,"work",1,true,1,1000,16000,13342710,"work",,16,"TNC_SINGLE",0.37779070103009205,106741681 +325431,257531,"work",1,false,1,16000,1000,13342710,"home",,19,"WALK_LOC",0.3421430227353672,106741685 +325432,257531,"work",1,true,1,17000,16000,13342751,"work",,7,"BIKE",4.532931609064337,106742009 +325432,257531,"work",1,false,1,16000,17000,13342751,"home",,15,"BIKE",4.495624422168687,106742013 +595684,370497,"escort",1,true,1,5000,21000,24423053,"escort",,7,"WALK",4.40185753136252,195384425 +595684,370497,"escort",1,false,1,21000,5000,24423053,"home",,8,"WALK_LOC",4.346352931873237,195384429 +595684,370497,"work",1,true,1,13000,21000,24423083,"work",,17,"WALK",-1.2617603816041787,195384665 +595684,370497,"work",1,false,1,21000,13000,24423083,"home",,22,"WALK",-1.2617599925977128,195384669 +595685,370497,"school",1,true,1,13000,21000,24423116,"school",,8,"WALK_LOC",-0.8229268584487489,195384929 595685,370497,"school",1,false,1,21000,13000,24423116,"home",,15,"WALK_LOC",-1.1046192102021704,195384933 -595686,370497,"school",1,true,1,21000,21000,24423157,"school",,7,"WALK",3.8962741508493144,195385257 -595686,370497,"school",1,false,1,21000,21000,24423157,"home",,11,"WALK",3.8962741508493144,195385261 -644292,386699,"school",1,true,1,2000,7000,26416003,"school",,5,"WALK",-0.7452680126547789,211328025 -644292,386699,"school",1,false,1,7000,2000,26416003,"home",,14,"WALK",-0.6585530704989658,211328029 -644476,386761,"work",1,true,1,7000,16000,26423555,"work",,7,"WALK_LOC",10.956012027742537,211388441 -644476,386761,"work",1,false,1,16000,7000,26423555,"home",,17,"WALK_LOC",10.843966812429903,211388445 -644477,386761,"work",1,true,1,4000,16000,26423596,"work",,8,"WALK",0.5554280270852756,211388769 -644477,386761,"work",1,false,3,8000,4000,26423596,"othdiscr",29.4847474381881,8,"WALK_LOC",0.39972663182606294,211388773 -644477,386761,"work",2,false,3,7000,8000,26423596,"shopping",47.722440883077674,18,"WALK_LOC",10.251476771714287,211388774 -644477,386761,"work",3,false,3,16000,7000,26423596,"home",,18,"WALK",10.843966918146847,211388775 -644478,386761,"school",1,true,1,25000,16000,26423629,"school",,7,"WALK_LOC",11.66210546356803,211389033 -644478,386761,"school",1,false,1,16000,25000,26423629,"home",,15,"WALK_LOC",11.638857607037355,211389037 -1267567,570454,"eatout",1,true,1,11000,21000,51970253,"eatout",,11,"WALK",4.940793809090325,415762025 -1267567,570454,"eatout",1,false,1,21000,11000,51970253,"home",,11,"WALK",4.9407938105296125,415762029 -1427193,703381,"shopping",1,true,1,16000,25000,58514946,"shopping",,15,"BIKE",6.640136269009802,468119569 -1427193,703381,"shopping",1,false,1,25000,16000,58514946,"home",,21,"BIKE",6.575179313692486,468119573 -1427194,703381,"othmaint",1,true,1,9000,25000,58514982,"othmaint",,9,"BIKE",6.353308735567675,468119857 -1427194,703381,"othmaint",1,false,1,25000,9000,58514982,"home",,13,"BIKE",6.3384404915582335,468119861 -1427194,703381,"othmaint",1,true,1,8000,25000,58514983,"othmaint",,16,"WALK",7.518240674377766,468119865 -1427194,703381,"othmaint",1,false,1,25000,8000,58514983,"home",,18,"BIKE",7.524697959231204,468119869 -1427194,703381,"othmaint",1,true,1,7000,25000,58514984,"othmaint",,18,"WALK",8.966945568005901,468119873 -1427194,703381,"othmaint",1,false,1,25000,7000,58514984,"home",,21,"BIKE",8.89909382789557,468119877 -1572659,763879,"othdiscr",1,true,1,7000,6000,64479044,"othdiscr",,5,"WALK",14.202826252443442,515832353 -1572659,763879,"othdiscr",1,false,1,6000,7000,64479044,"home",,13,"WALK",14.258626224164276,515832357 -1572930,764150,"eatout",1,true,1,12000,9000,64490136,"eatout",,7,"WALK",3.8127908930347663,515921089 -1572930,764150,"eatout",1,false,1,9000,12000,64490136,"home",,16,"WALK",3.7569912975474886,515921093 -1632206,823426,"work",1,true,1,12000,11000,66920485,"work",,7,"WALK",3.428915566265564,535363881 -1632206,823426,"work",1,false,1,11000,12000,66920485,"home",,18,"WALK",3.428915026154219,535363885 -1632281,823501,"work",1,true,1,1000,12000,66923560,"work",,8,"WALK",-1.1863064685276732,535388481 -1632281,823501,"work",1,false,1,12000,1000,66923560,"home",,18,"WALK",-1.2019248842710846,535388485 -1632987,824207,"atwork",1,true,1,13000,22000,66952471,"atwork",,11,"TNC_SINGLE",1.1982264470387332,535619769 -1632987,824207,"atwork",1,false,1,22000,13000,66952471,"work",,12,"WALK_LOC",1.1895157253486617,535619773 -1632987,824207,"work",1,true,1,22000,18000,66952506,"work",,7,"WALK_LRF",1.6456092779546538,535620049 -1632987,824207,"work",1,false,1,18000,22000,66952506,"home",,20,"WALK_LRF",1.7361065950351384,535620053 -1875721,982875,"work",1,true,1,4000,16000,76904600,"work",,7,"DRIVEALONEFREE",0.7076921143748968,615236801 -1875721,982875,"work",1,false,1,16000,4000,76904600,"home",,19,"WALK",-0.14334258561745436,615236805 -1875722,982875,"work",1,true,1,7000,16000,76904641,"work",,7,"WALK",9.804561421193789,615237129 -1875722,982875,"work",1,false,1,16000,7000,76904641,"home",,18,"WALK",9.584561383233053,615237133 -2159057,1099626,"work",1,true,1,11000,20000,88521376,"work",,7,"BIKE",3.40833863532139,708171009 -2159057,1099626,"work",1,false,1,20000,11000,88521376,"home",,17,"BIKE",3.370476902119967,708171013 -2159058,1099626,"univ",1,true,1,12000,20000,88521409,"univ",,7,"WALK_LOC",4.673210978795487,708171273 -2159058,1099626,"univ",1,false,1,20000,12000,88521409,"home",,14,"WALK_LOC",4.633766064553818,708171277 -2159059,1099626,"school",1,true,1,17000,20000,88521450,"school",,8,"WALK",2.5208933659011428,708171601 -2159059,1099626,"school",1,false,1,20000,17000,88521450,"home",,15,"WALK",2.4894161638956325,708171605 -2458500,1173905,"othdiscr",1,true,1,2000,8000,100798525,"othdiscr",,13,"WALK_LOC",-0.5086365848499728,806388201 -2458500,1173905,"othdiscr",1,false,1,8000,2000,100798525,"home",,15,"WALK_LOC",-0.5202541824471008,806388205 -2458502,1173905,"school",1,true,1,9000,8000,100798613,"school",,9,"WALK_LOC",9.584957165143996,806388905 -2458502,1173905,"school",1,false,1,8000,9000,100798613,"home",,15,"WALK",9.584141345328328,806388909 -2458503,1173905,"school",1,true,1,25000,8000,100798654,"school",,8,"WALK",11.502135863745265,806389233 -2458503,1173905,"school",1,false,1,8000,25000,100798654,"home",,17,"WALK",11.47503591488948,806389237 -2566698,1196298,"othmaint",1,true,1,12000,25000,105234646,"othmaint",,15,"WALK",2.7170806557365403,841877169 -2566698,1196298,"othmaint",1,false,1,25000,12000,105234646,"home",,16,"WALK",2.668082316887066,841877173 -2566698,1196298,"work",1,true,2,7000,25000,105234657,"work",46.368886774827516,7,"WALK",10.628886549023708,841877257 -2566698,1196298,"work",2,true,2,9000,7000,105234657,"work",,8,"WALK",7.921845595405086,841877258 -2566698,1196298,"work",1,false,3,7000,9000,105234657,"work",46.219913256688756,11,"WALK",8.04493677276671,841877261 -2566698,1196298,"work",2,false,3,7000,7000,105234657,"eatout",50.18591828103494,12,"WALK",11.253671604257528,841877262 -2566698,1196298,"work",3,false,3,25000,7000,105234657,"home",,12,"WALK",10.4836804085622,841877263 -2566699,1196298,"escort",1,true,1,9000,25000,105234668,"escort",,8,"WALK",7.464032001764529,841877345 -2566699,1196298,"escort",1,false,1,25000,9000,105234668,"home",,9,"WALK",7.380265288148372,841877349 -2566699,1196298,"escort",1,true,1,2000,25000,105234669,"escort",,12,"WALK",0.42979489937273185,841877353 -2566699,1196298,"escort",1,false,1,25000,2000,105234669,"home",,12,"DRIVEALONEFREE",0.39426362374490925,841877357 -2566699,1196298,"othdiscr",1,true,1,5000,25000,105234684,"othdiscr",,18,"WALK",4.44638450975239,841877473 -2566699,1196298,"othdiscr",1,false,1,25000,5000,105234684,"home",,21,"WALK",4.078106936734674,841877477 -2566699,1196298,"shopping",1,true,1,17000,25000,105234692,"shopping",,9,"WALK_LRF",5.427479653195204,841877537 -2566699,1196298,"shopping",1,false,1,25000,17000,105234692,"home",,10,"WALK_LOC",5.657997593724093,841877541 -2566700,1196298,"school",1,true,1,19000,25000,105234731,"school",,8,"BIKE",-1.581700286838991,841877849 -2566700,1196298,"school",1,false,1,25000,19000,105234731,"home",,15,"BIKE",-1.6089370260217823,841877853 -2566701,1196298,"escort",1,true,1,13000,25000,105234750,"escort",,13,"SHARED2FREE",-0.03166986394959175,841878001 -2566701,1196298,"escort",1,false,1,25000,13000,105234750,"home",,13,"SHARED2FREE",-0.05985983186201032,841878005 -2566701,1196298,"school",1,true,1,11000,25000,105234772,"school",,7,"WALK",2.362709080528027,841878177 -2566701,1196298,"school",1,false,1,25000,11000,105234772,"home",,13,"WALK",2.1942892427799245,841878181 -2566702,1196298,"othdiscr",1,true,1,9000,25000,105234807,"othdiscr",,18,"WALK",8.443967865601556,841878457 -2566702,1196298,"othdiscr",1,false,3,8000,9000,105234807,"othdiscr",50.54253049075866,20,"WALK",10.530636255741943,841878461 -2566702,1196298,"othdiscr",2,false,3,7000,8000,105234807,"escort",57.021060151258915,20,"WALK",12.331097986979803,841878462 +595686,370497,"school",1,true,1,16000,21000,24423157,"school",,7,"WALK_LOC",5.947341073015701,195385257 +595686,370497,"school",1,false,1,21000,16000,24423157,"home",,11,"WALK_LOC",5.8497465236911,195385261 +644292,386699,"school",1,true,1,10000,7000,26416003,"school",,5,"WALK_LRF",10.365229803761082,211328025 +644292,386699,"school",1,false,1,7000,10000,26416003,"home",,14,"WALK_LOC",9.139539939236254,211328029 +644476,386761,"work",1,true,1,16000,16000,26423555,"work",,7,"WALK",5.4506235318233465,211388441 +644476,386761,"work",1,false,1,16000,16000,26423555,"home",,17,"WALK",5.450623534940713,211388445 +644477,386761,"work",1,true,1,2000,16000,26423596,"work",,8,"WALK_LOC",0.49488458947717534,211388769 +644477,386761,"work",1,false,3,7000,2000,26423596,"othdiscr",28.40400355134066,8,"WALK",0.3968904431297342,211388773 +644477,386761,"work",2,false,3,7000,7000,26423596,"shopping",46.92412760009899,18,"WALK",11.479761687899988,211388774 +644477,386761,"work",3,false,3,16000,7000,26423596,"home",,18,"WALK_LOC",10.843966918146847,211388775 +644478,386761,"school",1,true,1,16000,16000,26423629,"school",,7,"WALK",6.714175467733903,211389033 +644478,386761,"school",1,false,1,16000,16000,26423629,"home",,15,"WALK",6.714175468282777,211389037 +1267567,570454,"eatout",1,true,1,5000,21000,51970253,"eatout",,11,"WALK",4.267825059089583,415762025 +1267567,570454,"eatout",1,false,1,21000,5000,51970253,"home",,11,"WALK",4.3682644959903065,415762029 +1427193,703381,"shopping",1,true,1,11000,25000,58514946,"shopping",,15,"WALK",3.908490482022162,468119569 +1427193,703381,"shopping",1,false,1,25000,11000,58514946,"home",,21,"WALK",3.7410903539902405,468119573 +1427194,703381,"othmaint",1,true,1,2000,25000,58514982,"othmaint",,9,"WALK",0.4560043077103118,468119857 +1427194,703381,"othmaint",1,false,1,25000,2000,58514982,"home",,13,"WALK",0.39307251819952954,468119861 +1427194,703381,"othmaint",1,true,1,9000,25000,58514983,"othmaint",,16,"BIKE",6.353308735471027,468119865 +1427194,703381,"othmaint",1,false,1,25000,9000,58514983,"home",,18,"BIKE",6.338440491658596,468119869 +1427194,703381,"othmaint",1,true,1,4000,25000,58514984,"othmaint",,18,"WALK",0.06675616330986604,468119873 +1427194,703381,"othmaint",1,false,1,25000,4000,58514984,"home",,21,"WALK",-0.013609664361095805,468119877 +1572659,763879,"othdiscr",1,true,1,5000,6000,64479044,"othdiscr",,5,"WALK",4.6361033109279965,515832353 +1572659,763879,"othdiscr",1,false,1,6000,5000,64479044,"home",,13,"WALK",4.479864006955085,515832357 +1572930,764150,"eatout",1,true,1,7000,9000,64490136,"eatout",,7,"WALK",13.778746190542957,515921089 +1572930,764150,"eatout",1,false,1,9000,7000,64490136,"home",,16,"WALK",13.622506184583505,515921093 +1632206,823426,"work",1,true,1,2000,11000,66920485,"work",,7,"BIKE",-0.28522129668359636,535363881 +1632206,823426,"work",1,false,1,11000,2000,66920485,"home",,18,"BIKE",-0.2955629257504098,535363885 +1632281,823501,"work",1,true,1,5000,12000,66923560,"work",,8,"WALK",4.202625417780758,535388481 +1632281,823501,"work",1,false,1,12000,5000,66923560,"home",,18,"WALK",4.038764733249729,535388485 +1632987,824207,"atwork",1,true,1,1000,2000,66952471,"atwork",,11,"WALK",-0.6493719063222496,535619769 +1632987,824207,"atwork",1,false,1,2000,1000,66952471,"work",,12,"WALK",-0.5976340036034059,535619773 +1632987,824207,"work",1,true,1,2000,18000,66952506,"work",,7,"WALK",-1.6273811422831237,535620049 +1632987,824207,"work",1,false,1,18000,2000,66952506,"home",,20,"WALK",-1.6493420285420004,535620053 +1875721,982875,"work",1,true,1,16000,16000,76904600,"work",,7,"WALK",5.450623743648392,615236801 +1875721,982875,"work",1,false,1,16000,16000,76904600,"home",,19,"WALK",5.45062374684539,615236805 +1875722,982875,"work",1,true,1,2000,16000,76904641,"work",,7,"TNC_SHARED",0.5145638545713905,615237129 +1875722,982875,"work",1,false,1,16000,2000,76904641,"home",,18,"WALK",0.4575440509718732,615237133 +2159057,1099626,"work",1,true,1,15000,20000,88521376,"work",,7,"WALK_LOC",0.8040027972334238,708171009 +2159057,1099626,"work",1,false,1,20000,15000,88521376,"home",,17,"WALK_LOC",0.7614683739232305,708171013 +2159058,1099626,"univ",1,true,1,10000,20000,88521409,"univ",,7,"WALK",8.007783984850628,708171273 +2159058,1099626,"univ",1,false,1,20000,10000,88521409,"home",,14,"WALK",8.06183233206498,708171277 +2159059,1099626,"school",1,true,1,10000,20000,88521450,"school",,8,"WALK",9.06543923174178,708171601 +2159059,1099626,"school",1,false,1,20000,10000,88521450,"home",,15,"WALK",9.119639179963317,708171605 +2458500,1173905,"othdiscr",1,true,1,1000,8000,100798525,"othdiscr",,13,"WALK",-1.5411007588568488,806388201 +2458500,1173905,"othdiscr",1,false,1,8000,1000,100798525,"home",,15,"WALK",-1.7195836652494862,806388205 +2458502,1173905,"school",1,true,1,7000,8000,100798613,"school",,9,"WALK",13.486637386215971,806388905 +2458502,1173905,"school",1,false,1,8000,7000,100798613,"home",,15,"WALK",13.204797391017513,806388909 +2458503,1173905,"school",1,true,2,16000,8000,100798654,"shopping",17.809084955388304,8,"WALK",5.517546221609351,806389233 +2458503,1173905,"school",2,true,2,18000,16000,100798654,"school",,8,"WALK_LOC",-0.07144513930505698,806389234 +2458503,1173905,"school",1,false,3,7000,18000,100798654,"othdiscr",30.67993027539147,17,"WALK_LOC",-0.15250090963444055,806389237 +2458503,1173905,"school",2,false,3,6000,7000,100798654,"othmaint",51.20578686432127,17,"WALK",12.79753441878666,806389238 +2458503,1173905,"school",3,false,3,8000,6000,100798654,"home",,17,"WALK",11.164784288930237,806389239 +2566698,1196298,"othmaint",1,true,1,2000,25000,105234646,"othmaint",,15,"WALK",0.13883447352916709,841877169 +2566698,1196298,"othmaint",1,false,1,25000,2000,105234646,"home",,16,"WALK",0.040885576652949784,841877173 +2566698,1196298,"work",1,true,1,13000,25000,105234657,"work",,7,"WALK",-1.2760866344600816,841877257 +2566698,1196298,"work",1,false,1,25000,13000,105234657,"home",,12,"WALK",-1.441002585134111,841877261 +2566699,1196298,"escort",1,true,1,9000,25000,105234668,"escort",,8,"WALK",6.419710427462857,841877345 +2566699,1196298,"escort",1,false,1,25000,9000,105234668,"home",,9,"WALK",6.335788330611109,841877349 +2566699,1196298,"escort",1,true,1,11000,25000,105234669,"escort",,12,"WALK_LOC",5.288975343463499,841877353 +2566699,1196298,"escort",1,false,1,25000,11000,105234669,"home",,12,"WALK_LOC",4.8173139183499485,841877357 +2566699,1196298,"othdiscr",1,true,1,2000,25000,105234684,"othdiscr",,18,"TNC_SINGLE",0.4693341559425369,841877473 +2566699,1196298,"othdiscr",1,false,1,25000,2000,105234684,"home",,21,"TNC_SINGLE",0.08030571790736232,841877477 +2566699,1196298,"shopping",1,true,1,2000,25000,105234692,"shopping",,9,"WALK",0.2207063663573701,841877537 +2566699,1196298,"shopping",1,false,1,25000,2000,105234692,"home",,10,"WALK",0.06452287253443097,841877541 +2566700,1196298,"school",1,true,1,7000,25000,105234731,"school",,8,"WALK_LOC",13.12890770099686,841877849 +2566700,1196298,"school",1,false,1,25000,7000,105234731,"home",,15,"WALK",12.572656390759992,841877853 +2566701,1196298,"escort",1,true,1,22000,25000,105234750,"escort",,13,"WALK",1.097784136547436,841878001 +2566701,1196298,"escort",1,false,1,25000,22000,105234750,"home",,13,"WALK",1.005415443289966,841878005 +2566701,1196298,"school",1,true,1,21000,25000,105234772,"school",,7,"WALK",1.2903956070849174,841878177 +2566701,1196298,"school",1,false,1,25000,21000,105234772,"home",,13,"WALK",0.6837244960300443,841878181 +2566702,1196298,"othdiscr",1,true,1,18000,25000,105234807,"othdiscr",,18,"SHARED2FREE",0.48057507554186435,841878457 +2566702,1196298,"othdiscr",1,false,3,6000,18000,105234807,"othdiscr",29.962789198733248,20,"SHARED2FREE",0.19481120111371147,841878461 +2566702,1196298,"othdiscr",2,false,3,7000,6000,105234807,"escort",54.46060393017329,20,"WALK",12.68589169990383,841878462 2566702,1196298,"othdiscr",3,false,3,25000,7000,105234807,"home",,20,"WALK",13.411567550566136,841878463 -2936848,1286557,"eatout",1,true,1,22000,11000,120410774,"eatout",,13,"WALK",-0.37744649738261987,963286193 -2936848,1286557,"eatout",1,false,1,11000,22000,120410774,"home",,17,"WALK",-0.12636613590700893,963286197 -2936848,1286557,"othmaint",1,true,3,25000,11000,120410796,"othmaint",33.53386230240779,8,"WALK_LOC",8.71506906881393,963286369 -2936848,1286557,"othmaint",2,true,3,7000,25000,120410796,"othmaint",35.15663604383877,9,"WALK_LOC",9.599397367555662,963286370 -2936848,1286557,"othmaint",3,true,3,5000,7000,120410796,"othmaint",,9,"WALK",3.8856993443502486,963286371 -2936848,1286557,"othmaint",1,false,2,8000,5000,120410796,"shopping",32.12692001802512,13,"WALK_LOC",3.8136019897435065,963286373 -2936848,1286557,"othmaint",2,false,2,11000,8000,120410796,"home",,13,"WALK",8.624638911562878,963286374 -2936848,1286557,"shopping",1,true,1,11000,11000,120410801,"shopping",,18,"WALK",5.186311186707391,963286409 -2936848,1286557,"shopping",1,false,3,7000,11000,120410801,"othdiscr",44.33949605364542,18,"WALK",4.706431251058834,963286413 -2936848,1286557,"shopping",2,false,3,7000,7000,120410801,"escort",61.6886580104208,19,"WALK",14.414866203326692,963286414 -2936848,1286557,"shopping",3,false,3,11000,7000,120410801,"home",,19,"WALK",13.856866219908829,963286415 -3061894,1363467,"othmaint",1,true,1,5000,24000,125537682,"othmaint",,8,"WALK_LOC",3.73725010300736,1004301457 -3061894,1363467,"othmaint",1,false,1,24000,5000,125537682,"home",,17,"WALK",3.7305842343863196,1004301461 -3061894,1363467,"shopping",1,true,2,7000,24000,125537687,"othmaint",43.73819861241678,8,"WALK_LOC",13.495011906378089,1004301497 -3061894,1363467,"shopping",2,true,2,5000,7000,125537687,"shopping",,8,"WALK_LOC",4.355163009411816,1004301498 -3061894,1363467,"shopping",1,false,2,9000,5000,125537687,"eatout",42.95816610580615,8,"WALK_LOC",4.348166044302983,1004301501 -3061894,1363467,"shopping",2,false,2,24000,9000,125537687,"home",,8,"WALK_HVY",10.804085780654413,1004301502 -3061895,1363467,"othmaint",1,true,1,22000,24000,125537723,"othmaint",,20,"WALK",2.1198382474364728,1004301785 -3061895,1363467,"othmaint",1,false,1,24000,22000,125537723,"home",,20,"WALK",2.2450248366859453,1004301789 -3061895,1363467,"shopping",1,true,2,25000,24000,125537728,"shopping",35.38452370877508,8,"WALK_LOC",12.887343785241573,1004301825 -3061895,1363467,"shopping",2,true,2,4000,25000,125537728,"shopping",,11,"WALK_LOC",0.8165311300377738,1004301826 -3061895,1363467,"shopping",1,false,1,24000,4000,125537728,"home",,20,"WALK_LOC",0.8266547678720484,1004301829 -3188483,1402945,"othmaint",1,true,1,3000,25000,130727831,"othmaint",,12,"WALK",6.049470656726249,1045822649 -3188483,1402945,"othmaint",1,false,1,25000,3000,130727831,"home",,12,"WALK",6.009290681658803,1045822653 -3188483,1402945,"shopping",1,true,1,14000,25000,130727836,"shopping",,14,"BIKE",0.6559860145760328,1045822689 -3188483,1402945,"shopping",1,false,1,25000,14000,130727836,"home",,15,"BIKE",0.5716281146927128,1045822693 -3188484,1402945,"work",1,true,1,21000,25000,130727883,"work",,15,"DRIVEALONEFREE",2.2611057836224124,1045823065 -3188484,1402945,"work",1,false,1,25000,21000,130727883,"home",,17,"WALK",1.79496008180406,1045823069 -3188485,1402945,"work",1,true,1,5000,25000,130727924,"work",,8,"WALK",3.176125996500932,1045823393 -3188485,1402945,"work",1,false,1,25000,5000,130727924,"home",,18,"WALK",2.885731258806822,1045823397 -3232955,1444715,"escort",1,true,1,7000,14000,132551164,"escort",,17,"WALK",13.438366259007205,1060409313 -3232955,1444715,"escort",1,false,1,14000,7000,132551164,"home",,19,"WALK",13.187266160073705,1060409317 -3232955,1444715,"work",1,true,1,22000,14000,132551194,"work",,6,"WALK",0.5603936172504794,1060409553 -3232955,1444715,"work",1,false,1,14000,22000,132551194,"home",,11,"WALK",0.8331864635175756,1060409557 -3232955,1444715,"work",1,true,1,22000,14000,132551195,"work",,13,"WALK",1.649789344008908,1060409561 -3232955,1444715,"work",1,false,1,14000,22000,132551195,"home",,16,"WALK_LOC",1.802140999072509,1060409565 -3233462,1445222,"atwork",1,true,1,5000,19000,132571946,"atwork",,9,"WALK",3.555384833111897,1060575569 -3233462,1445222,"atwork",1,false,2,10000,5000,132571946,"work",40.81104535324782,9,"WALK",5.09581492505858,1060575573 -3233462,1445222,"atwork",2,false,2,19000,10000,132571946,"work",,9,"WALK",10.432166529592788,1060575574 -3233462,1445222,"work",1,true,1,19000,17000,132571981,"work",,9,"DRIVEALONEFREE",-0.12604572730314542,1060575849 -3233462,1445222,"work",1,false,4,7000,19000,132571981,"work",26.29280923831885,17,"DRIVEALONEFREE",-0.7072010889663861,1060575853 -3233462,1445222,"work",2,false,4,6000,7000,132571981,"othmaint",42.469375553808376,17,"WALK",10.073578746267579,1060575854 -3233462,1445222,"work",3,false,4,7000,6000,132571981,"escort",40.40244821152917,17,"WALK",8.854601045166811,1060575855 -3233462,1445222,"work",4,false,4,17000,7000,132571981,"home",,20,"WALK",8.238896211056357,1060575856 -3328568,1511234,"work",1,true,1,22000,8000,136471327,"work",,7,"WALK_LRF",2.0129407133763646,1091770617 -3328568,1511234,"work",1,false,1,8000,22000,136471327,"home",,16,"WALK_LRF",2.013498720638361,1091770621 +2936848,1286557,"eatout",1,true,1,15000,11000,120410774,"eatout",,13,"WALK",0.38682460851208106,963286193 +2936848,1286557,"eatout",1,false,1,11000,15000,120410774,"home",,17,"WALK",0.3868207009379531,963286197 +2936848,1286557,"othmaint",1,true,3,8000,11000,120410796,"othmaint",38.906052390545284,8,"BIKE",7.868237914778634,963286369 +2936848,1286557,"othmaint",2,true,3,6000,8000,120410796,"othmaint",41.824444468196404,9,"BIKE",8.223732026668186,963286370 +2936848,1286557,"othmaint",3,true,3,7000,6000,120410796,"othmaint",,9,"WALK",9.199284646290385,963286371 +2936848,1286557,"othmaint",1,false,2,8000,7000,120410796,"shopping",38.16816389072188,13,"BIKE",9.122309127594738,963286373 +2936848,1286557,"othmaint",2,false,2,11000,8000,120410796,"home",,13,"BIKE",7.826904862376488,963286374 +2936848,1286557,"shopping",1,true,1,13000,11000,120410801,"shopping",,18,"TNC_SHARED",-0.5434875036210606,963286409 +2936848,1286557,"shopping",1,false,3,7000,13000,120410801,"othdiscr",31.130726198006485,18,"WALK_LOC",-0.610095374219108,963286413 +2936848,1286557,"shopping",2,false,3,8000,7000,120410801,"escort",53.26307947714522,19,"WALK_LOC",13.353581103659465,963286414 +2936848,1286557,"shopping",3,false,3,11000,8000,120410801,"home",,19,"WALK_LOC",11.81225948785342,963286415 +3061894,1363467,"othmaint",1,true,1,14000,24000,125537682,"othmaint",,8,"WALK_LOC",1.2926814279364989,1004301457 +3061894,1363467,"othmaint",1,false,2,25000,14000,125537682,"shopping",23.27118460644273,15,"WALK_LOC",1.135322813455107,1004301461 +3061894,1363467,"othmaint",2,false,2,24000,25000,125537682,"home",,17,"WALK_LOC",8.162361973873145,1004301462 +3061894,1363467,"shopping",1,true,1,22000,24000,125537687,"shopping",,8,"WALK",1.3300460709588522,1004301497 +3061894,1363467,"shopping",1,false,1,24000,22000,125537687,"home",,8,"WALK",1.6648164345642997,1004301501 +3061895,1363467,"othmaint",1,true,1,9000,24000,125537723,"othmaint",,20,"WALK",7.61165430783887,1004301785 +3061895,1363467,"othmaint",1,false,1,24000,9000,125537723,"home",,20,"WALK_LRF",7.443046334668142,1004301789 +3061895,1363467,"shopping",1,true,2,16000,24000,125537728,"shopping",32.10251685845653,8,"WALK",6.0140004448335835,1004301825 +3061895,1363467,"shopping",2,true,2,16000,16000,125537728,"shopping",,11,"WALK",7.330879678856341,1004301826 +3061895,1363467,"shopping",1,false,1,24000,16000,125537728,"home",,20,"WALK",6.516200014628165,1004301829 +3188483,1402945,"othmaint",1,true,1,9000,25000,130727831,"othmaint",,12,"WALK",4.0387743765186475,1045822649 +3188483,1402945,"othmaint",1,false,4,9000,9000,130727831,"eatout",25.271301119734684,12,"WALK",5.580349147172738,1045822653 +3188483,1402945,"othmaint",2,false,4,25000,9000,130727831,"shopping",26.70578569196363,12,"WALK",3.983411662412696,1045822654 +3188483,1402945,"othmaint",3,false,4,25000,25000,130727831,"eatout",33.298624921893136,12,"WALK",7.245792741191272,1045822655 +3188483,1402945,"othmaint",4,false,4,25000,25000,130727831,"home",,12,"WALK",7.24592550108157,1045822656 +3188483,1402945,"shopping",1,true,1,12000,25000,130727836,"shopping",,14,"WALK",4.331730075378117,1045822689 +3188483,1402945,"shopping",1,false,1,25000,12000,130727836,"home",,15,"WALK",4.253610414457722,1045822693 +3188484,1402945,"work",1,true,1,11000,25000,130727883,"work",,15,"WALK",2.7519908409931944,1045823065 +3188484,1402945,"work",1,false,1,25000,11000,130727883,"home",,17,"WALK",2.619994257003586,1045823069 +3188485,1402945,"work",1,true,1,7000,25000,130727924,"work",,8,"WALK",10.411761425965901,1045823393 +3188485,1402945,"work",1,false,1,25000,7000,130727924,"home",,18,"WALK",10.266561483041759,1045823397 +3232955,1444715,"escort",1,true,1,8000,14000,132551164,"escort",,17,"WALK",8.966006396739564,1060409313 +3232955,1444715,"escort",1,false,1,14000,8000,132551164,"home",,19,"WALK",9.13332128431259,1060409317 +3232955,1444715,"work",1,true,1,1000,14000,132551194,"work",,6,"WALK",-0.9468267165438904,1060409553 +3232955,1444715,"work",1,false,1,14000,1000,132551194,"home",,11,"WALK",-1.0200727136755097,1060409557 +3232955,1444715,"work",1,true,1,1000,14000,132551195,"work",,13,"WALK",-0.9468284618667532,1060409561 +3232955,1444715,"work",1,false,1,14000,1000,132551195,"home",,16,"WALK",-1.0200756251053722,1060409565 +3233462,1445222,"atwork",1,true,1,15000,16000,132571946,"atwork",,9,"WALK",1.1096520956877534,1060575569 +3233462,1445222,"atwork",1,false,2,7000,15000,132571946,"work",34.31459609686081,9,"WALK",0.6337462548517271,1060575573 +3233462,1445222,"atwork",2,false,2,16000,7000,132571946,"work",,9,"WALK",12.937466290891889,1060575574 +3233462,1445222,"work",1,true,1,16000,17000,132571981,"work",,9,"WALK",5.267781294213782,1060575849 +3233462,1445222,"work",1,false,4,6000,16000,132571981,"work",32.16677731515871,17,"WALK",4.30790051405025,1060575853 +3233462,1445222,"work",2,false,4,7000,6000,132571981,"othmaint",42.7646950227243,17,"WALK",9.911469636687714,1060575854 +3233462,1445222,"work",3,false,4,7000,7000,132571981,"escort",44.1873922785174,17,"WALK",11.253667681197784,1060575855 +3233462,1445222,"work",4,false,4,17000,7000,132571981,"home",,20,"WALK",9.295722166835667,1060575856 +3328568,1511234,"work",1,true,1,13000,8000,136471327,"work",,8,"WALK_LOC",-0.2435902544788206,1091770617 +3328568,1511234,"work",1,false,2,7000,13000,136471327,"escort",27.606976204031863,16,"WALK",-0.20149926925026043,1091770621 +3328568,1511234,"work",2,false,2,8000,7000,136471327,"home",,22,"WALK",11.478676207869436,1091770622 3328569,1511234,"univ",1,true,1,9000,8000,136471360,"univ",,8,"WALK_LOC",10.078747551647542,1091770881 -3328569,1511234,"univ",1,false,1,8000,9000,136471360,"home",,16,"WALK_LOC",10.077775953315786,1091770885 -3495342,1594621,"atwork",1,true,4,9000,11000,143309026,"escort",52.586546235089074,10,"WALK",10.660093773427764,1146472209 -3495342,1594621,"atwork",2,true,4,7000,9000,143309026,"eatout",54.81382092908523,10,"WALK",14.040826305510333,1146472210 -3495342,1594621,"atwork",3,true,4,9000,7000,143309026,"eatout",52.150762754342416,10,"WALK",10.50217377147708,1146472211 -3495342,1594621,"atwork",4,true,4,9000,9000,143309026,"atwork",,10,"WALK",11.021053654094953,1146472212 -3495342,1594621,"atwork",1,false,1,11000,9000,143309026,"work",,10,"WALK",10.675133776355615,1146472213 -3495342,1594621,"work",1,true,1,11000,10000,143309061,"work",,8,"WALK",4.542307812891537,1146472489 -3495342,1594621,"work",1,false,1,10000,11000,143309061,"home",,17,"WALK",4.540040319156421,1146472493 -3495343,1594621,"shopping",1,true,2,8000,10000,143309096,"eatout",44.07715065746949,15,"WALK_LOC",12.334958131287545,1146472769 -3495343,1594621,"shopping",2,true,2,21000,8000,143309096,"shopping",,15,"WALK_LOC",4.754322605614275,1146472770 -3495343,1594621,"shopping",1,false,2,3000,21000,143309096,"shopping",35.64487169509503,16,"WALK_LOC",4.579037479011556,1146472773 -3495343,1594621,"shopping",2,false,2,10000,3000,143309096,"home",,16,"WALK_LRF",9.834778533473422,1146472774 -3596364,1645132,"univ",1,true,1,9000,9000,147450955,"univ",,11,"WALK",10.238432625148134,1179607641 -3596364,1645132,"univ",1,false,1,9000,9000,147450955,"home",,11,"WALK",10.238432625148134,1179607645 -3596364,1645132,"shopping",1,true,1,2000,9000,147450957,"shopping",,13,"WALK_LRF",0.8285401596132209,1179607657 -3596364,1645132,"shopping",1,false,1,9000,2000,147450957,"home",,19,"WALK_LRF",0.8244659836149215,1179607661 -3596365,1645132,"school",1,true,1,9000,9000,147450996,"school",,10,"WALK",10.238432621193224,1179607969 -3596365,1645132,"school",1,false,3,25000,9000,147450996,"shopping",42.28625449659161,17,"WALK",7.7398125409163105,1179607973 -3596365,1645132,"school",2,false,3,6000,25000,147450996,"othmaint",53.965718905093475,17,"WALK",12.120015882476332,1179607974 -3596365,1645132,"school",3,false,3,9000,6000,147450996,"home",,17,"WALK",11.33462804101704,1179607975 -3891102,1747467,"atwork",1,true,1,4000,1000,159535186,"atwork",,10,"WALK",0.45813980969805285,1276281489 -3891102,1747467,"atwork",1,false,2,25000,4000,159535186,"eatout",34.80381527769826,13,"WALK",0.2084762800360644,1276281493 -3891102,1747467,"atwork",2,false,2,1000,25000,159535186,"work",,13,"WALK",13.371530528904229,1276281494 -3891102,1747467,"work",1,true,2,7000,16000,159535221,"escort",30.09854287915041,8,"WALK",11.099112493958854,1276281769 -3891102,1747467,"work",2,true,2,1000,7000,159535221,"work",,10,"WALK",-0.14398137208511666,1276281770 -3891102,1747467,"work",1,false,2,6000,1000,159535221,"shopping",26.473055121441792,17,"WALK",-0.29357283995282485,1276281773 -3891102,1747467,"work",2,false,2,16000,6000,159535221,"home",,21,"WALK_LOC",9.754298157016313,1276281774 -3891104,1747467,"othdiscr",1,true,1,17000,16000,159535289,"othdiscr",,7,"WALK",5.9419631748312804,1276282313 -3891104,1747467,"othdiscr",1,false,1,16000,17000,159535289,"home",,22,"WALK",5.85268329864962,1276282317 -4171615,1810015,"univ",1,true,1,13000,16000,171036246,"univ",,18,"TNC_SHARED",-0.752316656547577,1368289969 -4171615,1810015,"univ",1,false,1,16000,13000,171036246,"home",,18,"WALK_LOC",-0.7196572448668817,1368289973 -4171616,1810015,"shopping",1,true,1,14000,16000,171036289,"shopping",,10,"WALK",1.0944891382847346,1368290313 -4171616,1810015,"shopping",1,false,1,16000,14000,171036289,"home",,14,"WALK",0.9382513291595582,1368290317 -4171617,1810015,"atwork",1,true,1,5000,11000,171036301,"atwork",,10,"WALK",4.630819623213244,1368290409 -4171617,1810015,"atwork",1,false,2,6000,5000,171036301,"escort",44.69367598003321,10,"WALK",4.690979682144286,1368290413 -4171617,1810015,"atwork",2,false,2,11000,6000,171036301,"work",,10,"WALK",12.460449128670621,1368290414 -4171617,1810015,"work",1,true,1,11000,16000,171036336,"work",,8,"WALK",2.7959725074724067,1368290689 -4171617,1810015,"work",1,false,1,16000,11000,171036336,"home",,16,"WALK",2.7959727582005645,1368290693 -4171619,1810015,"othdiscr",1,true,1,16000,16000,171036404,"othdiscr",,9,"WALK",7.330879449163802,1368291233 -4171619,1810015,"othdiscr",1,false,1,16000,16000,171036404,"home",,19,"WALK",7.330879449572421,1368291237 -4171622,1810015,"othmaint",1,true,1,2000,16000,171036530,"othmaint",,11,"TNC_SINGLE",0.05700613810224191,1368292241 -4171622,1810015,"othmaint",1,false,1,16000,2000,171036530,"home",,12,"TNC_SHARED",0.12318961832260422,1368292245 -4823797,1952792,"work",1,true,1,2000,14000,197775716,"work",,10,"WALK",-0.12977069563455434,1582205729 -4823797,1952792,"work",1,false,1,14000,2000,197775716,"home",,18,"WALK",-0.270575833053774,1582205733 -5057160,2048204,"work",1,true,1,5000,5000,207343599,"work",,6,"BIKE",3.570867061441779,1658748793 -5057160,2048204,"work",1,false,1,5000,5000,207343599,"home",,17,"BIKE",3.570867061441779,1658748797 -5057338,2048382,"work",1,true,1,17000,7000,207350897,"work",,7,"WALK",4.42461238620194,1658807177 -5057338,2048382,"work",1,false,1,7000,17000,207350897,"home",,20,"WALK_LOC",4.387261966488915,1658807181 -5387762,2223027,"work",1,true,1,10000,9000,220898281,"work",,6,"WALK",7.4825971749227485,1767186249 -5387762,2223027,"work",1,false,1,9000,10000,220898281,"home",,15,"WALK",7.34179717744091,1767186253 -5387763,2223027,"eatout",1,true,1,10000,9000,220898289,"eatout",,16,"WALK",9.90779315781692,1767186313 -5387763,2223027,"eatout",1,false,1,9000,10000,220898289,"home",,16,"WALK",9.729233148863598,1767186317 -5387763,2223027,"othdiscr",1,true,1,15000,9000,220898308,"othdiscr",,18,"WALK_LRF",2.11214260241268,1767186465 -5387763,2223027,"othdiscr",1,false,1,9000,15000,220898308,"home",,18,"WALK_LRF",2.114487481879299,1767186469 -5389226,2223759,"work",1,true,1,14000,16000,220958305,"work",,8,"WALK",0.5332653083530436,1767666441 -5389226,2223759,"work",1,false,1,16000,14000,220958305,"home",,17,"WALK",0.4100846172156184,1767666445 -5389227,2223759,"atwork",1,true,1,16000,16000,220958311,"atwork",,11,"WALK",7.392759591563246,1767666489 -5389227,2223759,"atwork",1,false,1,16000,16000,220958311,"work",,11,"WALK",7.392759591563246,1767666493 -5389227,2223759,"escort",1,true,1,5000,16000,220958316,"escort",,17,"WALK",4.089263672515711,1767666529 -5389227,2223759,"escort",1,false,1,16000,5000,220958316,"home",,17,"WALK",4.011143862031004,1767666533 -5389227,2223759,"work",1,true,1,16000,16000,220958346,"work",,6,"WALK",5.450624006214329,1767666769 -5389227,2223759,"work",1,false,1,16000,16000,220958346,"home",,15,"WALK",5.450624005397038,1767666773 -7305540,2727273,"social",1,true,1,21000,20000,299527176,"social",,7,"WALK",2.5336186542574204,2396217409 -7305540,2727273,"social",1,false,1,20000,21000,299527176,"home",,7,"WALK",2.3656307848426334,2396217413 -7305540,2727273,"social",1,true,1,9000,20000,299527177,"social",,10,"WALK",6.447035361842409,2396217417 -7305540,2727273,"social",1,false,2,8000,9000,299527177,"eatout",36.15580696689693,11,"WALK",6.6220351635608905,2396217421 -7305540,2727273,"social",2,false,2,20000,8000,299527177,"home",,11,"WALK",7.1913682178766365,2396217422 -7305540,2727273,"work",1,true,1,24000,20000,299527179,"work",,13,"BIKE",1.6028747415427587,2396217433 -7305540,2727273,"work",1,false,1,20000,24000,299527179,"home",,16,"BIKE",1.5355807172674794,2396217437 -7305541,2727273,"shopping",1,true,1,16000,20000,299527214,"shopping",,18,"WALK",7.064914498109314,2396217713 -7305541,2727273,"shopping",1,false,1,20000,16000,299527214,"home",,20,"WALK_LOC",7.1093419615753985,2396217717 -7305541,2727273,"social",1,true,1,21000,20000,299527217,"social",,17,"WALK_LOC",3.0911048477980314,2396217737 -7305541,2727273,"social",1,false,1,20000,21000,299527217,"home",,17,"WALK_LOC",3.200851120839349,2396217741 -7305541,2727273,"work",1,true,1,4000,20000,299527220,"work",,7,"WALK",-0.11437934759116813,2396217761 -7305541,2727273,"work",1,false,1,20000,4000,299527220,"home",,15,"WALK_LRF",0.6663056921108688,2396217765 -7453413,2762078,"othmaint",1,true,1,8000,20000,305589961,"othmaint",,11,"WALK_LOC",8.511850861798528,2444719689 -7453413,2762078,"othmaint",1,false,1,20000,8000,305589961,"home",,14,"WALK",8.452543898989719,2444719693 -7511873,2820538,"work",1,true,1,1000,8000,307986832,"work",,7,"WALK_LOC",-0.5114030615970244,2463894657 -7511873,2820538,"work",1,false,1,8000,1000,307986832,"home",,15,"WALK",-0.6139093584117478,2463894661 -7512109,2820774,"work",1,true,1,14000,8000,307996508,"work",,7,"WALK_LOC",0.6718435845841986,2463972065 -7512109,2820774,"work",1,false,1,8000,14000,307996508,"home",,18,"WALK",0.610566926121762,2463972069 -7512514,2821179,"work",1,true,1,9000,8000,308013113,"work",,18,"WALK",7.994842521447154,2464104905 -7512514,2821179,"work",1,false,1,8000,9000,308013113,"home",,21,"WALK",7.994842521452135,2464104909 -7513432,2822097,"social",1,true,1,11000,8000,308050748,"social",,9,"WALK_LOC",3.706460558335686,2464405985 -7513432,2822097,"social",1,false,2,9000,11000,308050748,"eatout",31.394224343964623,16,"WALK",3.677383696890382,2464405989 -7513432,2822097,"social",2,false,2,8000,9000,308050748,"home",,16,"WALK_LOC",7.113541133018152,2464405990 -7513554,2822219,"work",1,true,2,9000,8000,308055753,"eatout",42.391324918183464,10,"WALK",8.722225942224798,2464446025 -7513554,2822219,"work",2,true,2,9000,9000,308055753,"work",,12,"WALK",8.5974651892884,2464446026 -7513554,2822219,"work",1,false,1,8000,9000,308055753,"home",,21,"WALK",8.698411202168328,2464446029 -7523517,2832182,"shopping",1,true,1,13000,7000,308464230,"shopping",,15,"WALK_LOC",-0.5264083063330212,2467713841 -7523517,2832182,"shopping",1,false,1,7000,13000,308464230,"home",,15,"WALK_LOC",-0.4961048751413181,2467713845 +3328569,1511234,"univ",1,false,1,8000,9000,136471360,"home",,16,"WALK",10.077775953315786,1091770885 +3495342,1594621,"atwork",1,true,4,8000,8000,143309026,"escort",55.46266860818287,10,"WALK",12.546654055806371,1146472209 +3495342,1594621,"atwork",2,true,4,7000,8000,143309026,"eatout",55.91434526117385,10,"WALK",14.39426624162804,1146472210 +3495342,1594621,"atwork",3,true,4,7000,7000,143309026,"eatout",56.04404466010437,10,"WALK",14.469466216435773,1146472211 +3495342,1594621,"atwork",4,true,4,8000,7000,143309026,"atwork",,10,"WALK",12.253374125394837,1146472212 +3495342,1594621,"atwork",1,false,1,8000,8000,143309026,"work",,10,"WALK",12.546654055806371,1146472213 +3495342,1594621,"work",1,true,1,8000,10000,143309061,"work",,8,"WALK",10.017940348880348,1146472489 +3495342,1594621,"work",1,false,1,10000,8000,143309061,"home",,17,"WALK",10.028242430555887,1146472493 +3495343,1594621,"shopping",1,true,2,6000,10000,143309096,"eatout",36.78646046022073,15,"WALK",11.585529063330783,1146472769 +3495343,1594621,"shopping",2,true,2,11000,6000,143309096,"shopping",,15,"WALK",4.505557730035075,1146472770 +3495343,1594621,"shopping",1,false,2,7000,11000,143309096,"shopping",39.48805042360866,16,"WALK",4.706436401588666,1146472773 +3495343,1594621,"shopping",2,false,2,10000,7000,143309096,"home",,16,"WALK",13.533226166426466,1146472774 +3596364,1645132,"univ",1,true,1,10000,9000,147450955,"univ",,11,"WALK",9.217199229897293,1179607641 +3596364,1645132,"univ",1,false,1,9000,10000,147450955,"home",,11,"WALK",9.043759217163936,1179607645 +3596364,1645132,"shopping",1,true,1,11000,9000,147450957,"shopping",,13,"WALK",4.739914821838448,1179607657 +3596364,1645132,"shopping",1,false,1,9000,11000,147450957,"home",,19,"WALK",4.717594809733594,1179607661 +3596365,1645132,"school",1,true,1,8000,9000,147450996,"school",,10,"WALK",11.681775804322669,1179607969 +3596365,1645132,"school",1,false,3,6000,8000,147450996,"shopping",48.18895062075897,17,"WALK",11.145019177550582,1179607973 +3596365,1645132,"school",2,false,3,7000,6000,147450996,"othmaint",53.52462857035891,17,"WALK_LOC",11.8481317290849,1179607974 +3596365,1645132,"school",3,false,3,9000,7000,147450996,"home",,17,"WALK",13.212573351460701,1179607975 +3891102,1747467,"atwork",1,true,1,23000,15000,159535186,"atwork",,10,"WALK",2.3591712055652576,1276281489 +3891102,1747467,"atwork",1,false,2,7000,23000,159535186,"eatout",32.521623686967494,13,"WALK",0.3700954492585565,1276281493 +3891102,1747467,"atwork",2,false,2,15000,7000,159535186,"work",,13,"WALK",13.551266299350594,1276281494 +3891102,1747467,"work",1,true,2,25000,16000,159535221,"escort",22.721184898615036,8,"WALK",9.029527126001836,1276281769 +3891102,1747467,"work",2,true,2,15000,25000,159535221,"work",,10,"WALK",-0.04244247036004735,1276281770 +3891102,1747467,"work",1,false,2,7000,15000,159535221,"shopping",23.79438043021271,17,"WALK",-0.11676435321124025,1276281773 +3891102,1747467,"work",2,false,2,16000,7000,159535221,"home",,21,"WALK",9.584561404322464,1276281774 +3891104,1747467,"othdiscr",1,true,1,21000,16000,159535289,"othdiscr",,7,"WALK",3.6151228593321267,1276282313 +3891104,1747467,"othdiscr",1,false,1,16000,21000,159535289,"home",,22,"WALK",3.8941204065716084,1276282317 +4171615,1810015,"univ",1,true,1,14000,16000,171036246,"univ",,18,"WALK",0.6566419385905423,1368289969 +4171615,1810015,"univ",1,false,1,16000,14000,171036246,"home",,18,"WALK",0.5048818715898489,1368289973 +4171616,1810015,"shopping",1,true,1,4000,16000,171036289,"shopping",,10,"WALK",-0.0607985292025343,1368290313 +4171616,1810015,"shopping",1,false,1,16000,4000,171036289,"home",,14,"WALK",-0.18980712710891498,1368290317 +4171617,1810015,"atwork",1,true,1,10000,13000,171036301,"atwork",,10,"WALK",8.38017345500569,1368290409 +4171617,1810015,"atwork",1,false,2,6000,10000,171036301,"escort",46.07812183047964,10,"WALK",9.133473345082415,1368290413 +4171617,1810015,"atwork",2,false,2,13000,6000,171036301,"work",,10,"WALK",12.208440928655007,1368290414 +4171617,1810015,"work",1,true,1,13000,16000,171036336,"work",,8,"WALK",-0.8047586364659038,1368290689 +4171617,1810015,"work",1,false,1,16000,13000,171036336,"home",,16,"WALK",-0.8614632110819316,1368290693 +4171619,1810015,"othdiscr",1,true,1,15000,16000,171036404,"othdiscr",,9,"WALK",0.9624566502678611,1368291233 +4171619,1810015,"othdiscr",1,false,1,16000,15000,171036404,"home",,19,"WALK",0.8254128128089915,1368291237 +4171622,1810015,"othmaint",1,true,1,9000,16000,171036530,"othmaint",,11,"WALK",3.66982435165648,1368292241 +4171622,1810015,"othmaint",1,false,1,16000,9000,171036530,"home",,12,"WALK",3.7151463451230744,1368292245 +4823797,1952792,"work",1,true,2,9000,14000,197775716,"work",23.994169577978532,10,"WALK",7.891553908606716,1582205729 +4823797,1952792,"work",2,true,2,2000,9000,197775716,"work",,10,"WALK_LOC",-0.3676272090623946,1582205730 +4823797,1952792,"work",1,false,1,14000,2000,197775716,"home",,18,"WALK",0.46985768907255554,1582205733 +5057160,2048204,"work",1,true,1,2000,5000,207343599,"work",,6,"BIKE",-0.10838976831540556,1658748793 +5057160,2048204,"work",1,false,1,5000,2000,207343599,"home",,17,"BIKE",-0.08509231386051125,1658748797 +5057338,2048382,"work",1,true,1,9000,7000,207350897,"work",,7,"WALK_LOC",8.758288436944824,1658807177 +5057338,2048382,"work",1,false,1,7000,9000,207350897,"home",,20,"WALK",8.818559157235638,1658807181 +5387762,2223027,"work",1,true,1,2000,9000,220898281,"work",,6,"WALK",0.42431728319721795,1767186249 +5387762,2223027,"work",1,false,1,9000,2000,220898281,"home",,15,"WALK",0.4956959539618453,1767186253 +5387763,2223027,"eatout",1,true,1,5000,9000,220898289,"eatout",,16,"WALK",3.899543591586989,1767186313 +5387763,2223027,"eatout",1,false,1,9000,5000,220898289,"home",,16,"WALK",3.983243122356904,1767186317 +5387763,2223027,"othdiscr",1,true,1,8000,9000,220898308,"othdiscr",,18,"WALK",12.045414088874892,1767186465 +5387763,2223027,"othdiscr",1,false,1,9000,8000,220898308,"home",,18,"WALK",12.045414088874892,1767186469 +5389226,2223759,"work",1,true,1,19000,16000,220958305,"work",,8,"WALK",-2.2159056636781154,1767666441 +5389226,2223759,"work",1,false,1,16000,19000,220958305,"home",,17,"WALK",-2.237678923930952,1767666445 +5389227,2223759,"atwork",1,true,1,11000,2000,220958311,"atwork",,11,"WALK",6.014273924618851,1767666489 +5389227,2223759,"atwork",1,false,1,2000,11000,220958311,"work",,11,"WALK_LOC",6.056702777866963,1767666493 +5389227,2223759,"escort",1,true,2,7000,16000,220958316,"escort",32.62643874211314,17,"WALK_LOC",13.605830939780558,1767666529 +5389227,2223759,"escort",2,true,2,4000,7000,220958316,"escort",,17,"WALK_LOC",0.5052129172906357,1767666530 +5389227,2223759,"escort",1,false,1,16000,4000,220958316,"home",,17,"WALK_LOC",0.57432873159691,1767666533 +5389227,2223759,"work",1,true,1,2000,16000,220958346,"work",,6,"WALK",-0.3405532662460644,1767666769 +5389227,2223759,"work",1,false,1,16000,2000,220958346,"home",,15,"WALK",-0.4901110240386973,1767666773 +7305540,2727273,"social",1,true,1,6000,20000,299527176,"social",,7,"WALK",5.206370496918408,2396217409 +7305540,2727273,"social",1,false,1,20000,6000,299527176,"home",,7,"WALK",5.033109285371569,2396217413 +7305540,2727273,"social",1,true,1,2000,20000,299527177,"social",,10,"DRIVEALONEFREE",0.54938316132333,2396217417 +7305540,2727273,"social",1,false,2,8000,2000,299527177,"eatout",18.249815215027038,11,"TNC_SINGLE",0.5321554611234836,2396217421 +7305540,2727273,"social",2,false,2,20000,8000,299527177,"home",,11,"WALK",5.391723077362911,2396217422 +7305540,2727273,"work",1,true,1,9000,20000,299527179,"work",,13,"WALK_LOC",8.938638173342552,2396217433 +7305540,2727273,"work",1,false,4,25000,9000,299527179,"shopping",40.267489600527185,13,"WALK_LOC",8.195552357824264,2396217437 +7305540,2727273,"work",2,false,4,7000,25000,299527179,"othmaint",47.488668442258984,16,"WALK_LOC",10.7919949001442,2396217438 +7305540,2727273,"work",3,false,4,7000,7000,299527179,"eatout",48.4246382965432,16,"WALK",11.479761844727252,2396217439 +7305540,2727273,"work",4,false,4,20000,7000,299527179,"home",,16,"WALK_LOC",11.0104368761446,2396217440 +7305541,2727273,"shopping",1,true,1,20000,20000,299527214,"shopping",,18,"TNC_SINGLE",0.5187216440570394,2396217713 +7305541,2727273,"shopping",1,false,1,20000,20000,299527214,"home",,20,"TNC_SINGLE",0.5185992852751763,2396217717 +7305541,2727273,"social",1,true,1,6000,20000,299527217,"social",,17,"WALK",7.004410046180289,2396217737 +7305541,2727273,"social",1,false,1,20000,6000,299527217,"home",,17,"WALK",6.829410156928113,2396217741 +7305541,2727273,"work",1,true,1,2000,20000,299527220,"work",,7,"WALK_LOC",-0.2830046111003284,2396217761 +7305541,2727273,"work",1,false,1,20000,2000,299527220,"home",,15,"WALK",0.44589889706759944,2396217765 +7453413,2762078,"othmaint",1,true,1,9000,20000,305589961,"othmaint",,11,"WALK_LOC",6.6779817612214325,2444719689 +7453413,2762078,"othmaint",1,false,1,20000,9000,305589961,"home",,14,"WALK_LOC",6.43748275452276,2444719693 +7511873,2820538,"work",1,true,1,13000,8000,307986832,"work",,7,"WALK",-1.5552805729656705,2463894657 +7511873,2820538,"work",1,false,1,8000,13000,307986832,"home",,15,"WALK",-1.714935968586466,2463894661 +7512109,2820774,"work",1,true,1,16000,8000,307996508,"work",,7,"WALK",5.0284500448723515,2463972065 +7512109,2820774,"work",1,false,1,8000,16000,307996508,"home",,18,"WALK",4.961958073640531,2463972069 +7512514,2821179,"work",1,true,1,5000,8000,308013113,"work",,18,"WALK",3.1849050442076052,2464104905 +7512514,2821179,"work",1,false,1,8000,5000,308013113,"home",,21,"WALK",3.1849050440009243,2464104909 +7513432,2822097,"social",1,true,1,4000,8000,308050748,"social",,9,"WALK_LOC",0.4383871632879739,2464405985 +7513432,2822097,"social",1,false,2,7000,4000,308050748,"eatout",24.82423194978245,16,"WALK_LOC",0.470761666015878,2464405989 +7513432,2822097,"social",2,false,2,8000,7000,308050748,"home",,16,"WALK_LOC",9.091035870708279,2464405990 +7513554,2822219,"work",1,true,1,5000,8000,308055753,"work",,10,"WALK",3.1849109536265523,2464446025 +7513554,2822219,"work",1,false,1,8000,5000,308055753,"home",,21,"WALK",3.1849109502813695,2464446029 +7523517,2832182,"shopping",1,true,1,11000,7000,308464230,"shopping",,15,"WALK",4.618728216116123,2467713841 +7523517,2832182,"shopping",1,false,1,7000,11000,308464230,"home",,15,"WALK",4.607627213416084,2467713845 From 8933d6c1cf06eb684c3431811fec647b2d47ea57 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 15:00:56 +1000 Subject: [PATCH 256/298] removes requirement of interaction_sample_simulate to have alts_context --- .../test/test_interaction_sample_simulate.py | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index adf4937211..090bf2d3d5 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -223,34 +223,3 @@ def fake_make_choices_utility_based( index=choosers.index, ) pd.testing.assert_frame_equal(captured["alt_nrs_df"], expected_alt_nrs) - - -def test_interaction_sample_simulate_requires_alts_context_for_eet_integer_choices( - state, -): - state.settings.use_explicit_error_terms = True - - choosers = pd.DataFrame( - {"chooser_attr": [1.0, 1.0]}, - index=pd.Index([200, 201], name="person_id"), - ) - alternatives = pd.DataFrame( - { - "alt_attr": [1.0, 0.5, 0.8, 1.2], - "tdd": [0, 2, 0, 2], - }, - index=pd.Index([200, 200, 201, 201], name="person_id"), - ) - spec = pd.DataFrame( - {"coefficient": [1.0]}, - index=pd.Index(["alt_attr"], name="Expression"), - ) - - with pytest.raises(ValueError, match="alts_context is required"): - interaction_sample_simulate.interaction_sample_simulate( - state, - choosers, - alternatives, - spec, - choice_column="tdd", - ) From 270d450c2f2063484d86dbf01fb1237b125cdf2d Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 20:47:38 +1000 Subject: [PATCH 257/298] fix test by using eet as intended, remove stable_indexing in tour_dest for mc --- activitysim/abm/models/util/tour_destination.py | 15 ++++++++++----- .../test_misc/test_tour_destination_sampling.py | 2 ++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index e227044a99..5e385bbb26 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -202,10 +202,15 @@ def destination_sample( # the name of the dest column to be returned in choices alt_dest_col_name = model_settings.ALT_DEST_COL_NAME - stable_alt_positions = full_destination_size_terms.index.get_indexer( - destination_size_terms.index - ) - assert (stable_alt_positions >= 0).all() + if state.settings.use_explicit_error_terms: + stable_alt_positions = full_destination_size_terms.index.get_indexer( + destination_size_terms.index + ) + assert (stable_alt_positions >= 0).all() + n_total_alts = len(full_destination_size_terms) + else: + stable_alt_positions = None + n_total_alts = None choices = _destination_sample( state, @@ -219,7 +224,7 @@ def destination_sample( chunk_tag=chunk_tag, trace_label=trace_label, stable_alt_positions=stable_alt_positions, - n_total_alts=len(full_destination_size_terms), + n_total_alts=n_total_alts, ) return choices diff --git a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py index 2b8a513dd8..2b74da1676 100644 --- a/activitysim/abm/test/test_misc/test_tour_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_tour_destination_sampling.py @@ -104,6 +104,7 @@ def fake_choose_maz_for_taz( monkeypatch.setattr(tour_destination, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) state = workflow.State().default_settings() + state.settings.use_explicit_error_terms = True choosers = pd.DataFrame( {"origin": [101]}, index=pd.Index([7001], name="tour_id"), @@ -353,6 +354,7 @@ def fake_destination_sample( ) state = workflow.State().default_settings() + state.settings.use_explicit_error_terms = True choosers = pd.DataFrame( {"origin": [101], "person_id": [55]}, index=pd.Index([7001], name="tour_id"), From 01b38c7210f58f54a086b07918a079a1c8046899 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 21:18:07 +1000 Subject: [PATCH 258/298] clean up --- activitysim/abm/models/location_choice.py | 1 - activitysim/abm/models/trip_destination.py | 1 + activitysim/core/interaction_sample.py | 5 ++--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index ff6e8f1324..dcd916f83b 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -810,7 +810,6 @@ def run_location_choice( chunk_size : int trace_label : str skip_choice : bool - alts_context : AltsContext or None Returns ------- diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index eecb46839a..278928df5c 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -215,6 +215,7 @@ def _destination_sample( preprocessor_setting_name="alts_preprocessor_sample", ) + # Trip destination keeps the alternative universe here so stable_alt_positions is not needed. choices = interaction_sample( state, choosers=trips, diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 9617e35640..4e27c3f52b 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -850,11 +850,10 @@ def _interaction_sample( return choices_df - if sampling_method != "monte_carlo": - + if use_eet: if estimation.manager.enabled: raise ValueError( - f"sample_method={sampling_method!r} is not supported with estimation mode" + "use_explicit_error_terms is not supported with estimation mode" ) utilities = logit.validate_utils( From 4b78e20a93a4e075b4c390617f8ba7c5af5c8d80 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 21:29:28 +1000 Subject: [PATCH 259/298] conditional stable sample indexes --- activitysim/abm/models/location_choice.py | 34 +++++++++++++------ .../test_location_choice_sampling.py | 7 ++-- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index dcd916f83b..bedc4c5b74 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -250,8 +250,16 @@ def location_sample( skims = skim_dict.wrap("home_zone_id", "zone_id") alt_dest_col_name = model_settings.ALT_DEST_COL_NAME - stable_alt_positions = full_dest_size_terms.index.get_indexer(dest_size_terms.index) - assert (stable_alt_positions >= 0).all() + + if state.settings.use_explicit_error_terms: + stable_alt_positions = full_dest_size_terms.index.get_indexer( + dest_size_terms.index + ) + assert (stable_alt_positions >= 0).all() + n_total_alts = len(full_dest_size_terms) + else: + stable_alt_positions = None + n_total_alts = None choices = _location_sample( state, @@ -266,7 +274,7 @@ def location_sample( chunk_tag, trace_label, stable_alt_positions=stable_alt_positions, - n_total_alts=len(full_dest_size_terms), + n_total_alts=n_total_alts, ) return choices @@ -390,12 +398,18 @@ def location_presample( ) if full_dest_size_terms is None: full_dest_size_terms = dest_size_terms - full_taz_index = pd.Index( - network_los.map_maz_to_taz(full_dest_size_terms.index), name=DEST_TAZ - ) - full_taz_index = full_taz_index[~full_taz_index.duplicated()] - stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) - assert (stable_alt_positions >= 0).all() + if state.settings.use_explicit_error_terms: + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_dest_size_terms.index), name=DEST_TAZ + ) + full_taz_index = full_taz_index[~full_taz_index.duplicated()] + stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) + assert (stable_alt_positions >= 0).all() + n_total_alts = len(full_taz_index) + else: + full_taz_index = None + stable_alt_positions = None + n_total_alts = None sample_compute_settings = getattr(model_settings, "compute_settings", None) if sample_compute_settings is not None: @@ -452,7 +466,7 @@ def location_presample( trace_label, zone_layer="taz", stable_alt_positions=stable_alt_positions, - n_total_alts=len(full_taz_index), + n_total_alts=n_total_alts, ) # print(f"taz_sample\n{taz_sample}") diff --git a/activitysim/abm/test/test_misc/test_location_choice_sampling.py b/activitysim/abm/test/test_misc/test_location_choice_sampling.py index 7c41136160..34bd41b675 100644 --- a/activitysim/abm/test/test_misc/test_location_choice_sampling.py +++ b/activitysim/abm/test/test_misc/test_location_choice_sampling.py @@ -54,7 +54,7 @@ def fake_location_sample( captured["alt_dest_col_name"] = alt_dest_col_name captured["zone_layer"] = zone_layer captured["active_taz_index"] = alternatives.index.copy() - captured["stable_alt_positions"] = stable_alt_positions.copy() + captured["stable_alt_positions"] = stable_alt_positions captured["n_total_alts"] = n_total_alts return pd.DataFrame( {"dest_TAZ": [1]}, @@ -146,8 +146,8 @@ def fake_choose_maz_for_taz( ) assert captured["alt_dest_col_name"] == location_choice.DEST_TAZ assert captured["zone_layer"] == "taz" - assert captured["n_total_alts"] == 3 - assert list(captured["stable_alt_positions"]) == [0, 2] + assert captured["n_total_alts"] is None + assert captured["stable_alt_positions"] is None assert captured["full_taz_index"] is None @@ -321,6 +321,7 @@ def fake_location_sample( monkeypatch.setattr(location_choice, "_location_sample", fake_location_sample) state = workflow.State().default_settings() + state.settings.use_explicit_error_terms = True model_settings = type( "ModelSettings", (), From ae278ccbfc45576bc009bf6fc7d5edf192f5a64e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 21:50:11 +1000 Subject: [PATCH 260/298] stable alts only for eet with poisson sampling for two-zone --- activitysim/abm/models/location_choice.py | 7 ++++++ activitysim/abm/models/trip_destination.py | 7 ++++++ .../abm/models/util/tour_destination.py | 24 +++++++++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index bedc4c5b74..d1dffd7edb 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -416,6 +416,13 @@ def location_presample( sample_compute_settings = sample_compute_settings.subcomponent_settings( "sample" ) + + # Stable alt positions are only used with explicit error terms and Poisson sampling for + # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms + # with eet sampling alignment would require a large amount of random numbers due to + # potential repeated occurence of MAZs (importance sampling with replacement). This is due + # to how random numbers are generated atm, but with a counter-based RNG this could be + # revisited. taz_sample_method = None if sample_compute_settings is not None: taz_sample_method = sample_compute_settings.sample_method diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 278928df5c..7485b9f074 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -665,6 +665,13 @@ def destination_presample( sample_compute_settings = sample_compute_settings.subcomponent_settings( "sample" ) + + # Stable alt positions are only used with explicit error terms and Poisson sampling for + # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms + # with eet sampling alignment would require a large amount of random numbers due to + # potential repeated occurence of MAZs (importance sampling with replacement). This is due + # to how random numbers are generated atm, but with a counter-based RNG this could be + # revisited. taz_sample_method = None if sample_compute_settings is not None: taz_sample_method = sample_compute_settings.sample_method diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 5e385bbb26..44ef733166 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -19,7 +19,10 @@ workflow, ) from activitysim.core.configuration.logit import TourLocationComponentSettings -from activitysim.core.interaction_sample import interaction_sample +from activitysim.core.interaction_sample import ( + _resolve_sample_method, + interaction_sample, +) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.util import reindex @@ -630,9 +633,26 @@ def destination_presample( full_taz_index = full_taz_index[~full_taz_index.duplicated()] stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) assert (stable_alt_positions >= 0).all() + + # Stable alt positions are only used with explicit error terms and Poisson sampling for + # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms + # with eet sampling alignment would require a large amount of random numbers due to + # potential repeated occurence of MAZs (importance sampling with replacement). This is due + # to how random numbers are generated atm, but with a counter-based RNG this could be + # revisited. + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + taz_sample_method = _resolve_sample_method( + state, sample_compute_settings, trace_label + ) + use_stable_taz_index = taz_sample_method == "poisson" else: full_taz_index = None stable_alt_positions = None + use_stable_taz_index = False orig_maz = model_settings.CHOOSER_ORIG_COL_NAME assert orig_maz in choosers @@ -668,7 +688,7 @@ def destination_presample( MAZ_size_terms, trace_label, model_settings, - full_taz_index=full_taz_index, + full_taz_index=full_taz_index if use_stable_taz_index else None, ) assert DEST_MAZ in maz_choices From de74dfc59ad32775245bff9bd1283cd12499ba25 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 21:56:00 +1000 Subject: [PATCH 261/298] trip maz-for-taz stable alts for eet with poisson sampling --- activitysim/abm/models/trip_destination.py | 41 ++++++++++------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 7485b9f074..90b8e08f0a 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -31,7 +31,10 @@ from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LocationComponentSettings from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError -from activitysim.core.interaction_sample import interaction_sample +from activitysim.core.interaction_sample import ( + _resolve_sample_method, + interaction_sample, +) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.skim_dictionary import DataFrameMatrix @@ -658,13 +661,6 @@ def destination_presample( alternatives = alternatives.groupby( network_los.map_maz_to_taz(alternatives.index) ).sum() - full_taz_index = pd.Index(alternatives.index, name=f"{alt_dest_col_name}_TAZ") - - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" - ) # Stable alt positions are only used with explicit error terms and Poisson sampling for # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms @@ -672,21 +668,20 @@ def destination_presample( # potential repeated occurence of MAZs (importance sampling with replacement). This is due # to how random numbers are generated atm, but with a counter-based RNG this could be # revisited. - taz_sample_method = None - if sample_compute_settings is not None: - taz_sample_method = sample_compute_settings.sample_method - if taz_sample_method is None: - taz_sample_method = getattr(state.settings, "sample_method", None) - if taz_sample_method is None: - taz_sample_method = ( - "poisson" - if getattr(state.settings, "use_explicit_error_terms", False) - else "monte_carlo" + full_taz_index = None + if state.settings.use_explicit_error_terms: + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + taz_sample_method = _resolve_sample_method( + state, sample_compute_settings, trace_label ) - use_stable_taz_index = ( - getattr(state.settings, "use_explicit_error_terms", False) - and taz_sample_method == "poisson" - ) + if taz_sample_method == "poisson": + full_taz_index = pd.Index( + alternatives.index, name=f"{alt_dest_col_name}_TAZ" + ) # # i did this but after changing alt_dest_col_name to 'trip_dest' it # # shouldn't be needed anymore @@ -719,7 +714,7 @@ def destination_presample( alt_dest_col_name, trace_label, model_settings, - full_taz_index=full_taz_index if use_stable_taz_index else None, + full_taz_index=full_taz_index, ) assert alt_dest_col_name in maz_sample From 77b80b3b649d33370873e0a4f8cf38f94a5795af Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 21:58:56 +1000 Subject: [PATCH 262/298] clean up --- activitysim/abm/models/location_choice.py | 52 ++++++++++------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index d1dffd7edb..4cc040717d 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -16,7 +16,10 @@ TourModeComponentSettings, ) from activitysim.core.exceptions import DuplicateWorkflowTableError -from activitysim.core.interaction_sample import interaction_sample +from activitysim.core.interaction_sample import ( + _resolve_sample_method, + interaction_sample, +) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.logit import AltsContext from activitysim.core.util import reindex @@ -396,8 +399,10 @@ def location_presample( MAZ_size_terms, TAZ_size_terms = aggregate_size_terms( state, dest_size_terms, network_los, model_settings ) + if full_dest_size_terms is None: full_dest_size_terms = dest_size_terms + if state.settings.use_explicit_error_terms: full_taz_index = pd.Index( network_los.map_maz_to_taz(full_dest_size_terms.index), name=DEST_TAZ @@ -406,38 +411,27 @@ def location_presample( stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) assert (stable_alt_positions >= 0).all() n_total_alts = len(full_taz_index) + + # Stable alt positions are only used with explicit error terms and Poisson sampling for + # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms + # with eet sampling alignment would require a large amount of random numbers due to + # potential repeated occurence of MAZs (importance sampling with replacement). This is due + # to how random numbers are generated atm, but with a counter-based RNG this could be + # revisited. + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + taz_sample_method = _resolve_sample_method( + state, sample_compute_settings, trace_label + ) + use_stable_taz_index = taz_sample_method == "poisson" else: full_taz_index = None stable_alt_positions = None n_total_alts = None - - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" - ) - - # Stable alt positions are only used with explicit error terms and Poisson sampling for - # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms - # with eet sampling alignment would require a large amount of random numbers due to - # potential repeated occurence of MAZs (importance sampling with replacement). This is due - # to how random numbers are generated atm, but with a counter-based RNG this could be - # revisited. - taz_sample_method = None - if sample_compute_settings is not None: - taz_sample_method = sample_compute_settings.sample_method - if taz_sample_method is None: - taz_sample_method = getattr(state.settings, "sample_method", None) - if taz_sample_method is None: - taz_sample_method = ( - "poisson" - if getattr(state.settings, "use_explicit_error_terms", False) - else "monte_carlo" - ) - use_stable_taz_index = ( - getattr(state.settings, "use_explicit_error_terms", False) - and taz_sample_method == "poisson" - ) + use_stable_taz_index = False # convert MAZ zone_id to 'TAZ' in choosers (persons_merged) # persons_merged[HOME_TAZ] = persons_merged[HOME_MAZ].map(maz_to_taz) From bdfe75771614cd6871be091d0a90e12d01c16008 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 22:07:26 +1000 Subject: [PATCH 263/298] stable two-zone alts for tour_od --- activitysim/abm/models/util/tour_od.py | 31 ++++- .../test/test_misc/test_tour_od_sampling.py | 108 +++++++++++++++++- 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 5892c9ea1b..878fe1a293 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -22,7 +22,10 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import TourLocationComponentSettings -from activitysim.core.interaction_sample import interaction_sample +from activitysim.core.interaction_sample import ( + _resolve_sample_method, + interaction_sample, +) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.util import reindex @@ -645,13 +648,29 @@ def od_presample( MAZ_size_terms, TAZ_size_terms = aggregate_size_terms( destination_size_terms, network_los ) + + full_taz_index = None if state.settings.use_explicit_error_terms: - full_taz_index = pd.Index( - network_los.map_maz_to_taz(full_destination_size_terms.index), name=DEST_TAZ + # Stable alt positions are only used with explicit error terms and Poisson sampling for + # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms + # with eet sampling alignment would require a large amount of random numbers due to + # potential repeated occurence of MAZs (importance sampling with replacement). This is due + # to how random numbers are generated atm, but with a counter-based RNG this could be + # revisited. + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + taz_sample_method = _resolve_sample_method( + state, sample_compute_settings, trace_label ) - full_taz_index = full_taz_index[~full_taz_index.duplicated()] - else: - full_taz_index = None + if taz_sample_method == "poisson": + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_destination_size_terms.index), + name=DEST_TAZ, + ) + full_taz_index = full_taz_index[~full_taz_index.duplicated()] # create wrapper with keys for this lookup - in this case there is a ORIG_TAZ # in the choosers and a DEST_TAZ in the alternatives which get merged during diff --git a/activitysim/abm/test/test_misc/test_tour_od_sampling.py b/activitysim/abm/test/test_misc/test_tour_od_sampling.py index 7a9c293b20..5383e524e5 100644 --- a/activitysim/abm/test/test_misc/test_tour_od_sampling.py +++ b/activitysim/abm/test/test_misc/test_tour_od_sampling.py @@ -51,7 +51,7 @@ def make(draws, use_explicit_error_terms=False): return state -def test_od_presample_passes_full_taz_index_for_eet(monkeypatch): +def test_od_presample_passes_full_taz_index_for_eet_poisson(monkeypatch): captured = {} def fake_od_sample( @@ -168,6 +168,112 @@ def fake_choose_maz_for_taz( ) +def test_od_presample_omits_full_taz_index_for_eet_non_poisson(monkeypatch): + captured = {} + + def fake_od_sample( + _state, + _spec_segment_name, + _choosers, + _network_los, + destination_size_terms, + _origin_id_col, + _dest_id_col, + _skims, + _estimator, + _model_settings, + alt_od_col_name, + _chunk_size, + chunk_tag, + trace_label, + ): + captured["active_taz_index"] = destination_size_terms.index.copy() + captured["alt_od_col_name"] = alt_od_col_name + captured["chunk_tag"] = chunk_tag + captured["trace_label"] = trace_label + return pd.DataFrame( + { + alt_od_col_name: ["101_1", "101_3"], + "prob": [0.5, 0.25], + "pick_count": [1, 1], + }, + index=pd.Index([7001, 7001], name="tour_id"), + ) + + def fake_choose_maz_for_taz( + _state, + _taz_sample, + _maz_size_terms, + _trace_label, + addtl_col_for_unique_key=None, + dest_maz_id_col=tour_od.DEST_MAZ, + full_taz_index=None, + ): + captured["addtl_col_for_unique_key"] = addtl_col_for_unique_key + captured["dest_maz_id_col"] = dest_maz_id_col + captured["full_taz_index"] = full_taz_index + return pd.DataFrame( + { + dest_maz_id_col: [101], + tour_od.ORIG_MAZ: [101], + "prob": [0.5], + "pick_count": [1], + }, + index=pd.Index([7001], name="tour_id"), + ) + + monkeypatch.setattr(tour_od, "_od_sample", fake_od_sample) + monkeypatch.setattr(tour_od, "choose_MAZ_for_TAZ", fake_choose_maz_for_taz) + + state = workflow.State().default_settings() + state.settings.use_explicit_error_terms = True + state.settings.sample_method = "eet" + choosers = pd.DataFrame( + {tour_od.ORIG_TAZ: [1]}, + index=pd.Index([7001], name="tour_id"), + ) + model_settings = type( + "ModelSettings", + (), + { + "ALT_DEST_COL_NAME": "alt_dest", + "CHOOSER_ORIG_COL_NAME": "origin", + }, + )() + network_los = _DummyNetworkLos({101: 1, 102: 2, 103: 3}) + + active_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 2.0]}, + index=pd.Index([101, 103], name="zone_id"), + ) + full_destination_size_terms = pd.DataFrame( + {"size_term": [1.0, 0.0, 2.0]}, + index=pd.Index([101, 102, 103], name="zone_id"), + ) + + out = tour_od.od_presample( + state, + "segment", + choosers, + model_settings, + network_los, + active_destination_size_terms, + full_destination_size_terms, + estimator=None, + chunk_size=0, + trace_label="test_trace", + ) + + pd.testing.assert_frame_equal( + out, + pd.DataFrame( + {"alt_dest": [101], "origin": [101], "prob": [0.5], "pick_count": [1]}, + index=pd.Index([7001], name="tour_id"), + ), + ) + assert captured["full_taz_index"] is None + + def test_choose_maz_for_taz_eet_uses_full_taz_positions_with_origin_key(): state = _DummyState.make([[0.99, 0.2, 0.99, 0.99, 0.8]]) From 67fd055b0110346fabf39f1254506b07057a094e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 22:32:56 +1000 Subject: [PATCH 264/298] tour_od stable alt cond --- activitysim/abm/models/util/tour_od.py | 40 ++++++++++++-------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 878fe1a293..176780d8f3 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -216,6 +216,8 @@ def _od_sample( preprocessor_setting_name="alts_preprocessor_sample", ) + # Note not using stable alternative positions for EET here, the cross-product of origins and destinations + # is too large for the way the RNG currently works. choices = interaction_sample( state, choosers, @@ -625,6 +627,15 @@ def choose_MAZ_for_TAZ( return taz_choices_w_maz +def resolve_sample_method(state, model_settings, trace_label): + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + return _resolve_sample_method(state, sample_compute_settings, trace_label) + + @workflow.func def od_presample( state: workflow.State, @@ -649,28 +660,15 @@ def od_presample( destination_size_terms, network_los ) - full_taz_index = None - if state.settings.use_explicit_error_terms: - # Stable alt positions are only used with explicit error terms and Poisson sampling for - # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms - # with eet sampling alignment would require a large amount of random numbers due to - # potential repeated occurence of MAZs (importance sampling with replacement). This is due - # to how random numbers are generated atm, but with a counter-based RNG this could be - # revisited. - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" - ) - taz_sample_method = _resolve_sample_method( - state, sample_compute_settings, trace_label + taz_sample_method = resolve_sample_method(state, model_settings, trace_label) + if taz_sample_method == "poisson": + full_taz_index = pd.Index( + network_los.map_maz_to_taz(full_destination_size_terms.index), + name=DEST_TAZ, ) - if taz_sample_method == "poisson": - full_taz_index = pd.Index( - network_los.map_maz_to_taz(full_destination_size_terms.index), - name=DEST_TAZ, - ) - full_taz_index = full_taz_index[~full_taz_index.duplicated()] + full_taz_index = full_taz_index[~full_taz_index.duplicated()] + else: + full_taz_index = None # create wrapper with keys for this lookup - in this case there is a ORIG_TAZ # in the choosers and a DEST_TAZ in the alternatives which get merged during From 249acfa7a247807d232be88b60f951b8e5ba8da0 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Thu, 14 May 2026 23:03:12 +1000 Subject: [PATCH 265/298] resolve_smapling_method --- activitysim/abm/models/location_choice.py | 4 +--- activitysim/abm/models/trip_destination.py | 4 +--- activitysim/abm/models/util/tour_destination.py | 4 +--- activitysim/abm/models/util/tour_od.py | 6 +++--- activitysim/core/interaction_sample.py | 14 ++++++-------- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 4cc040717d..6863ef55db 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -423,9 +423,7 @@ def location_presample( sample_compute_settings = sample_compute_settings.subcomponent_settings( "sample" ) - taz_sample_method = _resolve_sample_method( - state, sample_compute_settings, trace_label - ) + taz_sample_method = _resolve_sample_method(state, sample_compute_settings) use_stable_taz_index = taz_sample_method == "poisson" else: full_taz_index = None diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 90b8e08f0a..824ede937a 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -675,9 +675,7 @@ def destination_presample( sample_compute_settings = sample_compute_settings.subcomponent_settings( "sample" ) - taz_sample_method = _resolve_sample_method( - state, sample_compute_settings, trace_label - ) + taz_sample_method = _resolve_sample_method(state, sample_compute_settings) if taz_sample_method == "poisson": full_taz_index = pd.Index( alternatives.index, name=f"{alt_dest_col_name}_TAZ" diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 44ef733166..63d1977572 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -645,9 +645,7 @@ def destination_presample( sample_compute_settings = sample_compute_settings.subcomponent_settings( "sample" ) - taz_sample_method = _resolve_sample_method( - state, sample_compute_settings, trace_label - ) + taz_sample_method = _resolve_sample_method(state, sample_compute_settings) use_stable_taz_index = taz_sample_method == "poisson" else: full_taz_index = None diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 176780d8f3..6c83c1f484 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -627,13 +627,13 @@ def choose_MAZ_for_TAZ( return taz_choices_w_maz -def resolve_sample_method(state, model_settings, trace_label): +def resolve_sample_method(state, model_settings): sample_compute_settings = getattr(model_settings, "compute_settings", None) if sample_compute_settings is not None: sample_compute_settings = sample_compute_settings.subcomponent_settings( "sample" ) - return _resolve_sample_method(state, sample_compute_settings, trace_label) + return _resolve_sample_method(state, sample_compute_settings) @workflow.func @@ -660,7 +660,7 @@ def od_presample( destination_size_terms, network_los ) - taz_sample_method = resolve_sample_method(state, model_settings, trace_label) + taz_sample_method = resolve_sample_method(state, model_settings) if taz_sample_method == "poisson": full_taz_index = pd.Index( network_los.map_maz_to_taz(full_destination_size_terms.index), diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 4e27c3f52b..9347a6f376 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -37,7 +37,6 @@ def _resolve_sample_method( state: workflow.State, compute_settings: ComputeSettings | None, - use_eet: bool, ) -> InteractionSampleMethod: sampling_method = None if compute_settings is not None: @@ -45,7 +44,7 @@ def _resolve_sample_method( if sampling_method is None: sampling_method = state.settings.sample_method if sampling_method is None: - return "poisson" if use_eet else "monte_carlo" + return "poisson" if state.settings.use_explicit_error_terms else "monte_carlo" if sampling_method not in typing.get_args(InteractionSampleMethod): raise ValueError( f"Unsupported sample_method {sampling_method!r}; expected one of {typing.get_args(InteractionSampleMethod)}" @@ -820,7 +819,7 @@ def _interaction_sample( state.tracing.dump_df(DUMP, utilities, trace_label, "utilities") use_eet = state.settings.use_explicit_error_terms - sampling_method = _resolve_sample_method(state, compute_settings, use_eet) + sampling_method = _resolve_sample_method(state, compute_settings) if sample_size == 0: # Return full alternative set rather than sample @@ -1098,13 +1097,12 @@ def interaction_sample( if not choosers.index.is_monotonic_increasing: assert choosers.index.is_monotonic_increasing - use_eet = state.settings.use_explicit_error_terms - sampling_method = _resolve_sample_method(state, compute_settings, use_eet) + sampling_method = _resolve_sample_method(state, compute_settings) logger.debug(f" interaction_sample sample method = {sampling_method}") - if not use_eet: - # Do not support stable alt positions or tracking total alts when running with MC sampling - # to not introduce any additional changes while adding eet simulation support to ensure no + if not state.settings.use_explicit_error_terms: + # Do not support stable alt positions or tracking total alts when running with MC sampling. We do + # not want to introduce any additional changes while adding eet simulation support to ensure no # regressions. We can add these features later if desired. stable_alt_positions = None n_total_alts = None From 3ebe20cd973854f318c95a58f79df98b185779ec Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 18 May 2026 15:13:09 +1000 Subject: [PATCH 266/298] decouple sample and simulation methods --- activitysim/core/interaction_sample.py | 242 +++++------- .../core/test/test_interaction_sample.py | 347 +++++++++++++----- 2 files changed, 347 insertions(+), 242 deletions(-) diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 9347a6f376..2ac2024487 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -141,10 +141,11 @@ def _poisson_fallback_sample_alternatives( return fallback_sampled_values -def _eet_sample_alternatives_with_replacement( +def make_sample_choices_eet( state: workflow.State, choosers: pd.DataFrame, utilities: pd.DataFrame, + probs: pd.DataFrame, alternatives: pd.DataFrame, sample_size: int, alt_col_name: str, @@ -154,11 +155,15 @@ def _eet_sample_alternatives_with_replacement( n_total_alts: int | None = None, ) -> pd.DataFrame: """ - Sample alternatives by repeated EET draws with replacement. + Sample alternatives by repeated EET (Gumbel argmax) draws with replacement. + + Each chooser receives `sample_size` EV1 draw sets and the argmax-over-utility + winner is recorded per draw, so duplicates are possible (same with-replacement + semantics as the Monte Carlo sampling path). - Each chooser receives `sample_size` EV1 draw sets. The winning alternative - from each draw is recorded, allowing duplicates in the same way as the - Monte Carlo sampling path. + `utilities` drives the Gumbel argmax. `probs` (the MNL choice probabilities + computed from the same utilities by the caller) supplies the `prob` column + written back into the output for sampling-of-alternative correction factors. """ chosen_destinations = ( state.get_rn_generator() @@ -175,16 +180,6 @@ def _eet_sample_alternatives_with_replacement( chooser_idx = np.repeat(np.arange(utilities.shape[0]), sample_size) chunk_sizer.log_df(trace_label, "chooser_idx", chooser_idx) - probs = logit.utils_to_probs( - state, - utilities, - allow_zero_probs=False, - trace_label=trace_label, - overflow_protection=True, - trace_choosers=choosers, - ) - chunk_sizer.log_df(trace_label, "probs", probs) - choices_df = pd.DataFrame( { choosers.index.name: choosers.index.values[chooser_idx], @@ -198,85 +193,6 @@ def _eet_sample_alternatives_with_replacement( chunk_sizer.log_df(trace_label, "chooser_idx", None) del chosen_destinations chunk_sizer.log_df(trace_label, "chosen_destinations", None) - del probs - chunk_sizer.log_df(trace_label, "probs", None) - - return choices_df - - -def make_sample_choices_utility_based( - state: workflow.State, - choosers, - utilities, - alternatives, - sample_size, - alternative_count, - alt_col_name, - allow_zero_probs, - trace_label: str, - chunk_sizer: ChunkSizer, - sampling_method: InteractionSampleMethod = "poisson", - stable_alt_positions: np.ndarray | None = None, - n_total_alts: int | None = None, -): - assert isinstance(utilities, pd.DataFrame) - assert utilities.shape == (len(choosers), alternative_count) - - assert isinstance(alternatives, pd.DataFrame) - assert len(alternatives) == alternative_count - - if allow_zero_probs: - zero_probs = ( - utilities.sum(axis=1) <= utilities.shape[1] * logit.UTIL_UNAVAILABLE - ) - if zero_probs.all(): - return pd.DataFrame(columns=[choosers.index.name, "prob", alt_col_name]) - if zero_probs.any(): - # remove from sample - utilities = utilities[~zero_probs] - choosers = choosers[~zero_probs] - - utils_array = utilities.to_numpy() - chunk_sizer.log_df(trace_label, "utils_array", utils_array) - - if sampling_method == "eet": - choices_df = _eet_sample_alternatives_with_replacement( - state, - choosers, - utilities, - alternatives, - sample_size, - alt_col_name, - trace_label, - chunk_sizer, - stable_alt_positions=stable_alt_positions, - n_total_alts=n_total_alts, - ) - elif sampling_method == "poisson": - probs = logit.utils_to_probs( - state, - utilities, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - overflow_protection=not allow_zero_probs, - trace_choosers=choosers, - ) - - choices_df = _poisson_sample_alternatives( - chunk_sizer, - probs, - alternatives, - sample_size, - alt_col_name, - state, - trace_label, - stable_alt_positions=stable_alt_positions, - n_total_alts=n_total_alts, - ) - else: - raise ValueError( - f"Unsupported utility-based sampling method {sampling_method!r}" - ) return choices_df @@ -818,9 +734,13 @@ def _interaction_sample( state.tracing.dump_df(DUMP, utilities, trace_label, "utilities") - use_eet = state.settings.use_explicit_error_terms sampling_method = _resolve_sample_method(state, compute_settings) + if state.settings.use_explicit_error_terms and estimation.manager.enabled: + raise ValueError( + "use_explicit_error_terms is not supported with estimation mode" + ) + if sample_size == 0: # Return full alternative set rather than sample logger.info("Using unsampled alternatives for %s" % (trace_label,)) @@ -849,60 +769,29 @@ def _interaction_sample( return choices_df - if use_eet: - if estimation.manager.enabled: - raise ValueError( - "use_explicit_error_terms is not supported with estimation mode" - ) - - utilities = logit.validate_utils( - state, - utilities, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - trace_choosers=choosers, - ) + # All three sampling methods consume MNL choice probabilities, so compute + # them once up front. + probs = logit.utils_to_probs( + state, + utilities, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + trace_choosers=choosers, + overflow_protection=not allow_zero_probs, + ) + chunk_sizer.log_df(trace_label, "probs", probs) - choices_df = make_sample_choices_utility_based( - state, - choosers, - utilities, - alternatives, - sample_size, - alternative_count, - alt_col_name, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - chunk_sizer=chunk_sizer, - sampling_method=sampling_method, - stable_alt_positions=stable_alt_positions, - n_total_alts=n_total_alts, - ) - del utilities - chunk_sizer.log_df(trace_label, "utilities", None) - else: - # convert to probabilities (utilities exponentiated and normalized to probs) - # probs is same shape as utilities, one row per chooser and one column for alternative - probs = logit.utils_to_probs( - state, - utilities, - allow_zero_probs=allow_zero_probs, - trace_label=trace_label, - trace_choosers=choosers, - overflow_protection=not allow_zero_probs, + if have_trace_targets: + state.tracing.trace_df( + probs, + tracing.extend_trace_label(trace_label, "probs"), + column_labels=["alternative", "probability"], ) - chunk_sizer.log_df(trace_label, "probs", probs) + if sampling_method == "monte_carlo": del utilities chunk_sizer.log_df(trace_label, "utilities", None) - if have_trace_targets: - state.tracing.trace_df( - probs, - tracing.extend_trace_label(trace_label, "probs"), - column_labels=["alternative", "probability"], - ) - choices_df = make_sample_choices( state, choosers, @@ -953,6 +842,63 @@ def _interaction_sample( del probs chunk_sizer.log_df(trace_label, "probs", None) + else: + # eet and poisson: optionally trim choosers with all-zero probs. The MC + # path handles this inside make_sample_choices + if allow_zero_probs: + non_zero = probs.sum(axis=1) != 0 + if not non_zero.any(): + return pd.DataFrame( + columns=[alt_col_name, "prob", "pick_count"], + index=pd.Index([], name=choosers.index.name), + ) + if not non_zero.all(): + probs = probs[non_zero] + utilities = utilities[non_zero] + choosers = choosers[non_zero] + + if sampling_method == "eet": + # validate_utils clamps unavailable alternatives (utility <= UTIL_MIN) + # to UTIL_UNAVAILABLE so that the Gumbel argmax can't accidentally pick + # them when the Gumbel noise dominates. Probabilities are unaffected + # (both bounds exp() to ~0) so we do not recompute probs. + utilities = logit.validate_utils( + state, + utilities, + allow_zero_probs=allow_zero_probs, + trace_label=trace_label, + trace_choosers=choosers, + ) + choices_df = make_sample_choices_eet( + state, + choosers, + utilities, + probs, + alternatives, + sample_size, + alt_col_name, + trace_label, + chunk_sizer, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + else: # sampling_method == "poisson" + choices_df = _poisson_sample_alternatives( + chunk_sizer, + probs, + alternatives, + sample_size, + alt_col_name, + state, + trace_label, + stable_alt_positions=stable_alt_positions, + n_total_alts=n_total_alts, + ) + + del utilities + chunk_sizer.log_df(trace_label, "utilities", None) + del probs + chunk_sizer.log_df(trace_label, "probs", None) chunk_sizer.log_df(trace_label, "choices_df", choices_df) @@ -990,7 +936,7 @@ def _interaction_sample( column_labels=["sample_alt", "alternative"], ) - if "rand" in choices_df.columns and not use_eet: + if "rand" in choices_df.columns: # don't need this after tracing del choices_df["rand"] @@ -1100,10 +1046,10 @@ def interaction_sample( sampling_method = _resolve_sample_method(state, compute_settings) logger.debug(f" interaction_sample sample method = {sampling_method}") - if not state.settings.use_explicit_error_terms: - # Do not support stable alt positions or tracking total alts when running with MC sampling. We do - # not want to introduce any additional changes while adding eet simulation support to ensure no - # regressions. We can add these features later if desired. + if sampling_method == "monte_carlo": + # The MC sampling path (make_sample_choices) does not consume stable_alt_positions + # or n_total_alts. Null them out so callers that conservatively pass values along + # don't accidentally rely on them under MC sampling. stable_alt_positions = None n_total_alts = None diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index c85a1649dd..f2dd711257 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -394,6 +394,170 @@ def test_interaction_sample_peaked_utilities_poisson_matches_inclusion_shares(st assert abs(poisson_shares.get(1, 0.0) - expected_poisson_shares[1]) < 0.002 +def _shares_for_sample( + state, + choosers, + alternatives, + spec, + sample_size, + *, + use_eet, + sample_method, + seed, + step_name, +): + state.init_state() + state.settings.use_explicit_error_terms = use_eet + state.rng().set_base_seed(seed) + state.rng().add_channel("person_id", choosers) + state.rng().begin_step(step_name) + compute_settings = ( + ComputeSettings(sample_method=sample_method) if sample_method else None + ) + choices = interaction_sample.interaction_sample( + state, + choosers, + alternatives, + spec, + sample_size=sample_size, + alt_col_name="alt_id", + compute_settings=compute_settings, + ) + return choices, _weighted_shares(choices) + + +def test_interaction_sample_eet_sampling_under_mc_simulation(state): + # use_eet=False + sample_method="eet" was silently ignored before the + # sampling/simulation decoupling. The dispatch now keys on sampling_method + # only, so this combo must produce shares that match use_eet=True + eet. + num_choosers = 100_000 + num_alts = 100 + sample_size = 10 + + rng = np.random.default_rng(42) + choosers = pd.DataFrame( + {"chooser_attr": rng.random(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + alternatives = pd.DataFrame( + {"alt_attr": rng.random(num_alts)}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), + ) + + _, shares_mc_sim = _shares_for_sample( + state, choosers, alternatives, spec, sample_size, + use_eet=False, sample_method="eet", seed=42, + step_name="test_eet_under_mc_sim", + ) + _, shares_eet_sim = _shares_for_sample( + state, choosers, alternatives, spec, sample_size, + use_eet=True, sample_method="eet", seed=42, + step_name="test_eet_under_eet_sim", + ) + + all_alts = set(shares_mc_sim.index) | set(shares_eet_sim.index) + for alt in all_alts: + diff = abs(shares_mc_sim.get(alt, 0.0) - shares_eet_sim.get(alt, 0.0)) + assert diff < 0.01, ( + f"EET sampling shares should not depend on simulation mode at alt {alt}: " + f"mc_sim={shares_mc_sim.get(alt, 0.0):.4f}, " + f"eet_sim={shares_eet_sim.get(alt, 0.0):.4f}, diff={diff:.4f}" + ) + + +def test_interaction_sample_poisson_sampling_under_mc_simulation(state): + # use_eet=False + sample_method="poisson" used to silently fall through to MC + # sampling and then have pick_count forced to 1, corrupting results. After + # decoupling, the combo must run the Poisson path and match use_eet=True + poisson. + num_choosers = 100_000 + num_alts = 100 + sample_size = 10 + + rng = np.random.default_rng(42) + choosers = pd.DataFrame( + {"chooser_attr": rng.random(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + alternatives = pd.DataFrame( + {"alt_attr": rng.random(num_alts)}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), + ) + + choices_mc_sim, shares_mc_sim = _shares_for_sample( + state, choosers, alternatives, spec, sample_size, + use_eet=False, sample_method="poisson", seed=42, + step_name="test_poisson_under_mc_sim", + ) + _, shares_eet_sim = _shares_for_sample( + state, choosers, alternatives, spec, sample_size, + use_eet=True, sample_method="poisson", seed=42, + step_name="test_poisson_under_eet_sim", + ) + + # Poisson contract: pick_count must be uniformly 1 + assert (choices_mc_sim["pick_count"] == 1).all(), ( + "Poisson sampling under MC simulation must produce pick_count=1; got " + f"{choices_mc_sim['pick_count'].value_counts().to_dict()}" + ) + + all_alts = set(shares_mc_sim.index) | set(shares_eet_sim.index) + for alt in all_alts: + diff = abs(shares_mc_sim.get(alt, 0.0) - shares_eet_sim.get(alt, 0.0)) + assert diff < 0.01, ( + f"Poisson sampling shares should not depend on simulation mode at alt {alt}: " + f"mc_sim={shares_mc_sim.get(alt, 0.0):.4f}, " + f"eet_sim={shares_eet_sim.get(alt, 0.0):.4f}, diff={diff:.4f}" + ) + + +def test_interaction_sample_mc_sampling_under_eet_simulation(state): + num_choosers = 100_000 + num_alts = 100 + sample_size = 10 + + rng = np.random.default_rng(42) + choosers = pd.DataFrame( + {"chooser_attr": rng.random(num_choosers)}, + index=pd.Index(range(num_choosers), name="person_id"), + ) + alternatives = pd.DataFrame( + {"alt_attr": rng.random(num_alts)}, + index=pd.Index(range(num_alts), name="alt_id"), + ) + spec = pd.DataFrame( + {"coefficient": [1.0]}, + index=pd.Index(["chooser_attr * alt_attr"], name="Expression"), + ) + + _, shares_mc_sim = _shares_for_sample( + state, choosers, alternatives, spec, sample_size, + use_eet=False, sample_method="monte_carlo", seed=42, + step_name="test_mc_under_mc_sim", + ) + _, shares_eet_sim = _shares_for_sample( + state, choosers, alternatives, spec, sample_size, + use_eet=True, sample_method="monte_carlo", seed=42, + step_name="test_mc_under_eet_sim", + ) + + all_alts = set(shares_mc_sim.index) | set(shares_eet_sim.index) + for alt in all_alts: + diff = abs(shares_mc_sim.get(alt, 0.0) - shares_eet_sim.get(alt, 0.0)) + assert diff < 0.01, ( + f"MC sampling shares should not depend on simulation mode at alt {alt}: " + f"mc_sim={shares_mc_sim.get(alt, 0.0):.4f}, " + f"eet_sim={shares_eet_sim.get(alt, 0.0):.4f}, diff={diff:.4f}" + ) + + class _DummyChunkSizer: def log_df(self, *_args, **_kwargs): return None @@ -621,14 +785,13 @@ def test_poisson_sample_alternatives_falls_back_to_random_sampling_after_ten_ret pd.testing.assert_frame_equal(choices_df, expected_choices_df) -def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_heterogeneity(): +def test_poisson_sample_alternatives_repeat_alignment_chooser_dominant_heterogeneity(): # Edge case: utilities are close across alternatives but vary strongly by chooser. # This checks that the flattened Poisson result keeps chooser/prob alignment. chooser_index = pd.Index([101, 102, 103, 104, 105, 106], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([0, 1, 2, 3], name="alt_id")) - n_alts = len(alternatives) sample_size = 3 # Very small alternative differences... @@ -654,20 +817,6 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het ) state = _DummyState(_SequentialDummyRng([poisson_draws])) - out = interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=sample_size, - alternative_count=n_alts, - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_repeat_alignment_chooser_heterogeneity", - chunk_sizer=_DummyChunkSizer(), - sampling_method="poisson", - ) - probs = interaction_sample.logit.utils_to_probs( state, utilities, @@ -675,8 +824,20 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het trace_label="test_repeat_alignment_chooser_heterogeneity", overflow_protection=True, trace_choosers=choosers, - ).to_numpy() - inclusion_probs = 1 - np.power(1 - probs, sample_size) + ) + + out = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + alternatives=alternatives, + sample_size=sample_size, + alt_col_name="alt_id", + state=state, + trace_label="test_repeat_alignment_chooser_heterogeneity", + ) + + probs_np = probs.to_numpy() + inclusion_probs = 1 - np.power(1 - probs_np, sample_size) sampled_values = np.where(poisson_draws < inclusion_probs, inclusion_probs, np.nan) chooser_idx, alt_idx = np.nonzero(~np.isnan(sampled_values)) @@ -691,7 +852,7 @@ def test_make_sample_choices_utility_based_repeat_alignment_chooser_dominant_het pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) -def test_make_sample_choices_utility_based_poisson_retry_matches_materialized_path(): +def test_poisson_sample_alternatives_retry_matches_materialized_path(): chooser_index = pd.Index([201, 202, 203], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([10, 11, 12, 13], name="alt_id")) @@ -711,20 +872,6 @@ def test_make_sample_choices_utility_based_poisson_retry_matches_materialized_pa retry_draw = np.array([[0.40, 0.10, 0.90, 0.90]], dtype=np.float64) state = _DummyState(_SequentialDummyRng([poisson_draws, retry_draw])) - out = interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, - alternatives=alternatives, - sample_size=sample_size, - alternative_count=len(alternatives), - alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_fused_rng_matches_materialized", - chunk_sizer=_DummyChunkSizer(), - sampling_method="poisson", - ) - probs = interaction_sample.logit.utils_to_probs( state, utilities, @@ -732,8 +879,20 @@ def test_make_sample_choices_utility_based_poisson_retry_matches_materialized_pa trace_label="test_fused_rng_matches_materialized", overflow_protection=True, trace_choosers=choosers, - ).to_numpy() - inclusion_probs = 1 - np.power(1 - probs, sample_size) + ) + + out = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, + alternatives=alternatives, + sample_size=sample_size, + alt_col_name="alt_id", + state=state, + trace_label="test_fused_rng_matches_materialized", + ) + + probs_np = probs.to_numpy() + inclusion_probs = 1 - np.power(1 - probs_np, sample_size) sampled_values = np.full(inclusion_probs.shape, np.nan) first_pass = np.where(poisson_draws < inclusion_probs, inclusion_probs, np.nan) first_pass_empty = np.isnan(first_pass).all(axis=1) @@ -757,7 +916,7 @@ def test_make_sample_choices_utility_based_poisson_retry_matches_materialized_pa pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) -def test_make_sample_choices_utility_based_eet_matches_materialized_path(): +def test_make_sample_choices_eet_matches_materialized_path(): chooser_index = pd.Index([201, 202, 203], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([10, 11, 12, 13], name="alt_id")) @@ -766,7 +925,6 @@ def test_make_sample_choices_utility_based_eet_matches_materialized_path(): index=chooser_index, ) sample_size = 2 - n_alts = len(alternatives) rands_3d = np.array( [ [[0.1, -0.3], [0.2, 0.4], [0.5, -0.1], [0.0, 0.2]], @@ -777,18 +935,25 @@ def test_make_sample_choices_utility_based_eet_matches_materialized_path(): ) state = _DummyState(_DummyRngUtilityBased(rands_3d)) - out = interaction_sample.make_sample_choices_utility_based( + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_make_sample_choices_eet_matches_materialized_path", + overflow_protection=True, + trace_choosers=choosers, + ) + + out = interaction_sample.make_sample_choices_eet( state=state, choosers=choosers, utilities=utilities, + probs=probs, alternatives=alternatives, sample_size=sample_size, - alternative_count=n_alts, alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_eet_matches_materialized_path", + trace_label="test_make_sample_choices_eet_matches_materialized_path", chunk_sizer=_DummyChunkSizer(), - sampling_method="eet", ) chosen_positions = np.argmax( @@ -797,19 +962,11 @@ def test_make_sample_choices_utility_based_eet_matches_materialized_path(): ) chosen_flat = chosen_positions.reshape(-1) chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) - probs = interaction_sample.logit.utils_to_probs( - state, - utilities, - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_eet_matches_materialized_path", - overflow_protection=True, - trace_choosers=choosers, - ).to_numpy() expected = pd.DataFrame( { "person_id": choosers.index.values[chooser_idx], - "prob": probs[chooser_idx, chosen_flat], + "prob": probs.to_numpy()[chooser_idx, chosen_flat], "alt_id": alternatives.index.values[chosen_flat], } ) @@ -817,7 +974,7 @@ def test_make_sample_choices_utility_based_eet_matches_materialized_path(): pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) -def test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materialized_path(): +def test_make_sample_choices_eet_stable_alt_mapping_matches_materialized_path(): chooser_index = pd.Index([301, 302], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([10, 12, 14], name="alt_id")) @@ -837,18 +994,25 @@ def test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materi ) state = _DummyState(_DummyRngUtilityBased(dense_rands_3d)) - out = interaction_sample.make_sample_choices_utility_based( + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_make_sample_choices_eet_stable_alt_mapping_matches_materialized_path", + overflow_protection=True, + trace_choosers=choosers, + ) + + out = interaction_sample.make_sample_choices_eet( state=state, choosers=choosers, utilities=utilities, + probs=probs, alternatives=alternatives, sample_size=sample_size, - alternative_count=len(alternatives), alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materialized_path", + trace_label="test_make_sample_choices_eet_stable_alt_mapping_matches_materialized_path", chunk_sizer=_DummyChunkSizer(), - sampling_method="eet", stable_alt_positions=stable_alt_positions, n_total_alts=n_total_alts, ) @@ -860,19 +1024,11 @@ def test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materi ) chosen_flat = chosen_positions.reshape(-1) chooser_idx = np.repeat(np.arange(len(choosers)), sample_size) - probs = interaction_sample.logit.utils_to_probs( - state, - utilities, - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materialized_path", - overflow_protection=True, - trace_choosers=choosers, - ).to_numpy() expected = pd.DataFrame( { "person_id": choosers.index.values[chooser_idx], - "prob": probs[chooser_idx, chosen_flat], + "prob": probs.to_numpy()[chooser_idx, chosen_flat], "alt_id": alternatives.index.values[chosen_flat], } ) @@ -880,7 +1036,7 @@ def test_make_sample_choices_utility_based_eet_stable_alt_mapping_matches_materi pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) -def test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_materialized_path(): +def test_poisson_sample_alternatives_stable_alt_mapping_matches_materialized_path(): chooser_index = pd.Index([311, 312], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([10, 12, 14], name="alt_id")) @@ -900,31 +1056,29 @@ def test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_ma ) state = _DummyState(_SequentialDummyRng([dense_uniforms])) - out = interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_poisson_sample_alternatives_stable_alt_mapping_matches_materialized_path", + overflow_protection=True, + trace_choosers=choosers, + ) + + out = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, alternatives=alternatives, sample_size=sample_size, - alternative_count=len(alternatives), alt_col_name="alt_id", - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_materialized_path", - chunk_sizer=_DummyChunkSizer(), - sampling_method="poisson", + state=state, + trace_label="test_poisson_sample_alternatives_stable_alt_mapping_matches_materialized_path", stable_alt_positions=stable_alt_positions, n_total_alts=n_total_alts, ) - probs = interaction_sample.logit.utils_to_probs( - state, - utilities, - allow_zero_probs=False, - trace_label="test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_materialized_path", - overflow_protection=True, - trace_choosers=choosers, - ).to_numpy() - inclusion_probs = 1 - np.power(1 - probs, sample_size) + probs_np = probs.to_numpy() + inclusion_probs = 1 - np.power(1 - probs_np, sample_size) active_uniforms = dense_uniforms[:, stable_alt_positions] sampled_values = np.where( active_uniforms < inclusion_probs, inclusion_probs, np.nan @@ -942,7 +1096,7 @@ def test_make_sample_choices_utility_based_poisson_stable_alt_mapping_matches_ma pd.testing.assert_frame_equal(out.reset_index(drop=True), expected) -def test_make_sample_choices_utility_based_falls_back_after_retries(): +def test_poisson_sample_alternatives_falls_back_after_retries(): chooser_index = pd.Index([301, 302], name="person_id") choosers = pd.DataFrame(index=chooser_index) alternatives = pd.DataFrame(index=pd.Index([10, 12, 14], name="alt_id")) @@ -961,18 +1115,23 @@ def test_make_sample_choices_utility_based_falls_back_after_retries(): ) state = _DummyState(_SequentialDummyRng([fail_draw] * 10 + [fallback_draw])) - out = interaction_sample.make_sample_choices_utility_based( - state=state, - choosers=choosers, - utilities=utilities, + probs = interaction_sample.logit.utils_to_probs( + state, + utilities, + allow_zero_probs=False, + trace_label="test_falls_back_after_retries", + overflow_protection=True, + trace_choosers=choosers, + ) + + out = interaction_sample._poisson_sample_alternatives( + chunk_sizer=_DummyChunkSizer(), + probs=probs, alternatives=alternatives, sample_size=sample_size, - alternative_count=len(alternatives), alt_col_name="alt_id", - allow_zero_probs=False, + state=state, trace_label="test_falls_back_after_retries", - chunk_sizer=_DummyChunkSizer(), - sampling_method="poisson", ) expected = pd.DataFrame( From dc529075cc10640b00d286c92130350f4bf57295 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 18 May 2026 16:41:10 +1000 Subject: [PATCH 267/298] separate RNG for shadow pricing to enable loc choiec rng reset for simulation method --- activitysim/abm/models/location_choice.py | 16 +++- activitysim/abm/tables/shadow_pricing.py | 55 ++++++++++- .../test_misc/test_shadow_pricing_simulate.py | 94 +++++++++++++++++++ 3 files changed, 159 insertions(+), 6 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 6863ef55db..e149ea0dd0 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -1107,15 +1107,16 @@ def iterate_location_choice( persons_merged_df_ = persons_merged_df_.sort_index() # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is - # used on each iteration. Note this has to happen AFTER updating shadow prices because the simulation method - # draws random numbers. - # Only applying when using EET for now because this will need changes to integration - # tests, but it's probably a good idea for MC simulation as well. + # used on each iteration for the persons being re-simulated, so sampling and final choice draws are + # reproducible across shadow-pricing iterations. + # Scoped to the persons channel for these specific rows via reset_offsets_for_df so the dedicated + # shadow_pricing_persons channel (registered under EET) keeps its offset across iterations and advances + # naturally on each iteration's update_shadow_prices call. if state.settings.use_explicit_error_terms and iteration > 1: logger.debug( f"{trace_label} resetting random number generator offsets for iteration {iteration}" ) - state.get_rn_generator().reset_offsets_for_step(state.current_model_name) + state.get_rn_generator().reset_offsets_for_df(persons_merged_df_) choices_df_, save_sample_df = run_location_choice( state, @@ -1178,6 +1179,11 @@ def iterate_location_choice( ) break + # Drop the dedicated shadow_pricing RNG channel (registered lazily under EET by spc.update_shadow_prices) so it + # doesn't survive into the next location_choice model (e.g., school after work) — both models share the same + # channel name and would otherwise collide on the no-overlap assert in SimpleChannel.extend_domain. No-op for MC. + spc.cleanup_rng_channel(state) + # - shadow price table if locutor: if spc.use_shadow_pricing and model_settings.SHADOW_PRICE_TABLE: diff --git a/activitysim/abm/tables/shadow_pricing.py b/activitysim/abm/tables/shadow_pricing.py index 04c5eafc21..75667f3f0e 100644 --- a/activitysim/abm/tables/shadow_pricing.py +++ b/activitysim/abm/tables/shadow_pricing.py @@ -290,6 +290,9 @@ def __init__( self.choices_by_iteration = pd.DataFrame() self.global_pending_persons = 1 self.sampled_persons = pd.DataFrame() + # Under EET, simulation-method shadow pricing uses a dedicated RNG channel to be independent of the location + # choice randoms. Registered lazily on first call to update_shadow_prices. + self._sp_rng_channel_registered = False if ( self.use_shadow_pricing @@ -699,6 +702,44 @@ def check_fit(self, state: workflow.State, iteration): return converged + _SP_RNG_CHANNEL = "shadow_pricing_persons" + + def cleanup_rng_channel(self, state): + """ + Drop the dedicated shadow_pricing_persons RNG channel if it was registered. Called at the end of + iterate_location_choice so the channel doesn't survive into the next model (e.g., school after work) — which + would otherwise fail the no-overlap assert in SimpleChannel.extend_domain when the next SPC tries to register + the same persons. No-op under MC (channel was never registered). + """ + if not self._sp_rng_channel_registered: + return + state.get_rn_generator().drop_channel(self._SP_RNG_CHANNEL) + self._sp_rng_channel_registered = False + + def _ensure_sp_rng_channel(self, state): + """ + Lazily register a dedicated RNG channel for shadow-pricing re-simulation draws under EET. The channel covers + the same persons as the main persons channel but has its own per-person offsets, so its draws don't consume + the main persons channel and aren't reset by the per-iteration location-choice reset. + """ + if self._sp_rng_channel_registered: + return + if not ( + self.use_shadow_pricing + and self.shadow_settings.SHADOW_PRICE_METHOD == "simulation" + and state.settings.use_explicit_error_terms + ): + return + persons = state.get_dataframe("persons_merged") + # add_channel only consumes the index; the renamed axis is what maps this channel to probs DataFrames with + # index name SP_RNG_CHANNEL. We rename the axis on a thin view (one dummy column) so the domain DF isn't + # pandas-"empty" (which would log a spurious warning). + domain = pd.DataFrame( + {"_": 0}, index=persons.index.rename(self._SP_RNG_CHANNEL) + ) + state.get_rn_generator().add_channel(self._SP_RNG_CHANNEL, domain) + self._sp_rng_channel_registered = True + def update_shadow_prices(self, state): """ Adjust shadow_prices based on relative values of modeled_size and desired_size. @@ -738,6 +779,8 @@ def update_shadow_prices(self, state): assert self.desired_size is not None assert self.shadow_prices is not None + self._ensure_sp_rng_channel(state) + if shadow_price_method == "ctramp": # - CTRAMP """ @@ -899,12 +942,22 @@ def update_shadow_prices(self, state): if (len(choices) > 0) & (~converged): # person's probability of being selected for re-simulation is from the zonal sample rate sample_rates = choices.map(zonal_sample_rate.to_dict()) + # Under EET we route through a dedicated RNG channel so shadow-pricing draws are isolated from the + # persons-channel reset that location_choice does between iterations. Under MC we leave the index + # name alone so draws continue to consume the main persons channel exactly as before. + probs_index = choices.index + if state.settings.use_explicit_error_terms: + probs_index = probs_index.rename(self._SP_RNG_CHANNEL) probs = pd.DataFrame( data={"0": 1 - sample_rates, "1": sample_rates}, - index=choices.index, + index=probs_index, ) # using ActivitySim's RNG to make choices for repeatability current_sample, rands = logit.make_choices(state, probs) + if state.settings.use_explicit_error_terms: + current_sample.index = current_sample.index.rename( + choices.index.name + ) current_sample = current_sample[current_sample == 1] if len(sampled_persons) == 0: diff --git a/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py b/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py index 22de62a6d8..399445a016 100644 --- a/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py +++ b/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py @@ -578,3 +578,97 @@ def test_shadow_pricing_simulate(state, model_settings, network_los): choices_df.index ), ) + + +def test_shadow_pricing_dedicated_rng_channel_eet_only( + state, model_settings, network_los +): + """ + Under EET, ShadowPriceCalculator should register a dedicated + shadow_pricing_persons RNG channel on first call to update_shadow_prices, + and route its re-simulation draws through it so they are isolated from + the main persons channel. Under MC, no channel is registered and the + pre-existing coupling with the main RNG is preserved. + """ + from activitysim.core import logit + + model_settings.LOGSUM_SETTINGS = None + rng = state.get_rn_generator() + + # --- MC case: helper is a no-op --- + state.settings.use_explicit_error_terms = False + spc_mc = shadow_pricing.load_shadow_price_calculator(state, model_settings) + spc_mc._ensure_sp_rng_channel(state) + + assert not spc_mc._sp_rng_channel_registered + assert "shadow_pricing_persons" not in rng.channels + + # --- EET case: channel registered, idempotent, draws advance offsets --- + state.settings.use_explicit_error_terms = True + spc_eet = shadow_pricing.load_shadow_price_calculator(state, model_settings) + spc_eet._ensure_sp_rng_channel(state) + + assert spc_eet._sp_rng_channel_registered + assert "shadow_pricing_persons" in rng.channels + + # Idempotent re-registration + rng_channels_before = set(rng.channels.keys()) + spc_eet._ensure_sp_rng_channel(state) + assert set(rng.channels.keys()) == rng_channels_before + + # Channel covers the same person ids as persons_merged + persons = state.get_dataframe("persons_merged") + sp_channel = rng.channels["shadow_pricing_persons"] + pd.testing.assert_index_equal( + sp_channel.row_states.index, persons.index, check_names=False + ) + + # Draws via make_choices on a probs DF indexed by shadow_pricing_persons + # advance the dedicated channel's offsets each call, demonstrating the + # channel keeps its offset across iterations (no reset between calls). + rng.begin_step("test_shadow_pricing_sp_channel_draws") + + probs = pd.DataFrame( + {"0": [0.5] * len(persons), "1": [0.5] * len(persons)}, + index=persons.index.rename("shadow_pricing_persons"), + ) + + offsets_before = sp_channel.row_states["offset"].copy() + logit.make_choices(state, probs) + offsets_after_first = sp_channel.row_states["offset"].copy() + assert (offsets_after_first > offsets_before).all(), ( + "shadow_pricing_persons channel offsets should advance after first draw" + ) + + logit.make_choices(state, probs) + offsets_after_second = sp_channel.row_states["offset"] + assert (offsets_after_second > offsets_after_first).all(), ( + "shadow_pricing_persons channel offsets should advance further on second draw " + "(channel is not reset between shadow-pricing iterations)" + ) + + rng.end_step("test_shadow_pricing_sp_channel_draws") + + # cleanup_rng_channel drops the channel and resets the flag so the SPC can + # be re-used (or a fresh SPC for the next location_choice model can + # re-register the channel without colliding on extend_domain's no-overlap + # assert in SimpleChannel). + spc_eet.cleanup_rng_channel(state) + assert "shadow_pricing_persons" not in rng.channels + assert not spc_eet._sp_rng_channel_registered + + # Idempotent: calling cleanup again is a no-op + spc_eet.cleanup_rng_channel(state) + + # A fresh SPC can register the channel cleanly after cleanup (simulates the + # work-then-school sequential model pattern). + spc_eet_2 = shadow_pricing.load_shadow_price_calculator(state, model_settings) + spc_eet_2._ensure_sp_rng_channel(state) + assert "shadow_pricing_persons" in rng.channels + spc_eet_2.cleanup_rng_channel(state) + + # cleanup_rng_channel on an MC-only SPC is also a no-op + spc_mc.cleanup_rng_channel(state) + + # Reset for hygiene (other tests in this module assume MC default) + state.settings.use_explicit_error_terms = False From d58d0c57a0d23e325b52b9ae3676df2c63804a6a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 18 May 2026 19:27:32 +1000 Subject: [PATCH 268/298] lint --- .../test_misc/test_shadow_pricing_simulate.py | 18 +++--- .../core/test/test_interaction_sample.py | 60 +++++++++++++++---- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py b/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py index 399445a016..032e2d1aa9 100644 --- a/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py +++ b/activitysim/abm/test/test_misc/test_shadow_pricing_simulate.py @@ -1,17 +1,17 @@ +from __future__ import annotations + import os from pathlib import Path + import numpy as np +import openmatrix as omx import pandas as pd - import pytest -import openmatrix as omx - +from activitysim.abm.models.location_choice import run_location_choice from activitysim.abm.tables import shadow_pricing -from activitysim.core import workflow, los +from activitysim.core import los, workflow from activitysim.core.configuration.logit import TourLocationComponentSettings -from activitysim.abm.models.location_choice import run_location_choice - LAND_USE_FIELDS = [ "e01_nrm", @@ -636,9 +636,9 @@ def test_shadow_pricing_dedicated_rng_channel_eet_only( offsets_before = sp_channel.row_states["offset"].copy() logit.make_choices(state, probs) offsets_after_first = sp_channel.row_states["offset"].copy() - assert (offsets_after_first > offsets_before).all(), ( - "shadow_pricing_persons channel offsets should advance after first draw" - ) + assert ( + offsets_after_first > offsets_before + ).all(), "shadow_pricing_persons channel offsets should advance after first draw" logit.make_choices(state, probs) offsets_after_second = sp_channel.row_states["offset"] diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index f2dd711257..9fd6b267ae 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -449,13 +449,25 @@ def test_interaction_sample_eet_sampling_under_mc_simulation(state): ) _, shares_mc_sim = _shares_for_sample( - state, choosers, alternatives, spec, sample_size, - use_eet=False, sample_method="eet", seed=42, + state, + choosers, + alternatives, + spec, + sample_size, + use_eet=False, + sample_method="eet", + seed=42, step_name="test_eet_under_mc_sim", ) _, shares_eet_sim = _shares_for_sample( - state, choosers, alternatives, spec, sample_size, - use_eet=True, sample_method="eet", seed=42, + state, + choosers, + alternatives, + spec, + sample_size, + use_eet=True, + sample_method="eet", + seed=42, step_name="test_eet_under_eet_sim", ) @@ -492,13 +504,25 @@ def test_interaction_sample_poisson_sampling_under_mc_simulation(state): ) choices_mc_sim, shares_mc_sim = _shares_for_sample( - state, choosers, alternatives, spec, sample_size, - use_eet=False, sample_method="poisson", seed=42, + state, + choosers, + alternatives, + spec, + sample_size, + use_eet=False, + sample_method="poisson", + seed=42, step_name="test_poisson_under_mc_sim", ) _, shares_eet_sim = _shares_for_sample( - state, choosers, alternatives, spec, sample_size, - use_eet=True, sample_method="poisson", seed=42, + state, + choosers, + alternatives, + spec, + sample_size, + use_eet=True, + sample_method="poisson", + seed=42, step_name="test_poisson_under_eet_sim", ) @@ -538,13 +562,25 @@ def test_interaction_sample_mc_sampling_under_eet_simulation(state): ) _, shares_mc_sim = _shares_for_sample( - state, choosers, alternatives, spec, sample_size, - use_eet=False, sample_method="monte_carlo", seed=42, + state, + choosers, + alternatives, + spec, + sample_size, + use_eet=False, + sample_method="monte_carlo", + seed=42, step_name="test_mc_under_mc_sim", ) _, shares_eet_sim = _shares_for_sample( - state, choosers, alternatives, spec, sample_size, - use_eet=True, sample_method="monte_carlo", seed=42, + state, + choosers, + alternatives, + spec, + sample_size, + use_eet=True, + sample_method="monte_carlo", + seed=42, step_name="test_mc_under_eet_sim", ) From 7f6816056c9214cf339591cf3d4bc3b99d5e4f8a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 18 May 2026 21:18:07 +1000 Subject: [PATCH 269/298] logging and comments --- activitysim/abm/models/location_choice.py | 14 ++++++++------ activitysim/abm/models/trip_destination.py | 16 ++++++++++------ activitysim/abm/models/util/tour_destination.py | 14 ++++++++------ activitysim/abm/models/util/tour_od.py | 17 +++++++++++++++-- activitysim/abm/tables/shadow_pricing.py | 5 ++++- activitysim/core/interaction_sample.py | 1 + 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index e149ea0dd0..46e1448540 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -412,12 +412,14 @@ def location_presample( assert (stable_alt_positions >= 0).all() n_total_alts = len(full_taz_index) - # Stable alt positions are only used with explicit error terms and Poisson sampling for - # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms - # with eet sampling alignment would require a large amount of random numbers due to - # potential repeated occurence of MAZs (importance sampling with replacement). This is due - # to how random numbers are generated atm, but with a counter-based RNG this could be - # revisited. + # The TAZ presample call below passes stable_alt_positions for both EET and Poisson sampling, so each TAZ is + # keyed to its position in the full TAZ universe. The MAZ-for-TAZ second stage only receives full_taz_index for + # Poisson: that stage uses one per-(chooser, TAZ) uniform to pick a MAZ within each sampled TAZ. Under Poisson + # each sampled TAZ appears at most once per chooser, so the per-TAZ uniform produces an independent MAZ choice. + # Under EET sampling (importance sampling with replacement) the same TAZ can appear multiple times in a + # chooser's sample and would all share one uniform, forcing every duplicate to pick the same MAZ. An EET-stable + # MAZ-for-TAZ would need a (TAZ, occurrence-rank)-keyed draw and many more random numbers per chooser; that's + # too expensive with the current RNG, revisit if a counter-based RNG is adapted. sample_compute_settings = getattr(model_settings, "compute_settings", None) if sample_compute_settings is not None: sample_compute_settings = sample_compute_settings.subcomponent_settings( diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 824ede937a..08f7f760aa 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -662,12 +662,16 @@ def destination_presample( network_los.map_maz_to_taz(alternatives.index) ).sum() - # Stable alt positions are only used with explicit error terms and Poisson sampling for - # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms - # with eet sampling alignment would require a large amount of random numbers due to - # potential repeated occurence of MAZs (importance sampling with replacement). This is due - # to how random numbers are generated atm, but with a counter-based RNG this could be - # revisited. + # Trip destination keeps the alternative universe in `alternatives`, so the active TAZ set after aggregation always + # equals the full TAZ universe and stable_alt_positions is not needed at the TAZ presample call itself (unlike + # tour_destination / location_choice, which filter zero-attraction zones before presampling). full_taz_index is + # still computed here for the MAZ-for-TAZ second stage, but only for Poisson sampling: that stage uses one + # per-(chooser, TAZ) uniform to pick a MAZ within each sampled TAZ. Under Poisson each sampled TAZ appears at most + # once per chooser, so the per-TAZ uniform produces an independent MAZ choice. Under EET sampling (importance + # sampling with replacement) the same TAZ can appear multiple times in a chooser's sample and would allshare one + # uniform, forcing every duplicate to pick the same MAZ. An EET-stable MAZ-for-TAZ would need a + # (TAZ, occurrence-rank)-keyed draw and many more random numbers per chooser; that's too expensive with the + # current RNG, revisit if a counter-based RNG is adapted. full_taz_index = None if state.settings.use_explicit_error_terms: sample_compute_settings = getattr(model_settings, "compute_settings", None) diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 63d1977572..d60fca05b4 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -634,12 +634,14 @@ def destination_presample( stable_alt_positions = full_taz_index.get_indexer(TAZ_size_terms.index) assert (stable_alt_positions >= 0).all() - # Stable alt positions are only used with explicit error terms and Poisson sampling for - # two-zone systems with pre-sampling due to how MAZs are chosen. For explicit error terms - # with eet sampling alignment would require a large amount of random numbers due to - # potential repeated occurence of MAZs (importance sampling with replacement). This is due - # to how random numbers are generated atm, but with a counter-based RNG this could be - # revisited. + # The TAZ presample call below passes stable_alt_positions for both EET and Poisson sampling, so each TAZ is + # keyed to its position in the full TAZ universe. The MAZ-for-TAZ second stage only receives full_taz_index for + # Poisson: that stage uses one per-(chooser, TAZ) uniform to pick a MAZ within each sampled TAZ. Under Poisson + # each sampled TAZ appears at most once per chooser, so the per-TAZ uniform produces an independent MAZ choice. + # Under EET sampling (importance sampling with replacement) the same TAZ can appear multiple times in a + # chooser's sample and would all share one uniform, forcing every duplicate to pick the same MAZ. An + # EET-stable MAZ-for-TAZ would need a (TAZ, occurrence-rank)-keyed draw and many more random numbers per + # chooser; that's too expensive with the current RNG, revisit if a counter-based RNG is adapted. sample_compute_settings = getattr(model_settings, "compute_settings", None) if sample_compute_settings is not None: sample_compute_settings = sample_compute_settings.subcomponent_settings( diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 6c83c1f484..0e490207d7 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -216,8 +216,10 @@ def _od_sample( preprocessor_setting_name="alts_preprocessor_sample", ) - # Note not using stable alternative positions for EET here, the cross-product of origins and destinations - # is too large for the way the RNG currently works. + # Not passing stable_alt_positions here: the cross product of origins and destinations + # would make the per-chooser draw count (n_total_alts, or n_total_alts * sample_size for + # EET sampling) prohibitive under the current sequential RNG. Revisit with a counter-based + # RNG. choices = interaction_sample( state, choosers, @@ -660,6 +662,17 @@ def od_presample( destination_size_terms, network_los ) + # The OD presample call below does not pass stable_alt_positions: the cross product of + # origins and destinations is too large for the current sequential-RNG cost (see comment + # at the OD sample call in _od_sample). full_taz_index is still computed here for the + # MAZ-for-TAZ second stage, but only for Poisson sampling: that stage uses one + # per-(chooser, TAZ) uniform to pick a MAZ within each sampled TAZ. Under Poisson each + # sampled TAZ appears at most once per chooser, so the per-TAZ uniform produces an + # independent MAZ choice. Under EET sampling (importance sampling with replacement) the + # same TAZ can appear multiple times in a chooser's sample and would all share one + # uniform, forcing every duplicate to pick the same MAZ. An EET-stable MAZ-for-TAZ would + # need a (TAZ, occurrence-rank)-keyed draw and many more random numbers per chooser; that's + # too expensive with the current RNG, revisit if a counter-based RNG is adapted. taz_sample_method = resolve_sample_method(state, model_settings) if taz_sample_method == "poisson": full_taz_index = pd.Index( diff --git a/activitysim/abm/tables/shadow_pricing.py b/activitysim/abm/tables/shadow_pricing.py index 75667f3f0e..b3c20108e8 100644 --- a/activitysim/abm/tables/shadow_pricing.py +++ b/activitysim/abm/tables/shadow_pricing.py @@ -17,8 +17,8 @@ from activitysim.core import logit, tracing, util, workflow from activitysim.core.configuration import PydanticReadable from activitysim.core.configuration.logit import TourLocationComponentSettings +from activitysim.core.exceptions import MissingNameError, SystemConfigurationError from activitysim.core.input import read_input_table -from activitysim.core.exceptions import SystemConfigurationError, MissingNameError logger = logging.getLogger(__name__) @@ -947,6 +947,9 @@ def update_shadow_prices(self, state): # name alone so draws continue to consume the main persons channel exactly as before. probs_index = choices.index if state.settings.use_explicit_error_terms: + logger.debug( + f"Renaming probs index from {probs_index.name} to {self._SP_RNG_CHANNEL} for EET RNG channel matching." + ) probs_index = probs_index.rename(self._SP_RNG_CHANNEL) probs = pd.DataFrame( data={"0": 1 - sample_rates, "1": sample_rates}, diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 2ac2024487..a462fc288a 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -49,6 +49,7 @@ def _resolve_sample_method( raise ValueError( f"Unsupported sample_method {sampling_method!r}; expected one of {typing.get_args(InteractionSampleMethod)}" ) + logger.info(f"Using sample_method={sampling_method}") return sampling_method From e6b75eabe8333db2060be13e99464ba906c85b1b Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 20 May 2026 17:37:56 +1000 Subject: [PATCH 270/298] debug logging for sample method, add warning for disagg acc and poisson --- .../abm/models/disaggregate_accessibility.py | 13 ++++++++++++- activitysim/core/interaction_sample.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 5fe3375b31..733c6419d4 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -16,12 +16,13 @@ from activitysim.abm.tables import shadow_pricing from activitysim.core import estimation, los, tracing, util, workflow from activitysim.core.configuration.base import ( + ComputeSettings, PreprocessorSettings, PydanticReadable, - ComputeSettings, ) from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.expressions import assign_columns +from activitysim.core.interaction_sample import _resolve_sample_method logger = logging.getLogger(__name__) @@ -760,6 +761,16 @@ def get_disaggregate_logsums( state, "disaggregate_accessibility.yaml" ) + if _resolve_sample_method(state, disagg_model_settings) == "poisson": + logger.warning( + "Using Poisson sampling method for disaggregate accessibility calculations. Currently the results will" + + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" + + f" log({disagg_model_settings.DESTINATION_SAMPLE_SIZE}) if you are using the common correction factor" + + " `log(pick_count / prob)` in location choice specs. The results of the Poisson method are unbiased," + + " i.e., they agree with the results obtained with a full destination sample, unlike those for" + + " monte-carlo or eet sampling." + ) + for model_name in [ "workplace_location", "school_location", diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index a462fc288a..b438745763 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -49,7 +49,7 @@ def _resolve_sample_method( raise ValueError( f"Unsupported sample_method {sampling_method!r}; expected one of {typing.get_args(InteractionSampleMethod)}" ) - logger.info(f"Using sample_method={sampling_method}") + logger.debug(f"Using sample_method={sampling_method}") return sampling_method From 4803032be6d17e2260633f5d7e87ae2e0bed4157 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 20 May 2026 19:02:11 +1000 Subject: [PATCH 271/298] doco --- docs/dev-guide/explicit-error-terms.md | 36 ++--- docs/dev-guide/sampling-methods.md | 189 ++++++++++--------------- docs/users-guide/sampling-methods.rst | 23 ++- docs/users-guide/ways_to_run.rst | 4 +- 4 files changed, 113 insertions(+), 139 deletions(-) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 6fefe411e2..99e65772d5 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -82,8 +82,8 @@ but real benefits can show up as negative in a single run. Under EET, the sign o is far more trustworthy. Independent of any statistical argument, under EET, choice changes between two runs are -causally attributable to utility changes which can be helpful for model development, -sensitivity testing, and defending results to stakeholders. +attributable to utility changes which can be helpful for model development, sensitivity +testing, and presenting results to stakeholders. ### Aligning error terms @@ -103,31 +103,33 @@ this new mode should also be in the specification of the run where it is not ava its utility specification such that it is never chosen. In case the model is nested logit, the nesting structure also needs to be held constant across scenarios. For location choice models, all alternatives need to be listed in the land use table and the -zone IDs need to be stable between scenarios. Additionally, for computational efficiency -EET requires -0-based, contiguous zone IDs. For models where this is not the case, ActivitySim can -automatically perform the conversion for internal calculations, see -{ref}`explicit_error_terms_zone_encoding` for how to set this up. +zone IDs need to be stable between scenarios. Additionally, for computational efficiency it +is recommended to have zone IDs that are a contiguous 0-based sequence because ActivitySim aligns +random draws to positions in the full zone universe and generates draws for all zone IDs up to the +maximum. For models where this is not the case, ActivitySim can automatically perform the +conversion for internal calculations. The `recode_columns` option creates contiguous zero-based IDs +where needed; see the +[Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. For models that use sub-sampling of alternatives, it is important to keep the sampling scheme identical between scenarios, otherwise the error terms for the choice from the sampled set are -not guaranteed to be aligned. +not guaranteed to be aligned. When running with EET, the default sampling method is ``poisson``, +which balances runtime performance and noise reduction. For more details on sampling methods, +see {ref}`sampling_methods_dev_guide`. -Finally, it also important to keep the global random number generator seed constant for -comparison runs. +Finally, it also important to keep the global random number generator seed constant for two +individual comparison runs. ### Runtime and memory usage EET draws one error term per chooser and alternative, which requires many more random numbers than MC's one per chooser. For models with many alternatives, this can lead to a large amount -of random numbers being calculated. To keep memory usage in line with MC simulation, the -implementation of EET avoids materialization of large chooser-alternative arrays of error -terms in memory. -Regarding runtimes, EET with default settings currently carries a runtime penalty of about 5-10% +of random numbers being calculated. The implementation of EET avoids materialization of large +chooser-alternative arrays of error terms in memory so that the memory usage is in line with MC +simulation. +Regarding runtimes, EET with default settings currently carries a runtime penalty of about 3-10% per demand model run. However, when run in combination with an assignment model the overall -system converges faster and can cancel out any runtime penalty completely. Precise numbers are -hard to provide, but overall runtime and memory usage should not differ from runs with MC too -much. +system can converge faster and this can reduce the overall model runtime penalty. + +A chooser can occasionally receive no sampled alternatives under Poisson sampling, because each +alternative is tested independently. In the models that use sampling in ActivitySim, this should be +rare. If it happens, the sampler retries that chooser row up to 10 times and then falls back to a +simple without-replacement random sample. + + + +### Sampling Correction + +`interaction_sample` returns a dataframe indexed by chooser id with columns including: + +- the sampled alternative id column +- `prob` +- `pick_count` -ActivitySim's final sampled-choice specs typically include the term: +For `monte_carlo` and `eet`, `pick_count` is the number of times the alternative was selected in +the repeated with-replacement draws. For `poisson`, `pick_count` is always `1`, because an +alternative is either included or not included. For all methods, `prob` is the quantity used in +the correction term, but it means different things for different methods. ActivitySim's final +sampled-choice specs typically include the term: ```python np.log(df.pick_count/df.prob) @@ -100,10 +127,10 @@ This is the sample-of-alternatives correction factor used in the final choice mo For `monte_carlo` and `eet`, `prob` is the one-draw sampling probability implied by the approximate sampling utility, and `pick_count` is the number of times that alternative appeared in -the repeated sample. In textbook notation, the correction for repeated with-replacement sampling is -proportional to `pick_count / (sample_size * prob)`. ActivitySim omits the common `sample_size` -factor because it is the same for every sampled alternative for that chooser and therefore adds -only a chooser-specific constant to utility, which does not affect probabilities. +the repeated sample. McFadden's utility correction term for repeated with-replacement sampling is +`log(pick_count / (sample_size * prob)) = log(pick_count / prob) - log(sample_size)`. ActivitySim +omits the common `sample_size` term because it is the same for every sampled alternative for that +chooser and therefore does not affect choice probabilities. For `poisson`, `prob` is the inclusion probability of the alternative in the sampled set, not the one-draw choice probability. Specifically, if the original approximate choice probability is $p$ @@ -113,101 +140,35 @@ $$ 1 - (1 - p)^s $$ -Since `pick_count` is always `1` for `poisson`, the correction becomes exactly -$\log(1 / \text{prob})$. +Since `pick_count` is always `1` for `poisson`, the correction becomes $\log(1 / \text{prob})$. This means that all three methods use the same correction expression, `np.log(df.pick_count/df.prob)`, even though `prob` has a different interpretation for `poisson` than for the with-replacement methods. -## Methods in Detail - -### Monte Carlo and EET-with-replacement - -The `monte_carlo` and `eet` sampling methods both draw alternatives with replacement. As a result, -duplicates are possible within a chooser's sampled set, and sampled shares track repeated-draw MNL -behavior closely. - -The difference between them is how each draw is made: - -- `monte_carlo` draws from analytical probabilities using uniform random numbers -- `eet` draws explicit EV1 error terms and chooses the utility-plus-error argmax - -For both methods, the returned `prob` column is the one-draw sampling probability of the selected -alternative under the approximate sampling utility. If an alternative is drawn multiple times, the -duplicate rows are collapsed and the total multiplicity is stored in `pick_count`. - -These methods are useful when the sampled set should behave like repeated draws from the -approximate choice model. `eet` preserves that with-replacement behavior while also freezing the -unobserved draws, which can greatly reduce scenario-to-scenario sampling noise. - -### Poisson Sampling - -`poisson` does not perform repeated draws with replacement. Instead, each chooser-alternative -pair is sampled independently with inclusion probability -$1 - (1 - p)^s$, where $p$ is the original choice probability and $s$ is the configured -sample size. - -Because sampled alternatives appear at most once per chooser, raw sampled shares can differ -noticeably from repeated-draw MNL shares in highly peaked cases. This is structural behavior, not -numerical noise. The interaction-sample tests document this explicitly. - -A chooser can occasionally receive no sampled alternatives under Poisson sampling, because each -alternative is tested independently. In the models that use sampling in ActivitySim, this should be -rare. If it happens, the sampler retries that chooser row up to 10 times and then falls back to a -simple without-replacement random sample. This makes the method robust, but it also creates rare -edge cases where two nearby scenarios consume different random numbers because one scenario needed -retries or fallback and the other did not. +<> ## Runtime and Simulation Noise Runtime and noise characteristics differ across methods. -- `monte_carlo` is usually the cheapest method. It draws one uniform random number per repeated - sample, but it also has the most simulation noise because small changes in approximate +- `monte_carlo` is the fastest method. It draws one uniform random number per repeated sample for + each chooser, but it also has the most simulation noise because small changes in approximate probabilities can change the sampled set substantially. -- `poisson` is also relatively inexpensive. It draws one Bernoulli inclusion test per +- `poisson` is also relatively inexpensive. It draws one uniform random number per chooser-alternative pair, with possible retries for chooser rows that initially sample no - alternatives. With stable alternative alignment it is much less noisy than Monte Carlo, but it - can still show structural sample-set differences in highly peaked cases and rare retry edge - cases. -- `eet` is usually the most expensive sampling method. It draws one EV1 error term per chooser, - alternative, and repeated sample draw. In return, it produces the most stable sampled sets across - nearby scenarios because unchanged alternatives can keep the same unobserved error draws. - -For location choice models, this often leads to a practical ranking of: - -- runtime: `monte_carlo` and `poisson` low, `eet` high -- simulation noise: `monte_carlo` high, `poisson` low, `eet` lowest - -`eet` does not remove the dependence on the approximate sampling utility itself: if that utility -changes, the sampled set can still change. What it removes is the extra Monte Carlo noise from the -sampling draw. `poisson` also benefits from stable alignment, but unlike `eet` it still depends on -probability-based inclusion tests and retains the retry/fallback edge case described above. The -practical effect on scenario comparisons is ultimately empirical. - - -(explicit_error_terms_zone_encoding)= -### Runtime and Zone Encoding -For location choice models, ActivitySim can align random draws to positions in the full zone -universe rather than only to the alternatives active in the current sampled set. This keeps the -same zone attached to the same random draws regardless of which alternatives are present in a -particular chooser's calculation. - -Both aligned `eet` and aligned `poisson` sampling use this stable mapping. For `eet`, each chooser -receives `sample_size` sets of Gumbel draws over the full encoded zone universe, and the active -alternatives are selected from those draws by their stable zone positions. For `poisson`, each -chooser receives one aligned uniform draw per encoded zone, and those draws are used for the -Bernoulli inclusion tests. - -When zone IDs are a contiguous 0-based sequence, the aligned draw universe has exactly as many -positions as there are zones and every position is potentially useful. When zone IDs contain gaps -or start from a large value, the implementation must still cover the full encoded range, so draws -for missing IDs are generated but never used. This increases runtime and memory use, especially -for `eet`, where the aligned draw cost also scales with `sample_size`. - -ActivitySim's `recode_columns` option can create contiguous zero-based IDs where needed; see the -[Zero-based Recoding of Zones](using-sharrow.md#zero-based-recoding-of-zones) section for details. + alternatives. With stable alternative alignment it is much less noisy than Monte Carlo. +- `eet` is the slowest sampling method. It draws one EV1 error term per chooser, alternative, and + repeated sample draw. In return, it produces the most stable sampled sets across scenarios + because unchanged alternatives keep the same unobserved error draws and only observed utility + changes can change the sampled set. + +Note that `eet` does not remove the dependence on the approximate sampling utility itself: if that +utility changes, the sampled set can still change. What it removes is the extra Monte Carlo noise +from the sampling draw. `poisson` also benefits from stable alignment per alternative, but unlike +`eet` it still depends on probability-based inclusion tests. The practical effect on scenario +comparisons is ultimately empirical. + ## References diff --git a/docs/users-guide/sampling-methods.rst b/docs/users-guide/sampling-methods.rst index 172924d221..c3fbc0e674 100644 --- a/docs/users-guide/sampling-methods.rst +++ b/docs/users-guide/sampling-methods.rst @@ -11,13 +11,20 @@ Available methods are: * ``monte_carlo``: importance sampling with replacement using probabilities and uniform draws * ``eet``: importance sampling with replacement using explicit error-term draws -* ``poisson``: independent Poisson inclusion sampling +* ``poisson``: independent Poisson inclusion sampling using probabilities -Default behavior depends on the global EET setting: +Default behavior depends on the global simulation method setting: * if ``use_explicit_error_terms: False``, the default sampling method is ``monte_carlo`` * if ``use_explicit_error_terms: True``, the default sampling method is ``poisson`` +However, any method can be used with either simulation method and can be set +globally in the settings: + +.. code-block:: yaml + + sample_method: "poisson" + To override the default for a particular model, set the component's compute settings: .. code-block:: yaml @@ -33,9 +40,13 @@ Practical differences: * ``monte_carlo`` and ``eet`` both sample with replacement, so duplicated sampled alternatives are possible and their aggregate sampled shares track repeated-draw MNL behavior more closely. * ``poisson`` samples alternatives by inclusion probability, so each sampled alternative appears - at most once per chooser. This can materially change raw sampled shares in highly peaked cases, - even though the downstream sampling correction remains well defined. -* ``poisson`` is the current default when global EET is enabled because it avoids repeated - chooser-by-alternative explicit-error draws during sampling. + at most once per chooser. This can change raw sampled shares in highly peaked cases, even though + the downstream sampling correction remains well defined. +* ``monte-carlo`` is the fastest method, followed by ``poisson``, with ``eet`` being the slowest. + However, for models like location choice, most runtime comes from logsum calculations and the + total difference between ``monte-carlo`` and ``poisson`` sampling is usually very small. +* ``poisson`` is the current default when running with simulation method explicit error terms + because it avoids repeated chooser-by-alternative explicit-error draws during sampling while + still providing improved noise reduction compared to Monte Carlo sampling. For implementation details and runtime considerations, see :doc:`/dev-guide/sampling-methods`. diff --git a/docs/users-guide/ways_to_run.rst b/docs/users-guide/ways_to_run.rst index 4c5eb5085d..f39f9ac2f6 100644 --- a/docs/users-guide/ways_to_run.rst +++ b/docs/users-guide/ways_to_run.rst @@ -297,7 +297,7 @@ cumulative distribution for each chooser. Explicit Error Terms (EET) replaces th random-utility simulation by drawing the unobserved portion of utility (error term) for each chooser-alternative pair, adding it to the systematic utility, and selecting the alternative with the highest total utility. Both methods simulate the same underlying model, but EET can be less affected by Monte Carlo -noise when comparing scenarios and can make some comparisons easier to interpret. This is because the +noise when comparing scenarios and can make some comparisons easier to interpret. This is because the selected alternative is the one with the highest total utility after adding the explicit error term, and if the explicit error term is consistent between a base and scenario run then only (relative) increases in the observed utility can lead to previously un-chosen alternatives @@ -310,4 +310,4 @@ To enable EET for a model run, set the global switch in ``settings.yaml``: use_explicit_error_terms: True Enable or disable this setting consistently across all runs being compared. For more details, including -scenario comparison considerations and implementation notes, see :doc:`/dev-guide/explicit-error-terms`. +scenario comparison considerations, see :doc:`/dev-guide/explicit-error-terms`. From d11f2be7e997152bf305148523db6d49d6921e94 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 20 May 2026 21:17:06 +1000 Subject: [PATCH 272/298] info, nto warning --- activitysim/abm/models/disaggregate_accessibility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 733c6419d4..03506812fe 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -762,7 +762,7 @@ def get_disaggregate_logsums( ) if _resolve_sample_method(state, disagg_model_settings) == "poisson": - logger.warning( + logger.info( "Using Poisson sampling method for disaggregate accessibility calculations. Currently the results will" + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" + f" log({disagg_model_settings.DESTINATION_SAMPLE_SIZE}) if you are using the common correction factor" From 7b7170bd0f0168e097accf7ccc95144e41b0edbd Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 20 May 2026 22:45:52 +1000 Subject: [PATCH 273/298] compute settings, not model settings --- activitysim/abm/models/disaggregate_accessibility.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 03506812fe..be9088dd33 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -761,7 +761,10 @@ def get_disaggregate_logsums( state, "disaggregate_accessibility.yaml" ) - if _resolve_sample_method(state, disagg_model_settings) == "poisson": + if ( + _resolve_sample_method(state, getattr(model_settings, "compute_settings", None)) + == "poisson" + ): logger.info( "Using Poisson sampling method for disaggregate accessibility calculations. Currently the results will" + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" From b844372c1bdf3e31220b2063523b4e02b8f4e416 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 20 May 2026 22:46:20 +1000 Subject: [PATCH 274/298] compute settings, not model settings --- activitysim/abm/models/disaggregate_accessibility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index be9088dd33..2a4700fc48 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -765,7 +765,7 @@ def get_disaggregate_logsums( _resolve_sample_method(state, getattr(model_settings, "compute_settings", None)) == "poisson" ): - logger.info( + logger.warning( "Using Poisson sampling method for disaggregate accessibility calculations. Currently the results will" + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" + f" log({disagg_model_settings.DESTINATION_SAMPLE_SIZE}) if you are using the common correction factor" From 196704aa6968374472123146a38d6a0883df17b4 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Wed, 20 May 2026 23:15:57 +1000 Subject: [PATCH 275/298] variable name --- activitysim/abm/models/disaggregate_accessibility.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 2a4700fc48..89c41521a3 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -762,7 +762,9 @@ def get_disaggregate_logsums( ) if ( - _resolve_sample_method(state, getattr(model_settings, "compute_settings", None)) + _resolve_sample_method( + state, getattr(disagg_model_settings, "compute_settings", None) + ) == "poisson" ): logger.warning( From 99239c2fb6fc56ed6418f128b29645d286fdcf3a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 29 May 2026 12:13:38 +1000 Subject: [PATCH 276/298] log log on subset of rands only --- activitysim/core/random.py | 100 +++++++++---------------------------- 1 file changed, 24 insertions(+), 76 deletions(-) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 45cd4dcfb2..883227f208 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -366,7 +366,7 @@ def gumbel_max_positions_for_df( Returns ------- - positions : 2-D ndarray of int32 + positions : 2-D ndarray of int64 Array with shape (len(utilities), sample_size) containing the column position of the winning alternative for each chooser/sample pair. """ @@ -376,7 +376,7 @@ def gumbel_max_positions_for_df( utility_values = utilities.to_numpy() n_rows, n_alts = utility_values.shape - positions = np.empty((n_rows, sample_size), dtype=np.int32) + positions = np.empty((n_rows, sample_size), dtype=np.int64) if stable_alt_positions is not None or n_total_alts is not None: if stable_alt_positions is None or n_total_alts is None: @@ -406,11 +406,12 @@ def gumbel_max_positions_for_df( # first sample, the next n_alts values are the gumbels for the second sample, etc. for row_num, prng in enumerate(generators): utility_row = utility_values[row_num] - row_gumbels = -np.log(-np.log(prng.rand(n_gumbels * sample_size))).reshape( + row_uniforms = prng.rand(n_gumbels * sample_size).reshape( (sample_size, n_gumbels) ) if stable_alt_positions is not None: - row_gumbels = row_gumbels[:, stable_alt_positions] + row_uniforms = row_uniforms[:, stable_alt_positions] + row_gumbels = -np.log(-np.log(row_uniforms)) positions[row_num, :] = np.argmax( row_gumbels + utility_row[np.newaxis, :], axis=1, @@ -443,7 +444,7 @@ def gumbel_choice_positions_for_df( Returns ------- - positions : 1-D ndarray of int32 + positions : 1-D ndarray of int64 Array with shape (len(utilities),) containing the winning column position for each chooser row. """ @@ -453,7 +454,7 @@ def gumbel_choice_positions_for_df( utility_values = utilities.to_numpy() n_rows, n_alts = utility_values.shape - positions = np.empty(n_rows, dtype=np.int32) + positions = np.empty(n_rows, dtype=np.int64) if alt_nrs_df is not None: assert alt_nrs_df.shape == utilities.shape @@ -475,13 +476,24 @@ def gumbel_choice_positions_for_df( for row_num, prng in enumerate(generators): utility_row = utility_values[row_num] - row_gumbels = -np.log(-np.log(prng.rand(n_rands))) + row_randoms = prng.rand(n_rands) if alt_nrs_df is None: - positions[row_num] = np.argmax(row_gumbels + utility_row) + positions[row_num] = np.argmax( + utility_row - np.log(-np.log(row_randoms)) + ) else: - candidate_values = utility_row + row_gumbels[safe_alt_nrs[row_num]] - candidate_values[masked[row_num]] = utility_row[masked[row_num]] + # candidate_values = utility_row - np.log(-np.log(row_randoms[safe_alt_nrs[row_num]])) + # candidate_values[masked[row_num]] = utility_row[masked[row_num]] + row_mask = masked[row_num] + candidate_values = utility_row.copy() + if not row_mask.all(): + active = ~row_mask + row_alt_nrs = safe_alt_nrs[row_num, active] + candidate_values[active] += -np.log( + -np.log(row_randoms[row_alt_nrs]) + ) + positions[row_num] = np.argmax(candidate_values) self.row_states.loc[utilities.index, "offset"] += n_rands @@ -1003,49 +1015,10 @@ def gumbel_max_positions_for_df( Returns ------- - positions : 2-D ndarray of int32 + positions : 2-D ndarray of int64 Array with shape (len(utilities), sample_size) containing the column position of the winning alternative for each chooser/sample pair. """ - if not self.channels: - utility_values = utilities.to_numpy() - n_rows, n_alts = utility_values.shape - positions = np.empty((n_rows, sample_size), dtype=np.int32) - rng = np.random.RandomState(0) - - if stable_alt_positions is not None or n_total_alts is not None: - if stable_alt_positions is None or n_total_alts is None: - raise ValueError( - "stable_alt_positions and n_total_alts must both be provided or omitted together" - ) - stable_alt_positions = np.asarray(stable_alt_positions) - if stable_alt_positions.shape != (n_alts,): - raise ValueError( - "stable_alt_positions must be a 1-D array aligned to utilities columns" - ) - if ( - stable_alt_positions.min() < 0 - or stable_alt_positions.max() >= n_total_alts - ): - raise ValueError( - "stable_alt_positions values must be within [0, n_total_alts)" - ) - n_gumbels = n_total_alts - else: - n_gumbels = n_alts - - for row_num, utility_row in enumerate(utility_values): - row_gumbels = -np.log( - -np.log(rng.rand(n_gumbels * sample_size)) - ).reshape((sample_size, n_gumbels)) - if stable_alt_positions is not None: - row_gumbels = row_gumbels[:, stable_alt_positions] - positions[row_num, :] = np.argmax( - row_gumbels + utility_row[np.newaxis, :], - axis=1, - ) - - return positions channel = self.get_channel_for_df(utilities) return channel.gumbel_max_positions_for_df( @@ -1071,33 +1044,8 @@ def gumbel_choice_positions_for_df(self, utilities, alt_nrs_df=None, n_rands=Non Returns ------- - positions : 1-D ndarray of int32 + positions : 1-D ndarray of int64 """ - if not self.channels: - rng = np.random.RandomState(0) - utility_values = utilities.to_numpy() - positions = np.empty(len(utilities), dtype=np.int32) - - if alt_nrs_df is not None: - if n_rands is None: - raise ValueError("n_rands is required when alt_nrs_df is provided") - alt_nr_values = alt_nrs_df.to_numpy() - masked = alt_nr_values == -999 - safe_alt_nrs = np.where(masked, 0, alt_nr_values) - for row_num, utility_row in enumerate(utility_values): - row_gumbels = -np.log(-np.log(rng.rand(n_rands))) - candidate_values = utility_row + row_gumbels[safe_alt_nrs[row_num]] - candidate_values[masked[row_num]] = utility_row[masked[row_num]] - positions[row_num] = np.argmax(candidate_values) - else: - if n_rands is None: - n_rands = utility_values.shape[1] - for row_num, utility_row in enumerate(utility_values): - positions[row_num] = np.argmax( - -np.log(-np.log(rng.rand(n_rands))) + utility_row - ) - - return positions channel = self.get_channel_for_df(utilities) return channel.gumbel_choice_positions_for_df( From 7768547db66e21909135425c5b87a1bfba1de88e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 29 May 2026 12:39:32 +1000 Subject: [PATCH 277/298] optionally bias poisson disagg acc --- .../abm/models/disaggregate_accessibility.py | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 89c41521a3..302f6d7750 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -191,6 +191,14 @@ class DisaggregateAccessibilitySettings(PydanticReadable, extra="forbid"): compute_settings: ComputeSettings | None = None + bias_poisson_sampling_results: bool = False + """ + Whether to apply a bias of `log(sample_size)` to the Poisson sampling results. This is a temporary + workaround to align Poisson sampling results with the biased results of the monte-carlo and eet + sampling methods, such that models that were estimated with historical biased sampling results + can be run with Poisson sampling without needing to re-estimate the model. + """ + def read_disaggregate_accessibility_yaml( state: workflow.State, file_name @@ -761,20 +769,30 @@ def get_disaggregate_logsums( state, "disaggregate_accessibility.yaml" ) + bias_poisson_sampling_results = False if ( _resolve_sample_method( state, getattr(disagg_model_settings, "compute_settings", None) ) == "poisson" - ): - logger.warning( - "Using Poisson sampling method for disaggregate accessibility calculations. Currently the results will" - + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" - + f" log({disagg_model_settings.DESTINATION_SAMPLE_SIZE}) if you are using the common correction factor" - + " `log(pick_count / prob)` in location choice specs. The results of the Poisson method are unbiased," - + " i.e., they agree with the results obtained with a full destination sample, unlike those for" - + " monte-carlo or eet sampling." - ) + ) and (disagg_model_settings.DESTINATION_SAMPLE_SIZE > 0): + # Check for temporary fix to bias Poisson sampling results to align with MC/eet sampling. However, only + # apply this for final sample size, for unsampled disagg acc the MC/eet results are unbiased and we + # want to stay consistent. + if disagg_model_settings.bias_poisson_sampling_results: + logger.warning( + "Applying temporary bias correction to Poisson sampling results to align with MC/eet sampling." + ) + bias_poisson_sampling_results = True + else: + logger.warning( + "Using Poisson sampling method for disaggregate accessibility calculations. Currently the results will" + + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" + + f" log({disagg_model_settings.DESTINATION_SAMPLE_SIZE}) if you are using the common correction factor" + + " `log(pick_count / prob)` in location choice specs. The results of the Poisson method are unbiased," + + " i.e., they agree with the results obtained with a full destination sample, unlike those for" + + " monte-carlo or eet sampling." + ) for model_name in [ "workplace_location", @@ -843,6 +861,12 @@ def get_disaggregate_logsums( # Merge onto persons if _logsums is not None and len(_logsums.index) > 0: keep_cols = list(set(_logsums.columns).difference(choosers.columns)) + + if bias_poisson_sampling_results: + _logsums[keep_cols] += np.log( + disagg_model_settings.DESTINATION_SAMPLE_SIZE + ) + logsums[model_name] = persons_merged.merge( _logsums[keep_cols], on="proto_person_id" ) From aa13d2dfb6b053482a79e6fd68c1a4d0b3dad4e8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 29 May 2026 17:24:09 +1000 Subject: [PATCH 278/298] bias also for non-mand --- activitysim/abm/models/disaggregate_accessibility.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index 302f6d7750..f44a74165f 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -890,6 +890,10 @@ def get_disaggregate_logsums( # Merge onto persons & tours if _logsums is not None and len(_logsums.index) > 0: + if bias_poisson_sampling_results: + _logsums["logsums"] += np.log( + disagg_model_settings.DESTINATION_SAMPLE_SIZE + ) tour_logsums = tours.merge( _logsums["logsums"].to_frame(), left_index=True, right_index=True ) From 95c1058a8fe9a0a2406ed4fa14e7d57cb3e12101 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 31 May 2026 12:48:28 +1000 Subject: [PATCH 279/298] avoid copy in favour of log log for gumbel choice --- activitysim/core/logit.py | 2 +- activitysim/core/random.py | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index f8fb02707f..e76422f4d9 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -566,7 +566,7 @@ def make_choices_utility_based( trace_label: str = None, trace_choosers=None, allow_bad_utils=False, - nest_spec=None, # Make consistent with make_choices for generalizability of custom chooser. + nest_spec=None, alts_context: AltsContext | None = None, alt_nrs_df: pd.DataFrame | None = None, ) -> tuple[pd.Series, pd.Series]: diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 883227f208..d25b347d40 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -483,17 +483,16 @@ def gumbel_choice_positions_for_df( utility_row - np.log(-np.log(row_randoms)) ) else: - # candidate_values = utility_row - np.log(-np.log(row_randoms[safe_alt_nrs[row_num]])) - # candidate_values[masked[row_num]] = utility_row[masked[row_num]] - row_mask = masked[row_num] - candidate_values = utility_row.copy() - if not row_mask.all(): - active = ~row_mask - row_alt_nrs = safe_alt_nrs[row_num, active] - candidate_values[active] += -np.log( - -np.log(row_randoms[row_alt_nrs]) - ) - + candidate_values = utility_row - np.log(-np.log(row_randoms[safe_alt_nrs[row_num]])) + candidate_values[masked[row_num]] = utility_row[masked[row_num]] + # row_mask = masked[row_num] + # candidate_values = utility_row.copy() + # if not row_mask.all(): + # active = ~row_mask + # row_alt_nrs = safe_alt_nrs[row_num, active] + # candidate_values[active] += -np.log( + # -np.log(row_randoms[row_alt_nrs]) + # ) positions[row_num] = np.argmax(candidate_values) self.row_states.loc[utilities.index, "offset"] += n_rands From 82cd511fc1ce4542dd18f5c79ef40464b7c1612e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 31 May 2026 14:13:06 +1000 Subject: [PATCH 280/298] switch to bias all location choice logsums for poisson sampling --- .../abm/models/disaggregate_accessibility.py | 43 ---------------- activitysim/abm/models/location_choice.py | 5 ++ activitysim/abm/models/trip_destination.py | 4 ++ activitysim/abm/models/util/bias_logsums.py | 50 +++++++++++++++++++ .../abm/models/util/tour_destination.py | 5 ++ activitysim/abm/models/util/tour_od.py | 5 ++ activitysim/core/interaction_sample.py | 4 +- 7 files changed, 72 insertions(+), 44 deletions(-) create mode 100644 activitysim/abm/models/util/bias_logsums.py diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index f44a74165f..f1104e68f8 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -191,14 +191,6 @@ class DisaggregateAccessibilitySettings(PydanticReadable, extra="forbid"): compute_settings: ComputeSettings | None = None - bias_poisson_sampling_results: bool = False - """ - Whether to apply a bias of `log(sample_size)` to the Poisson sampling results. This is a temporary - workaround to align Poisson sampling results with the biased results of the monte-carlo and eet - sampling methods, such that models that were estimated with historical biased sampling results - can be run with Poisson sampling without needing to re-estimate the model. - """ - def read_disaggregate_accessibility_yaml( state: workflow.State, file_name @@ -769,31 +761,6 @@ def get_disaggregate_logsums( state, "disaggregate_accessibility.yaml" ) - bias_poisson_sampling_results = False - if ( - _resolve_sample_method( - state, getattr(disagg_model_settings, "compute_settings", None) - ) - == "poisson" - ) and (disagg_model_settings.DESTINATION_SAMPLE_SIZE > 0): - # Check for temporary fix to bias Poisson sampling results to align with MC/eet sampling. However, only - # apply this for final sample size, for unsampled disagg acc the MC/eet results are unbiased and we - # want to stay consistent. - if disagg_model_settings.bias_poisson_sampling_results: - logger.warning( - "Applying temporary bias correction to Poisson sampling results to align with MC/eet sampling." - ) - bias_poisson_sampling_results = True - else: - logger.warning( - "Using Poisson sampling method for disaggregate accessibility calculations. Currently the results will" - + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" - + f" log({disagg_model_settings.DESTINATION_SAMPLE_SIZE}) if you are using the common correction factor" - + " `log(pick_count / prob)` in location choice specs. The results of the Poisson method are unbiased," - + " i.e., they agree with the results obtained with a full destination sample, unlike those for" - + " monte-carlo or eet sampling." - ) - for model_name in [ "workplace_location", "school_location", @@ -861,12 +828,6 @@ def get_disaggregate_logsums( # Merge onto persons if _logsums is not None and len(_logsums.index) > 0: keep_cols = list(set(_logsums.columns).difference(choosers.columns)) - - if bias_poisson_sampling_results: - _logsums[keep_cols] += np.log( - disagg_model_settings.DESTINATION_SAMPLE_SIZE - ) - logsums[model_name] = persons_merged.merge( _logsums[keep_cols], on="proto_person_id" ) @@ -890,10 +851,6 @@ def get_disaggregate_logsums( # Merge onto persons & tours if _logsums is not None and len(_logsums.index) > 0: - if bias_poisson_sampling_results: - _logsums["logsums"] += np.log( - disagg_model_settings.DESTINATION_SAMPLE_SIZE - ) tour_logsums = tours.merge( _logsums["logsums"].to_frame(), left_index=True, right_index=True ) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 46e1448540..0de5ef8b29 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -9,6 +9,7 @@ from activitysim.abm.models.util import logsums as logsum from activitysim.abm.models.util import tour_destination +from activitysim.abm.models.util.bias_logsums import maybe_bias_logsums from activitysim.abm.tables import shadow_pricing from activitysim.core import estimation, expressions, los, simulate, tracing, workflow from activitysim.core.configuration.logit import ( @@ -920,6 +921,10 @@ def run_location_choice( alts_context=alts_context, ) + # Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling. + if want_logsums: + choices_df = maybe_bias_logsums(state, choices_df, model_settings) + if estimator: if state.settings.trace_hh_id: estimation_trace_label = tracing.extend_trace_label( diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 08f7f760aa..31677f68d1 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -1205,6 +1205,10 @@ def choose_trip_destination( alts_context=alts_context, ) + # Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling. + if want_logsums: + destinations = maybe_bias_logsums(state, destinations, model_settings) + dropped_trips = ~trips.index.isin(destinations.index) if dropped_trips.any(): logger.warning( diff --git a/activitysim/abm/models/util/bias_logsums.py b/activitysim/abm/models/util/bias_logsums.py new file mode 100644 index 0000000000..d594b06704 --- /dev/null +++ b/activitysim/abm/models/util/bias_logsums.py @@ -0,0 +1,50 @@ +# ActivitySim +# See full license in LICENSE.txt. +from __future__ import annotations + +import logging + +import numpy as np +import pandas as pd + +from activitysim.core import workflow +from activitysim.core.interaction_sample import _resolve_sample_method + +logger = logging.getLogger(__name__) + + +def maybe_bias_logsums(state: workflow.State, choices_df: pd.DataFrame, model_settings): + # Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling. + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + sample_method = _resolve_sample_method(state, sample_compute_settings) + if ( + (sample_method == "poisson") + and (model_settings.SAMPLE_SIZE > 0) + and not state.settings.disable_destination_sampling + ): + # Only apply for sample size > 0, for unsampled disagg acc the MC/eet results are unbiased and we + # want to stay consistent. + if state.settings.bias_location_choice_logsums_for_poisson_sampling: + logger.warning( + "Applying bias correction to location logsums with Poisson sampling to align with MC/eet sampling." + ) + # it looks like the logsum column can be named either "logsum" or "logsums", depending on if choices get skipped. + if "logsum" in choices_df.columns: + choices_df["logsum"] += np.log(model_settings.SAMPLE_SIZE) + if "logsums" in choices_df.columns: + choices_df["logsums"] += np.log(model_settings.SAMPLE_SIZE) + else: + logger.warning( + "Using Poisson sampling method for location choice logsum calculations. Currently the logsums results will" + + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" + + f" log({model_settings.SAMPLE_SIZE}) if using the common correction factor" + + " `log(pick_count / prob)` in location choice specs. The results of the Poisson method are unbiased," + + " i.e., they agree with the results obtained with a full destination sample, unlike those for" + + " monte-carlo or eet sampling." + ) + + return choices_df diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index d60fca05b4..29d2a5a215 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -8,6 +8,7 @@ import pandas as pd from activitysim.abm.models.util import logsums as logsum +from activitysim.abm.models.util.bias_logsums import maybe_bias_logsums from activitysim.abm.tables.size_terms import tour_destination_size_terms from activitysim.core import ( config, @@ -1102,6 +1103,10 @@ def run_tour_destination( skip_choice=skip_choice, ) + # Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling. + if want_logsums: + choices = maybe_bias_logsums(state, choices, model_settings) + choices_list.append(choices) if want_sample_table: diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 0e490207d7..60f952c183 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -10,6 +10,7 @@ from activitysim.abm.models.util import logsums as logsum from activitysim.abm.models.util import trip +from activitysim.abm.models.util.bias_logsums import maybe_bias_logsums from activitysim.abm.models.util.tour_destination import SizeTermCalculator from activitysim.core import ( config, @@ -1244,6 +1245,10 @@ def run_tour_od( ), ) + # Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling. + if want_logsums: + choices = maybe_bias_logsums(state, choices, model_settings) + choices_list.append(choices) if estimator: assert estimator.want_unsampled_alternatives diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index b438745763..e174c2c6b0 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -44,7 +44,9 @@ def _resolve_sample_method( if sampling_method is None: sampling_method = state.settings.sample_method if sampling_method is None: - return "poisson" if state.settings.use_explicit_error_terms else "monte_carlo" + sampling_method = ( + "poisson" if state.settings.use_explicit_error_terms else "monte_carlo" + ) if sampling_method not in typing.get_args(InteractionSampleMethod): raise ValueError( f"Unsupported sample_method {sampling_method!r}; expected one of {typing.get_args(InteractionSampleMethod)}" From 5bfd4b3128fedeaed4d46ffc00ec1840f30e2a6f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 31 May 2026 14:17:53 +1000 Subject: [PATCH 281/298] add setting --- activitysim/core/configuration/top.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/activitysim/core/configuration/top.py b/activitysim/core/configuration/top.py index cf5bfb297b..88ff28fdd4 100644 --- a/activitysim/core/configuration/top.py +++ b/activitysim/core/configuration/top.py @@ -793,7 +793,21 @@ def _check_store_skims_in_shm(self): """ Sampling method to use in `activitysim.core.interaction_sample`. - When unset, `monte_carlo` is used when `use_explicit_error_terms` is false and `poisson` is used when it is true. + When unset, `monte_carlo` is used when `use_explicit_error_terms` is false and + `poisson` is used when it is true. + + .. versionadded:: 1.6 + """ + + bias_location_choice_logsums_for_poisson_sampling: bool = False + """ + Whether to apply a bias of `log(sample_size)` to the Poisson sampling results. + This is a temporary workaround to align Poisson sampling results with the biased + results of the monte-carlo and eet sampling methods, such that models that were + estimated with historical biased sampling results can be run with Poisson sampling + without needing to re-estimate the model. + + .. versionadded:: 1.6 """ check_model_settings: bool = True From 7424aeca9b581f4659d64dc767848a2eb0b65dba Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 31 May 2026 16:34:17 +1000 Subject: [PATCH 282/298] adds missing import in trip dest --- activitysim/abm/models/trip_destination.py | 1 + 1 file changed, 1 insertion(+) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 31677f68d1..d43d9fc9a2 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -18,6 +18,7 @@ flag_failed_trip_leg_mates, ) from activitysim.abm.tables.size_terms import tour_destination_size_terms +from activitysim.activitysim.abm.models.util.bias_logsums import maybe_bias_logsums from activitysim.core import ( chunk, config, From 0f2ddbd99f62f4f35c9425ef0d23fa3570e2d88a Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 31 May 2026 16:34:41 +1000 Subject: [PATCH 283/298] adds missing import in trip dest --- activitysim/abm/models/trip_destination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index d43d9fc9a2..83a449b211 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -18,7 +18,7 @@ flag_failed_trip_leg_mates, ) from activitysim.abm.tables.size_terms import tour_destination_size_terms -from activitysim.activitysim.abm.models.util.bias_logsums import maybe_bias_logsums +from activitysim.abm.models.util.bias_logsums import maybe_bias_logsums from activitysim.core import ( chunk, config, From 3d6f1dbba8728d1856b1b6e9a1c45f5b3bc124f6 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sun, 31 May 2026 22:15:11 +1000 Subject: [PATCH 284/298] ling --- activitysim/core/random.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index d25b347d40..5a9a20c313 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -483,7 +483,9 @@ def gumbel_choice_positions_for_df( utility_row - np.log(-np.log(row_randoms)) ) else: - candidate_values = utility_row - np.log(-np.log(row_randoms[safe_alt_nrs[row_num]])) + candidate_values = utility_row - np.log( + -np.log(row_randoms[safe_alt_nrs[row_num]]) + ) candidate_values[masked[row_num]] = utility_row[masked[row_num]] # row_mask = masked[row_num] # candidate_values = utility_row.copy() From 979337b07833f6e339fb23eb44d5b6148147739f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Mon, 8 Jun 2026 23:50:57 -0500 Subject: [PATCH 285/298] addresses claude code review --- .../abm/models/disaggregate_accessibility.py | 1 - .../abm/models/joint_tour_participation.py | 4 +- activitysim/abm/models/location_choice.py | 175 +++++++++--------- activitysim/abm/models/trip_destination.py | 53 ++---- activitysim/abm/models/util/bias_logsums.py | 24 ++- activitysim/abm/models/util/maz_sampling.py | 75 ++++++++ .../abm/models/util/tour_destination.py | 52 ++---- activitysim/abm/models/util/tour_od.py | 52 ++---- activitysim/core/interaction_sample.py | 57 +++++- .../core/interaction_sample_simulate.py | 38 +++- activitysim/core/logit.py | 75 ++++---- activitysim/core/random.py | 50 +++-- activitysim/core/simulate.py | 47 +++-- .../test/test_interaction_sample_simulate.py | 1 - activitysim/core/test/test_logit.py | 90 ++++++++- 15 files changed, 513 insertions(+), 281 deletions(-) create mode 100644 activitysim/abm/models/util/maz_sampling.py diff --git a/activitysim/abm/models/disaggregate_accessibility.py b/activitysim/abm/models/disaggregate_accessibility.py index f1104e68f8..e5a66916bd 100644 --- a/activitysim/abm/models/disaggregate_accessibility.py +++ b/activitysim/abm/models/disaggregate_accessibility.py @@ -22,7 +22,6 @@ ) from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.expressions import assign_columns -from activitysim.core.interaction_sample import _resolve_sample_method logger = logging.getLogger(__name__) diff --git a/activitysim/abm/models/joint_tour_participation.py b/activitysim/abm/models/joint_tour_participation.py index b77fe71cfb..9b6a2a3d99 100644 --- a/activitysim/abm/models/joint_tour_participation.py +++ b/activitysim/abm/models/joint_tour_participation.py @@ -19,7 +19,7 @@ workflow, ) from activitysim.core.configuration.base import ComputeSettings, PreprocessorSettings -from activitysim.core.configuration.logit import LogitComponentSettings +from activitysim.core.configuration.logit import LogitComponentSettings, LogitNestSpec from activitysim.core.exceptions import InvalidTravelError from activitysim.core.util import assign_in_place, reindex @@ -131,7 +131,7 @@ def participants_chooser( choosers: pd.DataFrame, spec: pd.DataFrame, trace_label: str, - nest_spec: Optional[dict, LogitNestSpec] = None, + nest_spec: dict | LogitNestSpec | None = None, ) -> tuple[pd.Series, pd.Series]: """ custom alternative to logit.make_choices for simulate.simple_simulate diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index 0de5ef8b29..e2d94e073c 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -18,7 +18,7 @@ ) from activitysim.core.exceptions import DuplicateWorkflowTableError from activitysim.core.interaction_sample import ( - _resolve_sample_method, + resolve_sample_method, interaction_sample, ) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate @@ -421,12 +421,7 @@ def location_presample( # chooser's sample and would all share one uniform, forcing every duplicate to pick the same MAZ. An EET-stable # MAZ-for-TAZ would need a (TAZ, occurrence-rank)-keyed draw and many more random numbers per chooser; that's # too expensive with the current RNG, revisit if a counter-based RNG is adapted. - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" - ) - taz_sample_method = _resolve_sample_method(state, sample_compute_settings) + taz_sample_method = resolve_sample_method(state, model_settings) use_stable_taz_index = taz_sample_method == "poisson" else: full_taz_index = None @@ -1100,96 +1095,98 @@ def iterate_location_choice( choices_df ) = None # initialize to None, will be populated in first iteration - for iteration in range(1, max_iterations + 1): - persons_merged_df_ = persons_merged_df.copy() - - if spc.use_shadow_pricing and iteration > 1: - spc.update_shadow_prices(state) - - if spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation": - # filter from the sampled persons - persons_merged_df_ = persons_merged_df_[ - persons_merged_df_.index.isin(spc.sampled_persons.index) - ] - persons_merged_df_ = persons_merged_df_.sort_index() - - # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is - # used on each iteration for the persons being re-simulated, so sampling and final choice draws are - # reproducible across shadow-pricing iterations. - # Scoped to the persons channel for these specific rows via reset_offsets_for_df so the dedicated - # shadow_pricing_persons channel (registered under EET) keeps its offset across iterations and advances - # naturally on each iteration's update_shadow_prices call. - if state.settings.use_explicit_error_terms and iteration > 1: - logger.debug( - f"{trace_label} resetting random number generator offsets for iteration {iteration}" + try: + for iteration in range(1, max_iterations + 1): + persons_merged_df_ = persons_merged_df.copy() + + if spc.use_shadow_pricing and iteration > 1: + spc.update_shadow_prices(state) + + if spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation": + # filter from the sampled persons + persons_merged_df_ = persons_merged_df_[ + persons_merged_df_.index.isin(spc.sampled_persons.index) + ] + persons_merged_df_ = persons_merged_df_.sort_index() + + # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is + # used on each iteration for the persons being re-simulated, so sampling and final choice draws are + # reproducible across shadow-pricing iterations. + # Scoped to the persons channel for these specific rows via reset_offsets_for_df so the dedicated + # shadow_pricing_persons channel (registered under EET) keeps its offset across iterations and advances + # naturally on each iteration's update_shadow_prices call. + if state.settings.use_explicit_error_terms and iteration > 1: + logger.debug( + f"{trace_label} resetting random number generator offsets for iteration {iteration}" + ) + state.get_rn_generator().reset_offsets_for_df(persons_merged_df_) + + choices_df_, save_sample_df = run_location_choice( + state, + persons_merged_df_, + network_los, + shadow_price_calculator=spc, + want_logsums=want_logsums, + want_sample_table=want_sample_table, + estimator=estimator, + model_settings=model_settings, + chunk_size=chunk_size, + chunk_tag=chunk_tag, + trace_label=tracing.extend_trace_label(trace_label, "i%s" % iteration), ) - state.get_rn_generator().reset_offsets_for_df(persons_merged_df_) - choices_df_, save_sample_df = run_location_choice( - state, - persons_merged_df_, - network_los, - shadow_price_calculator=spc, - want_logsums=want_logsums, - want_sample_table=want_sample_table, - estimator=estimator, - model_settings=model_settings, - chunk_size=chunk_size, - chunk_tag=chunk_tag, - trace_label=tracing.extend_trace_label(trace_label, "i%s" % iteration), - ) + # choices_df is a pandas DataFrame with columns "choice" and (optionally) "logsum" + if choices_df_ is None: + break + + if spc.use_shadow_pricing: + # handle simulation method + if ( + spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation" + and iteration > 1 + ): + # if a process ends up with no sampled workers in it, hence an empty choice_df_, then choice_df wil be what it was previously + if len(choices_df_) != 0: + choices_df = pd.concat([choices_df, choices_df_], axis=0) + choices_df_index = choices_df_.index.name + choices_df = choices_df.reset_index() + # update choices of workers/students + choices_df = choices_df.drop_duplicates( + subset=[choices_df_index], keep="last" + ) + choices_df = choices_df.set_index(choices_df_index) + choices_df = choices_df.sort_index() + else: + choices_df = choices_df_.copy() - # choices_df is a pandas DataFrame with columns "choice" and (optionally) "logsum" - if choices_df_ is None: - break - - if spc.use_shadow_pricing: - # handle simulation method - if ( - spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation" - and iteration > 1 - ): - # if a process ends up with no sampled workers in it, hence an empty choice_df_, then choice_df wil be what it was previously - if len(choices_df_) != 0: - choices_df = pd.concat([choices_df, choices_df_], axis=0) - choices_df_index = choices_df_.index.name - choices_df = choices_df.reset_index() - # update choices of workers/students - choices_df = choices_df.drop_duplicates( - subset=[choices_df_index], keep="last" - ) - choices_df = choices_df.set_index(choices_df_index) - choices_df = choices_df.sort_index() else: - choices_df = choices_df_.copy() + choices_df = choices_df_ - else: - choices_df = choices_df_ - - spc.set_choices( - choices=choices_df["choice"], - segment_ids=persons_merged_df[chooser_segment_column].reindex( - choices_df.index - ), - ) + spc.set_choices( + choices=choices_df["choice"], + segment_ids=persons_merged_df[chooser_segment_column].reindex( + choices_df.index + ), + ) - if locutor: - spc.write_trace_files(state, iteration) + if locutor: + spc.write_trace_files(state, iteration) - if spc.use_shadow_pricing and spc.check_fit(state, iteration): - logging.info( - "%s converged after iteration %s" - % ( - trace_label, - iteration, + if spc.use_shadow_pricing and spc.check_fit(state, iteration): + logging.info( + "%s converged after iteration %s" + % ( + trace_label, + iteration, + ) ) - ) - break - - # Drop the dedicated shadow_pricing RNG channel (registered lazily under EET by spc.update_shadow_prices) so it - # doesn't survive into the next location_choice model (e.g., school after work) — both models share the same - # channel name and would otherwise collide on the no-overlap assert in SimpleChannel.extend_domain. No-op for MC. - spc.cleanup_rng_channel(state) + break + finally: + # Drop the dedicated shadow_pricing RNG channel (registered lazily under EET by spc.update_shadow_prices) so it + # doesn't survive into the next location_choice model (e.g., school after work) — both models share the same + # channel name and would otherwise collide on the no-overlap assert in SimpleChannel.extend_domain. No-op for MC. + # In try/finally so an exception escaping run_location_choice still drops the channel. + spc.cleanup_rng_channel(state) # - shadow price table if locutor: diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 83a449b211..39649f3ee2 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -19,6 +19,7 @@ ) from activitysim.abm.tables.size_terms import tour_destination_size_terms from activitysim.abm.models.util.bias_logsums import maybe_bias_logsums +from activitysim.abm.models.util.maz_sampling import draw_maz_rands from activitysim.core import ( chunk, config, @@ -33,7 +34,7 @@ from activitysim.core.configuration.logit import LocationComponentSettings from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError from activitysim.core.interaction_sample import ( - _resolve_sample_method, + resolve_sample_method, interaction_sample, ) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate @@ -458,37 +459,18 @@ def choose_MAZ_for_TAZ( # prob array with one row TAZ_choice, one column per alternative row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - if full_taz_index is not None: - full_taz_index = pd.Index(full_taz_index, name=DEST_TAZ) - taz_positions = full_taz_index.get_indexer(taz_choices[DEST_TAZ]) - assert (taz_positions >= 0).all() - chooser_rands = np.asarray( - state.get_rn_generator().random_for_df(chooser_df, n=len(full_taz_index)) - ) - chooser_row_positions = np.repeat( - np.arange(len(chooser_df)), taz_choice_counts.to_numpy() - ) - rands = chooser_rands[chooser_row_positions, taz_positions].reshape(-1, 1) - assert len(rands) == len(taz_choices) - elif uniform_taz_choice_counts: - assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) - rands = ( - state.get_rn_generator() - .random_for_df(chooser_df, n=taz_sample_size) - .reshape(-1, 1) - ) - assert len(rands) == len(chooser_df) * taz_sample_size - else: - assert maz_probs.shape == (len(taz_choices), max_maz_count) - chooser_rands = np.asarray( - state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) - ) - chooser_rand_mask = ( - np.arange(taz_sample_size) < taz_choice_counts.to_numpy()[:, np.newaxis] - ) - rands = chooser_rands[chooser_rand_mask].reshape(-1, 1) - assert len(rands) == len(taz_choices) - assert len(rands) == maz_probs.shape[0] + rands = draw_maz_rands( + state=state, + chooser_df=chooser_df, + taz_choices=taz_choices, + taz_choice_counts=taz_choice_counts, + taz_sample_size=taz_sample_size, + maz_probs=maz_probs, + max_maz_count=max_maz_count, + uniform_taz_choice_counts=uniform_taz_choice_counts, + dest_taz_col=DEST_TAZ, + full_taz_index=full_taz_index, + ) # make choices # positions is array with the chosen alternative represented as a column index in probs @@ -675,12 +657,7 @@ def destination_presample( # current RNG, revisit if a counter-based RNG is adapted. full_taz_index = None if state.settings.use_explicit_error_terms: - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" - ) - taz_sample_method = _resolve_sample_method(state, sample_compute_settings) + taz_sample_method = resolve_sample_method(state, model_settings) if taz_sample_method == "poisson": full_taz_index = pd.Index( alternatives.index, name=f"{alt_dest_col_name}_TAZ" diff --git a/activitysim/abm/models/util/bias_logsums.py b/activitysim/abm/models/util/bias_logsums.py index d594b06704..1651e23ef3 100644 --- a/activitysim/abm/models/util/bias_logsums.py +++ b/activitysim/abm/models/util/bias_logsums.py @@ -7,20 +7,28 @@ import numpy as np import pandas as pd -from activitysim.core import workflow -from activitysim.core.interaction_sample import _resolve_sample_method +from activitysim.core import estimation, workflow +from activitysim.core.interaction_sample import resolve_sample_method logger = logging.getLogger(__name__) def maybe_bias_logsums(state: workflow.State, choices_df: pd.DataFrame, model_settings): - # Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling. - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" + """Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling.""" + + # Defensive guard: estimation mode is supposed to be MC-only (see the guard in + # interaction_sample.py that blocks any non-monte_carlo sampling method during + # estimation). If that guard is ever bypassed, this function would otherwise apply + # log(SAMPLE_SIZE) instead of log(ESTIMATION_SAMPLE_SIZE) — the wrong correction — + # and silently skew estimated coefficients. Fail loudly here so the divergence + # cannot reach the user's estimation output. + if estimation.manager.enabled: + raise RuntimeError( + "maybe_bias_logsums should not be called during estimation; " + "Poisson sampling is not allowed in estimation mode." ) - sample_method = _resolve_sample_method(state, sample_compute_settings) + + sample_method = resolve_sample_method(state, model_settings) if ( (sample_method == "poisson") and (model_settings.SAMPLE_SIZE > 0) diff --git a/activitysim/abm/models/util/maz_sampling.py b/activitysim/abm/models/util/maz_sampling.py new file mode 100644 index 0000000000..9b695d4b4c --- /dev/null +++ b/activitysim/abm/models/util/maz_sampling.py @@ -0,0 +1,75 @@ +# ActivitySim +# See full license in LICENSE.txt. +from __future__ import annotations + +import numpy as np +import pandas as pd + +from activitysim.core import workflow + + +def draw_maz_rands( + state: workflow.State, + chooser_df: pd.DataFrame, + taz_choices: pd.DataFrame, + taz_choice_counts: pd.Series, + taz_sample_size: int, + maz_probs: np.ndarray, + max_maz_count: int, + uniform_taz_choice_counts: bool, + dest_taz_col: str, + full_taz_index: pd.Index | None = None, +) -> np.ndarray: + """ + Draw uniform random numbers for the MAZ-within-TAZ choice step. + + Three modes, selected by the inputs: + + - `full_taz_index is not None` (EET-stable / Poisson MAZ-for-TAZ): draw + `len(full_taz_index)` uniforms per chooser keyed to the fixed TAZ + universe, then project to the active TAZ rows via + `full_taz_index.get_indexer(taz_choices[dest_taz_col])`. Gives + cross-scenario stability when the TAZ universe is the same. + - `uniform_taz_choice_counts` (MC / EET with identical per-chooser TAZ + sample size): draw one uniform per (chooser, TAZ-rank). + - otherwise (MC / EET with variable per-chooser TAZ sample size): draw + `taz_sample_size` uniforms per chooser, then mask to each chooser's + actual TAZ count. + + Returns a 2-D array of shape `(maz_probs.shape[0], 1)` with the per-TAZ-row + uniform draw used to pick a MAZ within that TAZ. + """ + if full_taz_index is not None: + full_taz_index = pd.Index(full_taz_index, name=dest_taz_col) + taz_positions = full_taz_index.get_indexer(taz_choices[dest_taz_col]) + assert (taz_positions >= 0).all() + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df( + chooser_df, n=len(full_taz_index) + ) + ) + chooser_row_positions = np.repeat( + np.arange(len(chooser_df)), taz_choice_counts.to_numpy() + ) + rands = chooser_rands[chooser_row_positions, taz_positions].reshape(-1, 1) + assert len(rands) == len(taz_choices) + elif uniform_taz_choice_counts: + assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) + rands = ( + state.get_rn_generator() + .random_for_df(chooser_df, n=taz_sample_size) + .reshape(-1, 1) + ) + assert len(rands) == len(chooser_df) * taz_sample_size + else: + assert maz_probs.shape == (len(taz_choices), max_maz_count) + chooser_rands = np.asarray( + state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) + ) + chooser_rand_mask = ( + np.arange(taz_sample_size) < taz_choice_counts.to_numpy()[:, np.newaxis] + ) + rands = chooser_rands[chooser_rand_mask].reshape(-1, 1) + assert len(rands) == len(taz_choices) + assert len(rands) == maz_probs.shape[0] + return rands diff --git a/activitysim/abm/models/util/tour_destination.py b/activitysim/abm/models/util/tour_destination.py index 29d2a5a215..a9edd81b8c 100644 --- a/activitysim/abm/models/util/tour_destination.py +++ b/activitysim/abm/models/util/tour_destination.py @@ -9,6 +9,7 @@ from activitysim.abm.models.util import logsums as logsum from activitysim.abm.models.util.bias_logsums import maybe_bias_logsums +from activitysim.abm.models.util.maz_sampling import draw_maz_rands from activitysim.abm.tables.size_terms import tour_destination_size_terms from activitysim.core import ( config, @@ -21,7 +22,7 @@ ) from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.interaction_sample import ( - _resolve_sample_method, + resolve_sample_method, interaction_sample, ) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate @@ -434,34 +435,18 @@ def choose_MAZ_for_TAZ( row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - if full_taz_index is not None: - full_taz_index = pd.Index(full_taz_index, name=DEST_TAZ) - taz_positions = full_taz_index.get_indexer(taz_choices[DEST_TAZ]) - assert (taz_positions >= 0).all() - chooser_rands = np.asarray( - state.get_rn_generator().random_for_df(chooser_df, n=len(full_taz_index)) - ) - chooser_row_positions = np.repeat( - np.arange(len(chooser_df)), taz_choice_counts.to_numpy() - ) - rands = chooser_rands[chooser_row_positions, taz_positions].reshape(-1, 1) - assert len(rands) == len(taz_choices) - elif uniform_taz_choice_counts: - assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) - rands = state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) - rands = rands.reshape(-1, 1) - assert len(rands) == len(chooser_df) * taz_sample_size - else: - assert maz_probs.shape == (len(taz_choices), max_maz_count) - chooser_rands = np.asarray( - state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) - ) - chooser_rand_mask = ( - np.arange(taz_sample_size) < taz_choice_counts.to_numpy()[:, np.newaxis] - ) - rands = chooser_rands[chooser_rand_mask].reshape(-1, 1) - assert len(rands) == len(taz_choices) - assert len(rands) == maz_probs.shape[0] + rands = draw_maz_rands( + state=state, + chooser_df=chooser_df, + taz_choices=taz_choices, + taz_choice_counts=taz_choice_counts, + taz_sample_size=taz_sample_size, + maz_probs=maz_probs, + max_maz_count=max_maz_count, + uniform_taz_choice_counts=uniform_taz_choice_counts, + dest_taz_col=DEST_TAZ, + full_taz_index=full_taz_index, + ) # make choices # positions is array with the chosen alternative represented as a column index in probs @@ -643,12 +628,7 @@ def destination_presample( # chooser's sample and would all share one uniform, forcing every duplicate to pick the same MAZ. An # EET-stable MAZ-for-TAZ would need a (TAZ, occurrence-rank)-keyed draw and many more random numbers per # chooser; that's too expensive with the current RNG, revisit if a counter-based RNG is adapted. - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" - ) - taz_sample_method = _resolve_sample_method(state, sample_compute_settings) + taz_sample_method = resolve_sample_method(state, model_settings) use_stable_taz_index = taz_sample_method == "poisson" else: full_taz_index = None @@ -679,7 +659,7 @@ def destination_presample( trace_label=trace_label, zone_layer="taz", stable_alt_positions=stable_alt_positions, - n_total_alts=len(full_taz_index) if full_taz_index is not None else 0, + n_total_alts=len(full_taz_index) if full_taz_index is not None else None, ) # choose a MAZ for each DEST_TAZ choice, choice probability based on MAZ size_term fraction of TAZ total diff --git a/activitysim/abm/models/util/tour_od.py b/activitysim/abm/models/util/tour_od.py index 60f952c183..aae3104f3f 100644 --- a/activitysim/abm/models/util/tour_od.py +++ b/activitysim/abm/models/util/tour_od.py @@ -11,6 +11,7 @@ from activitysim.abm.models.util import logsums as logsum from activitysim.abm.models.util import trip from activitysim.abm.models.util.bias_logsums import maybe_bias_logsums +from activitysim.abm.models.util.maz_sampling import draw_maz_rands from activitysim.abm.models.util.tour_destination import SizeTermCalculator from activitysim.core import ( config, @@ -24,8 +25,8 @@ from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import TourLocationComponentSettings from activitysim.core.interaction_sample import ( - _resolve_sample_method, interaction_sample, + resolve_sample_method, ) from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.util import reindex @@ -509,34 +510,18 @@ def choose_MAZ_for_TAZ( # prob array with one row TAZ_choice, one column per alternative row_sums = padded_maz_sizes.sum(axis=1) maz_probs = np.divide(padded_maz_sizes, row_sums.reshape(-1, 1)) - if full_taz_index is not None: - full_taz_index = pd.Index(full_taz_index, name=DEST_TAZ) - taz_positions = full_taz_index.get_indexer(taz_choices[DEST_TAZ]) - assert (taz_positions >= 0).all() - chooser_rands = np.asarray( - state.get_rn_generator().random_for_df(chooser_df, n=len(full_taz_index)) - ) - chooser_row_positions = np.repeat( - np.arange(len(chooser_df)), taz_choice_counts.to_numpy() - ) - rands = chooser_rands[chooser_row_positions, taz_positions].reshape(-1, 1) - assert len(rands) == len(taz_choices) - elif uniform_taz_choice_counts: - assert maz_probs.shape == (len(chooser_df) * taz_sample_size, max_maz_count) - rands = state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) - rands = rands.reshape(-1, 1) - assert len(rands) == len(chooser_df) * taz_sample_size - else: - assert maz_probs.shape == (len(taz_choices), max_maz_count) - chooser_rands = np.asarray( - state.get_rn_generator().random_for_df(chooser_df, n=taz_sample_size) - ) - chooser_rand_mask = ( - np.arange(taz_sample_size) < taz_choice_counts.to_numpy()[:, np.newaxis] - ) - rands = chooser_rands[chooser_rand_mask].reshape(-1, 1) - assert len(rands) == len(taz_choices) - assert len(rands) == maz_probs.shape[0] + rands = draw_maz_rands( + state=state, + chooser_df=chooser_df, + taz_choices=taz_choices, + taz_choice_counts=taz_choice_counts, + taz_sample_size=taz_sample_size, + maz_probs=maz_probs, + max_maz_count=max_maz_count, + uniform_taz_choice_counts=uniform_taz_choice_counts, + dest_taz_col=DEST_TAZ, + full_taz_index=full_taz_index, + ) # make choices # positions is array with the chosen alternative represented as a column index in probs @@ -630,15 +615,6 @@ def choose_MAZ_for_TAZ( return taz_choices_w_maz -def resolve_sample_method(state, model_settings): - sample_compute_settings = getattr(model_settings, "compute_settings", None) - if sample_compute_settings is not None: - sample_compute_settings = sample_compute_settings.subcomponent_settings( - "sample" - ) - return _resolve_sample_method(state, sample_compute_settings) - - @workflow.func def od_presample( state: workflow.State, diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index e174c2c6b0..745714626a 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -55,6 +55,39 @@ def _resolve_sample_method( return sampling_method +def resolve_sample_method( + state: workflow.State, + model_settings, +) -> InteractionSampleMethod: + """ + Resolve the sampling method for a model from its `model_settings`. + + Wraps the `model_settings.compute_settings → subcomponent_settings("sample") + → _resolve_sample_method` boilerplate that several call sites need (so the + boilerplate lives in one place rather than being open-coded in + location_choice / tour_destination / trip_destination / tour_od / + bias_logsums). + + Parameters + ---------- + state : workflow.State + model_settings : a pydantic model exposing an optional + `compute_settings` attribute (typically a `LogitComponentSettings` + subclass). If absent or None, the method is resolved purely from + `state.settings`. + + Returns + ------- + sampling_method : InteractionSampleMethod + """ + sample_compute_settings = getattr(model_settings, "compute_settings", None) + if sample_compute_settings is not None: + sample_compute_settings = sample_compute_settings.subcomponent_settings( + "sample" + ) + return _resolve_sample_method(state, sample_compute_settings) + + def _poisson_sample_alternatives_inner( probs: pd.DataFrame, poisson_inclusion_probs_values: np.ndarray, @@ -250,6 +283,14 @@ def _poisson_sample_alternatives( while active_row_positions.size > 0: probs_subset = probs.iloc[active_row_positions] + # Each retry call advances the per-row offset by n_total_alts on the + # underlying RNG channel (see SimpleChannel.random_for_df_stable_alt_positions). + # The number of retries is data-dependent, so two scenarios with slightly + # different inclusion probabilities for the same chooser can end up with + # different downstream RNG offsets — defeating cross-scenario stability + # for that chooser. This is rare (only fires when the first Poisson draw + # yields zero samples) but worth keeping in mind for base/project + # comparisons; see the docstring above for context. sampled_results_subset = _poisson_sample_alternatives_inner( probs_subset, inclusion_probs_values[active_row_positions], @@ -265,8 +306,16 @@ def _poisson_sample_alternatives( ] = sampled_results_subset[~no_alts_sampled_mask] if no_alts_sampled_mask.any(): - logger.info(f"Poisson sampling of alternatives failed with {n=}, retrying") failed_row_positions = active_row_positions[no_alts_sampled_mask] + extra_per_retry = ( + f"{n_total_alts}" if n_total_alts is not None else "n_total_alts" + ) + logger.warning( + f"Poisson sampling of alternatives failed for {len(failed_row_positions)} " + f"chooser(s) with {n=}, retrying. Note: retried choosers consume an extra " + f"{extra_per_retry} randoms per retry on this RNG channel, which can cause " + f"downstream RNG offset divergence vs scenarios that did not retry." + ) logger.debug( f"Sampled size was {sample_size}, poisson method mean expected sample size was" + f" {inclusion_probs_values[failed_row_positions].sum(axis=1).mean():.1f}, actual sampled mean was" @@ -739,9 +788,11 @@ def _interaction_sample( sampling_method = _resolve_sample_method(state, compute_settings) - if state.settings.use_explicit_error_terms and estimation.manager.enabled: + # Estimation requires MC sampling and MC choice for now + if estimation.manager.enabled and sampling_method != "monte_carlo": raise ValueError( - "use_explicit_error_terms is not supported with estimation mode" + f"{trace_label}: estimation requires monte_carlo sampling and choice. Set sample_method='monte_carlo'" + + " (or leave it unset) and use_explicit_error_terms=False for estimation runs." ) if sample_size == 0: diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 0202fc986f..c4cc315b3c 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -7,7 +7,15 @@ import numpy as np import pandas as pd -from activitysim.core import chunk, interaction_simulate, logit, tracing, util, workflow +from activitysim.core import ( + chunk, + interaction_simulate, + logit, + random, + tracing, + util, + workflow, +) from activitysim.core.configuration.base import ComputeSettings from activitysim.core.exceptions import SegmentedSpecificationError from activitysim.core.logit import AltsContext @@ -269,7 +277,9 @@ def _interaction_sample_simulate( padded_utilities = padded_utilities.reshape(-1, max_sample_count) if alts_context is not None: - padded_alt_nrs = np.insert(interaction_df[choice_column], inserts, -999) + padded_alt_nrs = np.insert( + interaction_df[choice_column], inserts, random.MASKED_ALT_ID + ) chunk_sizer.log_df(trace_label, "padded_alt_nrs", padded_alt_nrs) padded_alt_nrs = padded_alt_nrs.reshape(-1, max_sample_count) # alt_nrs_df has columns for each alt in the choice set, with values indicating which alt_id @@ -547,14 +557,30 @@ def interaction_sample_simulate( trace_label = tracing.extend_trace_label(trace_label, "interaction_sample_simulate") chunk_tag = chunk_tag or trace_label - # TODO EET: Do we just want to warn here? Or better throw and be explicit? + # Note: when use_explicit_error_terms is True but alts_context is None, EET draws are + # keyed to the per-call active alternative count rather than a stable universe, so they + # are NOT guaranteed to be consistent across scenarios that differ in alternative + # availability. We cannot make this a hard error today because two production callers + # rely on the warning-only fallback: + # - trip_scheduling_choice: SCHEDULE_ID is a per-call enumeration that depends on + # chunk composition and tour duration distribution (see FIXME in + # trip_scheduling_choice.py:282-289 for the proposed redesign that would key + # SCHEDULE_ID to a fixed (OB, MAIN, IB) duration tuple). + # - tour_od_choice: OD id is a string concatenation `f"{orig}_{dest}"`; a stable + # integer universe would be O(n_zones^2) error terms per chooser, which is too + # large to allocate. + # If you add a new EET caller that uses an integer choice column, please pass an + # alts_context built from the stable universe (e.g., AltsContext.from_series(land_use.index)). if state.settings.use_explicit_error_terms: choice_ids_are_int = pd.api.types.is_integer_dtype(alternatives[choice_column]) if alts_context is None and choice_ids_are_int: logger.warning( - "Using integer-coded choice_column values without alts_context when use_explicit_error_terms is true." - + " Ensure this is desired, when running on a sample it should be provided to ensure consistent random" - + " numbers across the whole alternative set." + f"{trace_label}: use_explicit_error_terms is True but no alts_context " + "was passed; EET draws will be keyed to the per-call active alternative " + "count rather than a stable universe. Cross-scenario reproducibility for " + "this model is best-effort only. See the comment in interaction_sample_simulate.py " + "for the two known callers (trip_scheduling_choice, tour_od_choice) and " + "the requirements for opting into stable randoms." ) if alts_context is not None and not choice_ids_are_int: raise ValueError( diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index e76422f4d9..cc5c85d84e 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -10,7 +10,7 @@ import numpy as np import pandas as pd -from activitysim.core import tracing, workflow +from activitysim.core import estimation, tracing, workflow from activitysim.core.choosing import choice_maker from activitysim.core.configuration.logit import LogitNestSpec from activitysim.core.exceptions import ( @@ -40,11 +40,6 @@ class AltsContext: min_alt_id: int max_alt_id: int - def __post_init__(self): - # e.g. for zero based zones max_alt_id = n_alts - 1 - # but for 1 based zones, we don't need to add extra padding - self.n_rands_to_sample = max(self.max_alt_id, self.n_alts_to_cover_max_id) - @classmethod def from_series(cls, ser: Union[pd.Series, pd.Index]) -> "AltsContext": min_alt_id = ser.min() @@ -382,14 +377,23 @@ def _log_positive_stable_for_df( state: workflow.State, df: pd.DataFrame, alpha: float ) -> np.ndarray: alpha = EXACT_NESTED_LOGIT_DTYPE(alpha) - if np.isclose(alpha, 1.0): - return np.zeros(len(df), dtype=EXACT_NESTED_LOGIT_DTYPE) + # ALWAYS draw the two uniforms so the channel offset advances by a data- + # independent amount, regardless of alpha. Without this, a degenerate nest + # with coefficient=1.0 would consume zero rands and shift all downstream + # draws on the persons/tours channel — breaking EET cross-scenario stability + # for any config change that introduces or collapses such a nest. eps = np.finfo(EXACT_NESTED_LOGIT_DTYPE).eps uniforms = np.asarray( state.get_rn_generator().random_for_df(df, n=2), dtype=EXACT_NESTED_LOGIT_DTYPE, ) + + if np.isclose(alpha, 1.0): + # degenerate nest: positive-stable variate is deterministically 1, so log = 0. + # Offset has already advanced above. + return np.zeros(len(df), dtype=EXACT_NESTED_LOGIT_DTYPE) + angle_uniform = np.clip(uniforms[:, 0], eps, 1.0 - eps) exp_uniform = np.clip(uniforms[:, 1], eps, 1.0 - eps) @@ -449,19 +453,26 @@ def sample_nested_logit_exact_leaf_error_terms( if nest.type == "node": all_leaf_children = leaf_children_for_each_node.get(nest.name, []) - if not all_leaf_children: - logger.warning(f"Node nest {nest.name} has no leaf children, skipping.") - continue - - # draw stable term with nest coeff as scale and multiply by path coeff, add to each child alternative + # ALWAYS draw to keep offset advancement topology-independent, even for degenerate nodes with no leaf + # children. This ensures that a config change that adds or removes a no-leaf intermediate node does + # not shift downstream draws on the persons/tours channel. log_stable_for_node = ( nest.product_of_coefficients * _log_positive_stable_for_df(state, alt_utilities, nest.coefficient) ) - # all alternatives for a chooser (row) get the same term, so we repeat the values across columns - error_terms.loc[:, all_leaf_children] += log_stable_for_node.reshape( - -1, 1 - ).repeat(len(all_leaf_children), axis=1) + if not all_leaf_children: + logger.warning( + f"Node nest {nest.name} has no leaf children; discarding draw." + ) + continue + + # All alternatives for a chooser (row) get the same term. + # Use direct numpy broadcasting into the underlying values array — avoids the `.repeat()` materialization + # and pandas label alignment overhead. + # error_terms.loc[:, all_leaf_children] += log_stable_for_node.reshape(-1, 1 + # ).repeat(len(all_leaf_children), axis=1) + col_idx = error_terms.columns.get_indexer(all_leaf_children) + error_terms.values[:, col_idx] += log_stable_for_node[:, None] leaf_path_coefficients = _leaf_path_coefficients( nest_spec, alt_utilities.columns.to_numpy() @@ -565,7 +576,6 @@ def make_choices_utility_based( utilities: pd.DataFrame, trace_label: str = None, trace_choosers=None, - allow_bad_utils=False, nest_spec=None, alts_context: AltsContext | None = None, alt_nrs_df: pd.DataFrame | None = None, @@ -585,8 +595,6 @@ def make_choices_utility_based( the choosers df (for interaction_simulate) to facilitate the reporting of hh_id by report_bad_choices because it can't deduce hh_id from the interaction_dataset which is indexed on index values from alternatives df. - allow_bad_utils : bool - If True, allows utilities with missing or invalid values without raising an error. nest_spec : dict or LogitNestSpec, optional Nest specification for the choice model. If None, will be treated as a multinomial logit model. alts_context : AltsContext, optional @@ -605,9 +613,24 @@ def make_choices_utility_based( is an index into the columns of `probs`. rands : pandas.Series A series of 0s for compatibility with make_choices. For EET, we do not have per-row random numbers. + + Notes + ----- + Bad-row reporting (e.g., a chooser whose alternatives are all `UTIL_UNAVAILABLE`) is the + responsibility of `validate_utils()`, which is invoked at every EET call site + (interaction_sample, interaction_sample_simulate, simulate.eval_mnl, simulate.eval_nl) + BEFORE this function is called. EET argmax always returns a valid integer position; + we do not re-check here. """ trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") + # Estimation requires MC choice currently. Block the EET choice path under estimation. + if estimation.manager.enabled: + raise RuntimeError( + f"{trace_label}: EET choice path reached during estimation. Estimation must use make_choices(probs=...)" + + " on MC probabilities; set use_explicit_error_terms=False for estimation runs." + ) + if nest_spec is None: choices = make_choices_explicit_error_term_mnl( state, @@ -630,18 +653,6 @@ def make_choices_utility_based( alt_nrs_df, ) - missing_choices = np.isnan(choices) # TODO: should we check for infs here too? - if missing_choices.any() and not allow_bad_utils: - report_bad_choices( - state, - missing_choices, - utilities, - trace_label=tracing.extend_trace_label(trace_label, "bad_utils"), - msg="no alternative selected", - # raise_error=False, - trace_choosers=trace_choosers, - ) - # EET does not expose per-row random draws; return zeros for compatibility. # Maybe exposing the seed of the chooser could be an alternative to re-create the random number for # debugging/tracing purposes? diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 5a9a20c313..7be6e314c1 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -20,6 +20,11 @@ _MAX_SEED = 1 << 32 _SEED_MASK = 0xFFFFFFFF +# Sentinel used by callers of gumbel_choice_positions_for_df to mark padded / +# unavailable alternative slots in alt_nrs_df. Exported so callers don't +# re-encode the magic number. +MASKED_ALT_ID = -999 + def hash32(s): """ @@ -437,7 +442,8 @@ def gumbel_choice_positions_for_df( DataFrame with one row per chooser and one column per available alternative. alt_nrs_df : pandas.DataFrame, optional DataFrame aligned to `utilities` whose values identify which dense alternative - each utility column corresponds to. Use -999 for masked or unavailable positions. + each utility column corresponds to. Use `MASKED_ALT_ID` (-999) for masked or + unavailable positions; any other negative value raises ValueError. n_rands : int, optional Number of EV1 draws to generate per chooser row. Required when `alt_nrs_df` is provided and may exceed the visible number of utility columns. @@ -461,7 +467,15 @@ def gumbel_choice_positions_for_df( if n_rands is None: raise ValueError("n_rands is required when alt_nrs_df is provided") alt_nr_values = alt_nrs_df.to_numpy() - masked = alt_nr_values == -999 + # Validate sentinel: MASKED_ALT_ID is the only allowed negative value. + bad_negatives = (alt_nr_values < 0) & (alt_nr_values != MASKED_ALT_ID) + if bad_negatives.any(): + offenders = np.unique(alt_nr_values[bad_negatives]) + raise ValueError( + f"alt_nrs contains negative values other than the " + f"{MASKED_ALT_ID} sentinel: {offenders}" + ) + masked = alt_nr_values == MASKED_ALT_ID safe_alt_nrs = np.where(masked, 0, alt_nr_values) else: if n_rands is None: @@ -483,18 +497,15 @@ def gumbel_choice_positions_for_df( utility_row - np.log(-np.log(row_randoms)) ) else: - candidate_values = utility_row - np.log( - -np.log(row_randoms[safe_alt_nrs[row_num]]) - ) - candidate_values[masked[row_num]] = utility_row[masked[row_num]] - # row_mask = masked[row_num] - # candidate_values = utility_row.copy() - # if not row_mask.all(): - # active = ~row_mask - # row_alt_nrs = safe_alt_nrs[row_num, active] - # candidate_values[active] += -np.log( - # -np.log(row_randoms[row_alt_nrs]) - # ) + # Masked positions are set to -inf so they cannot win argmax, + # and the gumbel transform is skipped for them entirely. + row_mask = masked[row_num] + candidate_values = np.full(n_alts, -np.inf, dtype=np.float64) + active = ~row_mask + if active.any(): + candidate_values[active] = utility_row[active] - np.log( + -np.log(row_randoms[safe_alt_nrs[row_num, active]]) + ) positions[row_num] = np.argmax(candidate_values) self.row_states.loc[utilities.index, "offset"] += n_rands @@ -789,6 +800,17 @@ def drop_channel(self, channel_name): if channel_name in self.channels: logger.debug("Dropping channel '%s'" % (channel_name,)) del self.channels[channel_name] + # Also clear any index_to_channel entries that pointed at the + # dropped channel; a stale mapping would otherwise survive and + # could mis-route a subsequent channel registered against the + # same index name. + stale_index_names = [ + index_name + for index_name, mapped in self.index_to_channel.items() + if mapped == channel_name + ] + for index_name in stale_index_names: + del self.index_to_channel[index_name] else: logger.error( "drop_channel called with unknown channel '%s'" % (channel_name,) diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index 55153cb29f..ef06c83c21 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -46,17 +46,42 @@ logger = logging.getLogger(__name__) -CustomChooser_T = Callable[ - [ - workflow.State, - pd.DataFrame, - pd.DataFrame, - pd.DataFrame, - str, - dict | LogitNestSpec | None, - ], - tuple[pd.Series, pd.Series], -] +CustomChooser_T = Callable[..., tuple[pd.Series, pd.Series]] +""" +Type alias for a custom choice function passed to ``simple_simulate`` / +``simulate.eval_mnl`` / ``simulate.eval_nl``. + +The runtime calling convention is: + +- ``custom_chooser(state, utilities_or_probs, choosers, spec, trace_label)`` + in MC paths (``eval_mnl``, the non-EET branch of ``eval_nl``) and in the + EET branch of ``eval_mnl``. Five positional arguments. +- ``custom_chooser(state, utilities, choosers, spec, trace_label, + nest_spec=nest_spec)`` in the EET branch of ``eval_nl`` only. Five + positional arguments plus a ``nest_spec`` keyword argument. + +The alias is intentionally widened to ``Callable[..., ...]`` rather than +a fully-specified ``Callable[[State, ...], ...]`` so that: + +1. Existing custom choosers written before nested-logit EET landed (5 + positional args, no ``nest_spec``) remain valid implementations — they + are never called with ``nest_spec`` because only the eval_nl EET branch + passes it. +2. New custom choosers that want to support NL+EET can opt in by accepting + ``nest_spec`` as a keyword-only argument, e.g.:: + + def my_chooser(state, utilities, choosers, spec, trace_label, *, + nest_spec: dict | LogitNestSpec | None = None, + ) -> tuple[pd.Series, pd.Series]: + ... + + They MUST give ``nest_spec`` a default of ``None`` so the non-NL-EET call + sites (which do not pass it) still work. + +Return value is ``(choices, rands)`` where ``choices`` is a Series of +chosen alternative positions and ``rands`` is the per-row random draw used +(or zeros for EET, which has no per-row draw to expose). +""" def random_rows(state: workflow.State, df, n): diff --git a/activitysim/core/test/test_interaction_sample_simulate.py b/activitysim/core/test/test_interaction_sample_simulate.py index 090bf2d3d5..40a8dfca5b 100644 --- a/activitysim/core/test/test_interaction_sample_simulate.py +++ b/activitysim/core/test/test_interaction_sample_simulate.py @@ -185,7 +185,6 @@ def fake_make_choices_utility_based( nest_spec=None, trace_label=None, trace_choosers=None, - allow_bad_utils=False, alts_context=None, alt_nrs_df=None, ): diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index 143920fda6..f995965fe8 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -464,6 +464,94 @@ def get_rn_generator(): pdt.assert_frame_equal(error_terms, expected) +def test_sample_nested_logit_exact_leaf_error_terms_three_level_galichon( + monkeypatch, +): + """ + Three-level deterministic accumulation test guarding the Galichon endpoint + convention: Λ_n includes the nest's own coefficient. With + `_log_positive_stable_for_df` monkeypatched to a per-nest constant, the + per-leaf error term is exactly + + sum over ancestors-and-self t of Λ_n(t) * log_S_n(t) + + Λ_parent(leaf) * leaf_gumbel + + where Λ_n is the product of coefficients along the path from root to and + including n. If the implementation were to switch to ancestor-only + products (the misreading flagged in finding #11), the per-leaf totals + would diverge from these expected values. + """ + nest_log_S = {"MOTORIZED": 0.30, "AUTO": -0.20} + + def fake_log_positive_stable_for_df(_state, df, alpha): + # identify nest by its coefficient (each non-root nest has a unique alpha here) + if alpha == pytest.approx(0.85): + return np.full(len(df), nest_log_S["MOTORIZED"], dtype=np.float64) + if alpha == pytest.approx(0.72): + return np.full(len(df), nest_log_S["AUTO"], dtype=np.float64) + raise AssertionError(f"unexpected alpha {alpha}") + + monkeypatch.setattr( + logit, "_log_positive_stable_for_df", fake_log_positive_stable_for_df + ) + + class DummyRNG: + @staticmethod + def gumbel_for_df(df, n): + assert n == df.shape[1] + # one chooser, three leaf columns in order [car, bus, walk] + return np.array([[1.0, 2.0, 3.0]], dtype=np.float64) + + class DummyState: + @staticmethod + def get_rn_generator(): + return DummyRNG() + + nest_spec = { + "name": "root", + "coefficient": 1.0, + "alternatives": [ + { + "name": "MOTORIZED", + "coefficient": 0.85, + "alternatives": [ + { + "name": "AUTO", + "coefficient": 0.72, + "alternatives": ["car", "bus"], + }, + ], + }, + "walk", + ], + } + alt_utilities = pd.DataFrame( + 0.0, + index=pd.Index([42], name="chooser_id"), + columns=["car", "bus", "walk"], + dtype=np.float64, + ) + + error_terms = logit.sample_nested_logit_exact_leaf_error_terms( + DummyState(), alt_utilities, nest_spec + ) + + L_M = 0.85 # Λ_MOTORIZED = θ_MOTORIZED + L_A = 0.85 * 0.72 # Λ_AUTO = θ_MOTORIZED * θ_AUTO + expected_car = L_M * nest_log_S["MOTORIZED"] + L_A * nest_log_S["AUTO"] + L_A * 1.0 + expected_bus = L_M * nest_log_S["MOTORIZED"] + L_A * nest_log_S["AUTO"] + L_A * 2.0 + # walk's parent is root: Λ_parent = 1.0; only the walk gumbel contributes. + expected_walk = 1.0 * 3.0 + + expected = pd.DataFrame( + {"car": [expected_car], "bus": [expected_bus], "walk": [expected_walk]}, + index=alt_utilities.index, + dtype=np.float64, + ) + + pdt.assert_frame_equal(error_terms, expected) + + def test_make_choices_utility_based_sets_zero_rands(monkeypatch): def fake_make_choices_explicit_error_term_mnl( _state, @@ -1682,7 +1770,6 @@ def test_alts_context_from_series_and_properties(): assert ctx.min_alt_id == 3 assert ctx.max_alt_id == 9 assert ctx.n_alts_to_cover_max_id == 10 - assert ctx.n_rands_to_sample == 10 @pytest.mark.parametrize( @@ -1700,4 +1787,3 @@ def test_alts_context_from_num_alts( assert ctx.min_alt_id == expected_min assert ctx.max_alt_id == expected_max assert ctx.n_alts_to_cover_max_id == expected_n_cover - assert ctx.n_rands_to_sample == expected_n_cover From a57e78adcdd4453122750fe849ef0e8094ba66eb Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 19:35:24 -0500 Subject: [PATCH 286/298] consistent spelling --- activitysim/abm/models/util/bias_logsums.py | 4 ++-- activitysim/core/configuration/top.py | 2 +- activitysim/core/random.py | 2 +- docs/dev-guide/sampling-methods.md | 2 +- docs/users-guide/sampling-methods.rst | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/activitysim/abm/models/util/bias_logsums.py b/activitysim/abm/models/util/bias_logsums.py index 1651e23ef3..61bc0ca0a2 100644 --- a/activitysim/abm/models/util/bias_logsums.py +++ b/activitysim/abm/models/util/bias_logsums.py @@ -48,11 +48,11 @@ def maybe_bias_logsums(state: workflow.State, choices_df: pd.DataFrame, model_se else: logger.warning( "Using Poisson sampling method for location choice logsum calculations. Currently the logsums results will" - + " differ from those obtained with monte-carlo or eet sampling by a constant shift of" + + " differ from those obtained with monte_carlo or eet sampling by a constant shift of" + f" log({model_settings.SAMPLE_SIZE}) if using the common correction factor" + " `log(pick_count / prob)` in location choice specs. The results of the Poisson method are unbiased," + " i.e., they agree with the results obtained with a full destination sample, unlike those for" - + " monte-carlo or eet sampling." + + " monte_carlo or eet sampling." ) return choices_df diff --git a/activitysim/core/configuration/top.py b/activitysim/core/configuration/top.py index 88ff28fdd4..d8cdda738e 100644 --- a/activitysim/core/configuration/top.py +++ b/activitysim/core/configuration/top.py @@ -803,7 +803,7 @@ def _check_store_skims_in_shm(self): """ Whether to apply a bias of `log(sample_size)` to the Poisson sampling results. This is a temporary workaround to align Poisson sampling results with the biased - results of the monte-carlo and eet sampling methods, such that models that were + results of the monte_carlo and eet sampling methods, such that models that were estimated with historical biased sampling results can be run with Poisson sampling without needing to re-estimate the model. diff --git a/activitysim/core/random.py b/activitysim/core/random.py index 7be6e314c1..dbf2ed35a1 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -695,7 +695,7 @@ def reset_offsets_for_df(self, df): channel.row_states.loc[df.index, "offset"] = 0 logger.info( f"RNG: resetting random number generator offsets for channel '{channel.channel_name}' for {len(df)} rows" - + f" with index name '{df.index.name}'. Total lenght df: {len(channel.row_states)}" + + f" with index name '{df.index.name}'. Total length df: {len(channel.row_states)}" ) def begin_step(self, step_name): diff --git a/docs/dev-guide/sampling-methods.md b/docs/dev-guide/sampling-methods.md index 937c8848ca..60cb91379e 100644 --- a/docs/dev-guide/sampling-methods.md +++ b/docs/dev-guide/sampling-methods.md @@ -87,7 +87,7 @@ chooser-alternative arrays of error terms in memory. pair is sampled independently with inclusion probability $1 - (1 - p)^s$, where $p$ is the original choice probability and $s$ is the configured sample size. A single inclusion draw is made for each alternative. This is much cheaper than repeated draws for -`eet`, and unlike ``monte-carlo``, it can still benefit from stable alignment of random draws to +`eet`, and unlike ``monte_carlo``, it can still benefit from stable alignment of random draws to alternatives, so it can provide improved noise reduction compared to `monte_carlo` without the full cost of `eet` and therefore it is the default when running with explicit error terms, see {ref}`explicit-error-terms-dev`. diff --git a/docs/users-guide/sampling-methods.rst b/docs/users-guide/sampling-methods.rst index c3fbc0e674..22c47b0d5c 100644 --- a/docs/users-guide/sampling-methods.rst +++ b/docs/users-guide/sampling-methods.rst @@ -42,9 +42,9 @@ Practical differences: * ``poisson`` samples alternatives by inclusion probability, so each sampled alternative appears at most once per chooser. This can change raw sampled shares in highly peaked cases, even though the downstream sampling correction remains well defined. -* ``monte-carlo`` is the fastest method, followed by ``poisson``, with ``eet`` being the slowest. +* ``monte_carlo`` is the fastest method, followed by ``poisson``, with ``eet`` being the slowest. However, for models like location choice, most runtime comes from logsum calculations and the - total difference between ``monte-carlo`` and ``poisson`` sampling is usually very small. + total difference between ``monte_carlo`` and ``poisson`` sampling is usually very small. * ``poisson`` is the current default when running with simulation method explicit error terms because it avoids repeated chooser-by-alternative explicit-error draws during sampling while still providing improved noise reduction compared to Monte Carlo sampling. From ac2adf310f50d23d1209abea2c715cde664b85b8 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 19:36:51 -0500 Subject: [PATCH 287/298] mal-formed html comment --- docs/dev-guide/sampling-methods.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev-guide/sampling-methods.md b/docs/dev-guide/sampling-methods.md index 60cb91379e..53c0984500 100644 --- a/docs/dev-guide/sampling-methods.md +++ b/docs/dev-guide/sampling-methods.md @@ -146,7 +146,7 @@ This means that all three methods use the same correction expression, `np.log(df.pick_count/df.prob)`, even though `prob` has a different interpretation for `poisson` than for the with-replacement methods. -<> + ## Runtime and Simulation Noise From d3b64ba55b68c50c5abfbca7cbb72cc785416c2f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 19:38:01 -0500 Subject: [PATCH 288/298] doc ref --- docs/dev-guide/explicit-error-terms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev-guide/explicit-error-terms.md b/docs/dev-guide/explicit-error-terms.md index 99e65772d5..5211e9fbde 100644 --- a/docs/dev-guide/explicit-error-terms.md +++ b/docs/dev-guide/explicit-error-terms.md @@ -115,7 +115,7 @@ For models that use sub-sampling of alternatives, it is important to keep the sa identical between scenarios, otherwise the error terms for the choice from the sampled set are not guaranteed to be aligned. When running with EET, the default sampling method is ``poisson``, which balances runtime performance and noise reduction. For more details on sampling methods, -see {ref}`sampling_methods_dev_guide`. +see {ref}`sampling-methods-dev`. Finally, it also important to keep the global random number generator seed constant for two individual comparison runs. From 9a10edb5ebffd98c521cdd5fe9773e232a3a1732 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 19:40:54 -0500 Subject: [PATCH 289/298] addresses copilot code review --- activitysim/core/interaction_sample_simulate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index c4cc315b3c..cf1e6dcd97 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -534,8 +534,8 @@ def interaction_sample_simulate( Representation of the full alternatives domain (min and max alternative id) in the absence of sampling. This is used with EET simulation to ensure consistent random numbers across the whole alternative set - ( as the sampled set may change between base and project). When not provided, - EET with integer-coded choice ids will raise an error. + ( as the sampled set may change between base and project). When not provided, ActivitySim will log a + warning when running with EET, because this may reduce alignment of error terms between scenario runs. Returns ------- From 05452ec528d314a5beea2948e8b831147fea930f Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 20:28:43 -0500 Subject: [PATCH 290/298] remove try block --- activitysim/abm/models/location_choice.py | 165 +++++++++++----------- 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/activitysim/abm/models/location_choice.py b/activitysim/abm/models/location_choice.py index e2d94e073c..45c7677908 100644 --- a/activitysim/abm/models/location_choice.py +++ b/activitysim/abm/models/location_choice.py @@ -1095,98 +1095,95 @@ def iterate_location_choice( choices_df ) = None # initialize to None, will be populated in first iteration - try: - for iteration in range(1, max_iterations + 1): - persons_merged_df_ = persons_merged_df.copy() - - if spc.use_shadow_pricing and iteration > 1: - spc.update_shadow_prices(state) - - if spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation": - # filter from the sampled persons - persons_merged_df_ = persons_merged_df_[ - persons_merged_df_.index.isin(spc.sampled_persons.index) - ] - persons_merged_df_ = persons_merged_df_.sort_index() - - # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is - # used on each iteration for the persons being re-simulated, so sampling and final choice draws are - # reproducible across shadow-pricing iterations. - # Scoped to the persons channel for these specific rows via reset_offsets_for_df so the dedicated - # shadow_pricing_persons channel (registered under EET) keeps its offset across iterations and advances - # naturally on each iteration's update_shadow_prices call. - if state.settings.use_explicit_error_terms and iteration > 1: - logger.debug( - f"{trace_label} resetting random number generator offsets for iteration {iteration}" - ) - state.get_rn_generator().reset_offsets_for_df(persons_merged_df_) - - choices_df_, save_sample_df = run_location_choice( - state, - persons_merged_df_, - network_los, - shadow_price_calculator=spc, - want_logsums=want_logsums, - want_sample_table=want_sample_table, - estimator=estimator, - model_settings=model_settings, - chunk_size=chunk_size, - chunk_tag=chunk_tag, - trace_label=tracing.extend_trace_label(trace_label, "i%s" % iteration), + for iteration in range(1, max_iterations + 1): + persons_merged_df_ = persons_merged_df.copy() + + if spc.use_shadow_pricing and iteration > 1: + spc.update_shadow_prices(state) + + if spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation": + # filter from the sampled persons + persons_merged_df_ = persons_merged_df_[ + persons_merged_df_.index.isin(spc.sampled_persons.index) + ] + persons_merged_df_ = persons_merged_df_.sort_index() + + # reset rng offsets to identical state on each iteration. This ensures that the same set of random numbers is + # used on each iteration for the persons being re-simulated, so sampling and final choice draws are + # reproducible across shadow-pricing iterations. + # Scoped to the persons channel for these specific rows via reset_offsets_for_df so the dedicated + # shadow_pricing_persons channel (registered under EET) keeps its offset across iterations and advances + # naturally on each iteration's update_shadow_prices call. + if state.settings.use_explicit_error_terms and iteration > 1: + logger.debug( + f"{trace_label} resetting random number generator offsets for iteration {iteration}" ) + state.get_rn_generator().reset_offsets_for_df(persons_merged_df_) - # choices_df is a pandas DataFrame with columns "choice" and (optionally) "logsum" - if choices_df_ is None: - break - - if spc.use_shadow_pricing: - # handle simulation method - if ( - spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation" - and iteration > 1 - ): - # if a process ends up with no sampled workers in it, hence an empty choice_df_, then choice_df wil be what it was previously - if len(choices_df_) != 0: - choices_df = pd.concat([choices_df, choices_df_], axis=0) - choices_df_index = choices_df_.index.name - choices_df = choices_df.reset_index() - # update choices of workers/students - choices_df = choices_df.drop_duplicates( - subset=[choices_df_index], keep="last" - ) - choices_df = choices_df.set_index(choices_df_index) - choices_df = choices_df.sort_index() - else: - choices_df = choices_df_.copy() + choices_df_, save_sample_df = run_location_choice( + state, + persons_merged_df_, + network_los, + shadow_price_calculator=spc, + want_logsums=want_logsums, + want_sample_table=want_sample_table, + estimator=estimator, + model_settings=model_settings, + chunk_size=chunk_size, + chunk_tag=chunk_tag, + trace_label=tracing.extend_trace_label(trace_label, "i%s" % iteration), + ) + # choices_df is a pandas DataFrame with columns "choice" and (optionally) "logsum" + if choices_df_ is None: + break + + if spc.use_shadow_pricing: + # handle simulation method + if ( + spc.shadow_settings.SHADOW_PRICE_METHOD == "simulation" + and iteration > 1 + ): + # if a process ends up with no sampled workers in it, hence an empty choice_df_, then choice_df wil be what it was previously + if len(choices_df_) != 0: + choices_df = pd.concat([choices_df, choices_df_], axis=0) + choices_df_index = choices_df_.index.name + choices_df = choices_df.reset_index() + # update choices of workers/students + choices_df = choices_df.drop_duplicates( + subset=[choices_df_index], keep="last" + ) + choices_df = choices_df.set_index(choices_df_index) + choices_df = choices_df.sort_index() else: - choices_df = choices_df_ + choices_df = choices_df_.copy() - spc.set_choices( - choices=choices_df["choice"], - segment_ids=persons_merged_df[chooser_segment_column].reindex( - choices_df.index - ), - ) + else: + choices_df = choices_df_ + + spc.set_choices( + choices=choices_df["choice"], + segment_ids=persons_merged_df[chooser_segment_column].reindex( + choices_df.index + ), + ) - if locutor: - spc.write_trace_files(state, iteration) + if locutor: + spc.write_trace_files(state, iteration) - if spc.use_shadow_pricing and spc.check_fit(state, iteration): - logging.info( - "%s converged after iteration %s" - % ( - trace_label, - iteration, - ) + if spc.use_shadow_pricing and spc.check_fit(state, iteration): + logging.info( + "%s converged after iteration %s" + % ( + trace_label, + iteration, ) - break - finally: - # Drop the dedicated shadow_pricing RNG channel (registered lazily under EET by spc.update_shadow_prices) so it - # doesn't survive into the next location_choice model (e.g., school after work) — both models share the same - # channel name and would otherwise collide on the no-overlap assert in SimpleChannel.extend_domain. No-op for MC. - # In try/finally so an exception escaping run_location_choice still drops the channel. - spc.cleanup_rng_channel(state) + ) + break + # Drop the dedicated shadow_pricing RNG channel (registered lazily under EET by spc.update_shadow_prices) so it + # doesn't survive into the next location_choice model (e.g., school after work) — both models share the same + # channel name and would otherwise collide on the no-overlap assert in SimpleChannel.extend_domain. No-op for MC. + spc.cleanup_rng_channel(state) # - shadow price table if locutor: From c226928750c8fbe01e4ea157b4e9abd79e71af33 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 20:54:55 -0500 Subject: [PATCH 291/298] clean up --- activitysim/abm/models/util/bias_logsums.py | 11 +---------- activitysim/core/interaction_sample.py | 6 ------ activitysim/core/interaction_sample_simulate.py | 17 ++++++----------- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/activitysim/abm/models/util/bias_logsums.py b/activitysim/abm/models/util/bias_logsums.py index 61bc0ca0a2..d3b1cc8e8e 100644 --- a/activitysim/abm/models/util/bias_logsums.py +++ b/activitysim/abm/models/util/bias_logsums.py @@ -16,17 +16,8 @@ def maybe_bias_logsums(state: workflow.State, choices_df: pd.DataFrame, model_settings): """Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling.""" - # Defensive guard: estimation mode is supposed to be MC-only (see the guard in - # interaction_sample.py that blocks any non-monte_carlo sampling method during - # estimation). If that guard is ever bypassed, this function would otherwise apply - # log(SAMPLE_SIZE) instead of log(ESTIMATION_SAMPLE_SIZE) — the wrong correction — - # and silently skew estimated coefficients. Fail loudly here so the divergence - # cannot reach the user's estimation output. if estimation.manager.enabled: - raise RuntimeError( - "maybe_bias_logsums should not be called during estimation; " - "Poisson sampling is not allowed in estimation mode." - ) + raise RuntimeError("maybe_bias_logsums should not be called during estimation.") sample_method = resolve_sample_method(state, model_settings) if ( diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index 745714626a..a244c05e37 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -62,12 +62,6 @@ def resolve_sample_method( """ Resolve the sampling method for a model from its `model_settings`. - Wraps the `model_settings.compute_settings → subcomponent_settings("sample") - → _resolve_sample_method` boilerplate that several call sites need (so the - boilerplate lives in one place rather than being open-coded in - location_choice / tour_destination / trip_destination / tour_od / - bias_logsums). - Parameters ---------- state : workflow.State diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index cf1e6dcd97..993d1f0a57 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -572,20 +572,15 @@ def interaction_sample_simulate( # If you add a new EET caller that uses an integer choice column, please pass an # alts_context built from the stable universe (e.g., AltsContext.from_series(land_use.index)). if state.settings.use_explicit_error_terms: - choice_ids_are_int = pd.api.types.is_integer_dtype(alternatives[choice_column]) - if alts_context is None and choice_ids_are_int: + if alts_context is None: logger.warning( - f"{trace_label}: use_explicit_error_terms is True but no alts_context " - "was passed; EET draws will be keyed to the per-call active alternative " - "count rather than a stable universe. Cross-scenario reproducibility for " - "this model is best-effort only. See the comment in interaction_sample_simulate.py " - "for the two known callers (trip_scheduling_choice, tour_od_choice) and " - "the requirements for opting into stable randoms." + f"{trace_label}: use_explicit_error_terms is True but no alts_context was passed; EET draws will be " + "keyed to the per-call active alternative count rather than a stable universe, which can affect " + "cross-scenario reproducibility." ) + choice_ids_are_int = pd.api.types.is_integer_dtype(alternatives[choice_column]) if alts_context is not None and not choice_ids_are_int: - raise ValueError( - "alts_context can only be used with integer-coded choice_column values" - ) + raise ValueError("alts_context can only be used with integer-coded choice_column values") result_list = [] for ( From a93da9c4150705dda04cc3e657939ac360ee0c69 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 21:04:45 -0500 Subject: [PATCH 292/298] logit clean up --- activitysim/core/logit.py | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index cc5c85d84e..fd18643596 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -10,7 +10,7 @@ import numpy as np import pandas as pd -from activitysim.core import estimation, tracing, workflow +from activitysim.core import tracing, workflow from activitysim.core.choosing import choice_maker from activitysim.core.configuration.logit import LogitNestSpec from activitysim.core.exceptions import ( @@ -377,12 +377,6 @@ def _log_positive_stable_for_df( state: workflow.State, df: pd.DataFrame, alpha: float ) -> np.ndarray: alpha = EXACT_NESTED_LOGIT_DTYPE(alpha) - - # ALWAYS draw the two uniforms so the channel offset advances by a data- - # independent amount, regardless of alpha. Without this, a degenerate nest - # with coefficient=1.0 would consume zero rands and shift all downstream - # draws on the persons/tours channel — breaking EET cross-scenario stability - # for any config change that introduces or collapses such a nest. eps = np.finfo(EXACT_NESTED_LOGIT_DTYPE).eps uniforms = np.asarray( state.get_rn_generator().random_for_df(df, n=2), @@ -391,7 +385,8 @@ def _log_positive_stable_for_df( if np.isclose(alpha, 1.0): # degenerate nest: positive-stable variate is deterministically 1, so log = 0. - # Offset has already advanced above. + # This early exit needs to happen after drawing the two uniform randoms so the channel + # offset advances by the same amount independent of alpha. return np.zeros(len(df), dtype=EXACT_NESTED_LOGIT_DTYPE) angle_uniform = np.clip(uniforms[:, 0], eps, 1.0 - eps) @@ -453,9 +448,6 @@ def sample_nested_logit_exact_leaf_error_terms( if nest.type == "node": all_leaf_children = leaf_children_for_each_node.get(nest.name, []) - # ALWAYS draw to keep offset advancement topology-independent, even for degenerate nodes with no leaf - # children. This ensures that a config change that adds or removes a no-leaf intermediate node does - # not shift downstream draws on the persons/tours channel. log_stable_for_node = ( nest.product_of_coefficients * _log_positive_stable_for_df(state, alt_utilities, nest.coefficient) @@ -467,12 +459,12 @@ def sample_nested_logit_exact_leaf_error_terms( continue # All alternatives for a chooser (row) get the same term. - # Use direct numpy broadcasting into the underlying values array — avoids the `.repeat()` materialization - # and pandas label alignment overhead. - # error_terms.loc[:, all_leaf_children] += log_stable_for_node.reshape(-1, 1 - # ).repeat(len(all_leaf_children), axis=1) col_idx = error_terms.columns.get_indexer(all_leaf_children) error_terms.values[:, col_idx] += log_stable_for_node[:, None] + # Now using direct numpy broadcasting into the underlying values array — avoids the `.repeat()` + # materialization and pandas label alignment overhead. + # error_terms.loc[:, all_leaf_children] += log_stable_for_node.reshape(-1, 1 + # ).repeat(len(all_leaf_children), axis=1) leaf_path_coefficients = _leaf_path_coefficients( nest_spec, alt_utilities.columns.to_numpy() @@ -624,13 +616,6 @@ def make_choices_utility_based( """ trace_label = tracing.extend_trace_label(trace_label, "make_choices_utility_based") - # Estimation requires MC choice currently. Block the EET choice path under estimation. - if estimation.manager.enabled: - raise RuntimeError( - f"{trace_label}: EET choice path reached during estimation. Estimation must use make_choices(probs=...)" + - " on MC probabilities; set use_explicit_error_terms=False for estimation runs." - ) - if nest_spec is None: choices = make_choices_explicit_error_term_mnl( state, From 28b4011fbcd7a4cdfded220f0dbd8a979b61f357 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Fri, 26 Jun 2026 21:08:34 -0500 Subject: [PATCH 293/298] clean up --- activitysim/core/random.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/activitysim/core/random.py b/activitysim/core/random.py index dbf2ed35a1..3524bc155c 100644 --- a/activitysim/core/random.py +++ b/activitysim/core/random.py @@ -20,9 +20,7 @@ _MAX_SEED = 1 << 32 _SEED_MASK = 0xFFFFFFFF -# Sentinel used by callers of gumbel_choice_positions_for_df to mark padded / -# unavailable alternative slots in alt_nrs_df. Exported so callers don't -# re-encode the magic number. +# Used by callers of gumbel_choice_positions_for_df to mark padded or unavailable alternative slots in alt_nrs_df MASKED_ALT_ID = -999 From 82b208f3cc967bbb116544fe5f7aec6bc47a9257 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 27 Jun 2026 12:22:41 +1000 Subject: [PATCH 294/298] lint --- activitysim/abm/models/util/maz_sampling.py | 4 +--- activitysim/core/interaction_sample.py | 4 ++-- activitysim/core/interaction_sample_simulate.py | 4 +++- activitysim/core/test/test_logit.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/activitysim/abm/models/util/maz_sampling.py b/activitysim/abm/models/util/maz_sampling.py index 9b695d4b4c..ad718b0812 100644 --- a/activitysim/abm/models/util/maz_sampling.py +++ b/activitysim/abm/models/util/maz_sampling.py @@ -44,9 +44,7 @@ def draw_maz_rands( taz_positions = full_taz_index.get_indexer(taz_choices[dest_taz_col]) assert (taz_positions >= 0).all() chooser_rands = np.asarray( - state.get_rn_generator().random_for_df( - chooser_df, n=len(full_taz_index) - ) + state.get_rn_generator().random_for_df(chooser_df, n=len(full_taz_index)) ) chooser_row_positions = np.repeat( np.arange(len(chooser_df)), taz_choice_counts.to_numpy() diff --git a/activitysim/core/interaction_sample.py b/activitysim/core/interaction_sample.py index a244c05e37..f86712c2bf 100644 --- a/activitysim/core/interaction_sample.py +++ b/activitysim/core/interaction_sample.py @@ -785,8 +785,8 @@ def _interaction_sample( # Estimation requires MC sampling and MC choice for now if estimation.manager.enabled and sampling_method != "monte_carlo": raise ValueError( - f"{trace_label}: estimation requires monte_carlo sampling and choice. Set sample_method='monte_carlo'" + - " (or leave it unset) and use_explicit_error_terms=False for estimation runs." + f"{trace_label}: estimation requires monte_carlo sampling and choice. Set sample_method='monte_carlo'" + + " (or leave it unset) and use_explicit_error_terms=False for estimation runs." ) if sample_size == 0: diff --git a/activitysim/core/interaction_sample_simulate.py b/activitysim/core/interaction_sample_simulate.py index 993d1f0a57..eb8896d009 100644 --- a/activitysim/core/interaction_sample_simulate.py +++ b/activitysim/core/interaction_sample_simulate.py @@ -580,7 +580,9 @@ def interaction_sample_simulate( ) choice_ids_are_int = pd.api.types.is_integer_dtype(alternatives[choice_column]) if alts_context is not None and not choice_ids_are_int: - raise ValueError("alts_context can only be used with integer-coded choice_column values") + raise ValueError( + "alts_context can only be used with integer-coded choice_column values" + ) result_list = [] for ( diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index f995965fe8..1d726bee62 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -536,8 +536,8 @@ def get_rn_generator(): DummyState(), alt_utilities, nest_spec ) - L_M = 0.85 # Λ_MOTORIZED = θ_MOTORIZED - L_A = 0.85 * 0.72 # Λ_AUTO = θ_MOTORIZED * θ_AUTO + L_M = 0.85 # Λ_MOTORIZED = θ_MOTORIZED + L_A = 0.85 * 0.72 # Λ_AUTO = θ_MOTORIZED * θ_AUTO expected_car = L_M * nest_log_S["MOTORIZED"] + L_A * nest_log_S["AUTO"] + L_A * 1.0 expected_bus = L_M * nest_log_S["MOTORIZED"] + L_A * nest_log_S["AUTO"] + L_A * 2.0 # walk's parent is root: Λ_parent = 1.0; only the walk gumbel contributes. From a859214a455db441f90567365fca43ca9b7d258e Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 27 Jun 2026 16:41:03 +1000 Subject: [PATCH 295/298] estimation safe guard only for poisson --- activitysim/abm/models/util/bias_logsums.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/activitysim/abm/models/util/bias_logsums.py b/activitysim/abm/models/util/bias_logsums.py index d3b1cc8e8e..26793dad69 100644 --- a/activitysim/abm/models/util/bias_logsums.py +++ b/activitysim/abm/models/util/bias_logsums.py @@ -16,9 +16,6 @@ def maybe_bias_logsums(state: workflow.State, choices_df: pd.DataFrame, model_settings): """Check for temporary fix to bias logsums for Poisson sampling results to align with MC/eet sampling.""" - if estimation.manager.enabled: - raise RuntimeError("maybe_bias_logsums should not be called during estimation.") - sample_method = resolve_sample_method(state, model_settings) if ( (sample_method == "poisson") @@ -27,6 +24,11 @@ def maybe_bias_logsums(state: workflow.State, choices_df: pd.DataFrame, model_se ): # Only apply for sample size > 0, for unsampled disagg acc the MC/eet results are unbiased and we # want to stay consistent. + if estimation.manager.enabled: + raise RuntimeError( + "maybe_bias_logsums should not be called during estimation." + ) + if state.settings.bias_location_choice_logsums_for_poisson_sampling: logger.warning( "Applying bias correction to location logsums with Poisson sampling to align with MC/eet sampling." From ecb3131db72012ebe8567fadc60611198af318f3 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 27 Jun 2026 18:36:54 +1000 Subject: [PATCH 296/298] deletes arc test as discussed in engineering meeting in June --- .github/workflows/core_tests.yml | 1 - .../prototype_arc/test/configs/settings.yaml | 42 --------- .../test/configs_eet/settings.yaml | 3 - .../test/configs_recode/network_los.yaml | 6 -- .../test/configs_recode/settings.yaml | 30 ------ .../test/configs_sharrow/network_los.yaml | 6 -- .../test/configs_sharrow/settings.yaml | 31 ------- .../test/regress/final_trips.csv | 91 ------------------ .../test/regress/final_trips_eet.csv | 69 -------------- .../test/regress/final_trips_sh.csv | 91 ------------------ .../examples/prototype_arc/test/simulation.py | 16 ---- .../examples/prototype_arc/test/test_arc.py | 93 ------------------- 12 files changed, 479 deletions(-) delete mode 100644 activitysim/examples/prototype_arc/test/configs/settings.yaml delete mode 100644 activitysim/examples/prototype_arc/test/configs_eet/settings.yaml delete mode 100644 activitysim/examples/prototype_arc/test/configs_recode/network_los.yaml delete mode 100644 activitysim/examples/prototype_arc/test/configs_recode/settings.yaml delete mode 100644 activitysim/examples/prototype_arc/test/configs_sharrow/network_los.yaml delete mode 100644 activitysim/examples/prototype_arc/test/configs_sharrow/settings.yaml delete mode 100644 activitysim/examples/prototype_arc/test/regress/final_trips.csv delete mode 100644 activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv delete mode 100644 activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv delete mode 100644 activitysim/examples/prototype_arc/test/simulation.py delete mode 100644 activitysim/examples/prototype_arc/test/test_arc.py diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 7c85efbd28..82a6ce4a04 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -142,7 +142,6 @@ jobs: matrix: region: - prototype_mtc - - prototype_arc - placeholder_psrc - prototype_mtc_extended - production_semcog diff --git a/activitysim/examples/prototype_arc/test/configs/settings.yaml b/activitysim/examples/prototype_arc/test/configs/settings.yaml deleted file mode 100644 index 52e1643451..0000000000 --- a/activitysim/examples/prototype_arc/test/configs/settings.yaml +++ /dev/null @@ -1,42 +0,0 @@ -inherit_settings: True - -# EXAMPLE_HAS_WARNINGS -strict: False - -# number of households to simulate -households_sample_size: 10 -# simulate all households -# households_sample_size: 0 - -chunk_size: 0 - -# - shadow pricing global switches -# turn shadow_pricing on and off for all models (e.g. school and work) -# shadow pricing is deprecated for less than full samples -# see shadow_pricing.yaml for additional settings -use_shadow_pricing: False - -# turn writing of sample_tables on and off for all models -# (if True, tables will be written if DEST_CHOICE_SAMPLE_TABLE_NAME is specified in individual model settings) -want_dest_choice_sample_tables: False - -# global switch to turn on or off presampling of destination alternatives at TAZ level (multizone models only) -want_dest_choice_presampling: True - -# - tracing -# trace household id; comment out or leave empty for no trace -trace_hh_id: - -# trace origin, destination in accessibility calculation; comment out or leave empty for no trace -# trace_od: [5, 11] -trace_od: - -output_tables: - h5_store: False - action: include - prefix: final_ - sort: True - tables: - - trips - -recode_pipeline_columns: False diff --git a/activitysim/examples/prototype_arc/test/configs_eet/settings.yaml b/activitysim/examples/prototype_arc/test/configs_eet/settings.yaml deleted file mode 100644 index 08c06d702e..0000000000 --- a/activitysim/examples/prototype_arc/test/configs_eet/settings.yaml +++ /dev/null @@ -1,3 +0,0 @@ -inherit_settings: True - -use_explicit_error_terms: True diff --git a/activitysim/examples/prototype_arc/test/configs_recode/network_los.yaml b/activitysim/examples/prototype_arc/test/configs_recode/network_los.yaml deleted file mode 100644 index 1c4cd79daf..0000000000 --- a/activitysim/examples/prototype_arc/test/configs_recode/network_los.yaml +++ /dev/null @@ -1,6 +0,0 @@ -inherit_settings: True - -# read cached skims (using numpy memmap) from output directory (memmap is faster than omx ) -read_skim_cache: False -# write memmapped cached skims to output directory after reading from omx, for use in subsequent runs -write_skim_cache: False diff --git a/activitysim/examples/prototype_arc/test/configs_recode/settings.yaml b/activitysim/examples/prototype_arc/test/configs_recode/settings.yaml deleted file mode 100644 index 7e9d6f92ac..0000000000 --- a/activitysim/examples/prototype_arc/test/configs_recode/settings.yaml +++ /dev/null @@ -1,30 +0,0 @@ -inherit_settings: True - -# EXAMPLE_HAS_WARNINGS -strict: False - -# number of households to simulate -households_sample_size: 10 -chunk_size: 0 - -# - shadow pricing global switches -use_shadow_pricing: False - -# turn writing of sample_tables on and off for all models -# (if True, tables will be written if DEST_CHOICE_SAMPLE_TABLE_NAME is specified in individual model settings) -want_dest_choice_sample_tables: False - -cleanup_pipeline_after_run: True - -output_tables: - h5_store: False - action: include - prefix: final_ - sort: True - tables: - - tablename: trips - decode_columns: - origin: land_use.zone_id - destination: land_use.zone_id - -recode_pipeline_columns: True diff --git a/activitysim/examples/prototype_arc/test/configs_sharrow/network_los.yaml b/activitysim/examples/prototype_arc/test/configs_sharrow/network_los.yaml deleted file mode 100644 index 1c4cd79daf..0000000000 --- a/activitysim/examples/prototype_arc/test/configs_sharrow/network_los.yaml +++ /dev/null @@ -1,6 +0,0 @@ -inherit_settings: True - -# read cached skims (using numpy memmap) from output directory (memmap is faster than omx ) -read_skim_cache: False -# write memmapped cached skims to output directory after reading from omx, for use in subsequent runs -write_skim_cache: False diff --git a/activitysim/examples/prototype_arc/test/configs_sharrow/settings.yaml b/activitysim/examples/prototype_arc/test/configs_sharrow/settings.yaml deleted file mode 100644 index ad0541e5fc..0000000000 --- a/activitysim/examples/prototype_arc/test/configs_sharrow/settings.yaml +++ /dev/null @@ -1,31 +0,0 @@ -inherit_settings: True - -# EXAMPLE_HAS_WARNINGS -strict: False - -# number of households to simulate -households_sample_size: 10 -chunk_size: 0 - -# - shadow pricing global switches -use_shadow_pricing: False - -# turn writing of sample_tables on and off for all models -# (if True, tables will be written if DEST_CHOICE_SAMPLE_TABLE_NAME is specified in individual model settings) -want_dest_choice_sample_tables: False - -cleanup_pipeline_after_run: True - -output_tables: - h5_store: False - action: include - prefix: final_ - sort: True - tables: - - tablename: trips - decode_columns: - origin: land_use.zone_id - destination: land_use.zone_id - -sharrow: require -recode_pipeline_columns: True diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips.csv b/activitysim/examples/prototype_arc/test/regress/final_trips.csv deleted file mode 100644 index 79f1c9b937..0000000000 --- a/activitysim/examples/prototype_arc/test/regress/final_trips.csv +++ /dev/null @@ -1,91 +0,0 @@ -"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","depart","trip_mode","mode_choice_logsum","trip_id" -113762,42730,"othmaint",1,true,1,106,103,4664270,"othmaint",,10,"DRIVEALONEFREE",-0.3567815833091734,37314161 -113762,42730,"othmaint",1,false,1,103,106,4664270,"home",,11,"DRIVEALONEFREE",-0.3564603142738344,37314165 -116448,43843,"atwork",1,true,1,106,101,4774372,"atwork",,20,"DRIVEALONEFREE",-0.3217517094135284,38194977 -116448,43843,"atwork",1,false,1,101,106,4774372,"work",,21,"DRIVEALONEFREE",-0.3217517094135284,38194981 -116449,43843,"othdiscr",1,true,1,106,103,4774383,"othdiscr",,32,"SHARED2FREE",0.7593914979829192,38195065 -116449,43843,"othdiscr",1,false,1,103,106,4774383,"home",,37,"SHARED2FREE",0.7593914979829192,38195069 -116448,43843,"work",1,true,2,107,103,4774407,"othmaint",9.244319214996622,10,"DRIVEALONEFREE",-0.6671371741294861,38195257 -116448,43843,"work",2,true,2,101,107,4774407,"work",,11,"DRIVEALONEFREE",-0.5893840121269226,38195258 -116448,43843,"work",1,false,1,103,101,4774407,"home",,30,"DRIVEALONEFREE",-0.5012717045307159,38195261 -116449,43843,"work",1,true,2,106,103,4774448,"othmaint",10.644734946815246,12,"DRIVEALONEFREE",0.05086305622830629,38195585 -116449,43843,"work",2,true,2,102,106,4774448,"work",,15,"DRIVEALONEFREE",0.03291252148410589,38195586 -116449,43843,"work",1,false,3,103,102,4774448,"othmaint",10.796498240479236,24,"SHARED2FREE",0.09835208854623434,38195589 -116449,43843,"work",2,false,3,103,103,4774448,"work",12.367122837815295,26,"DRIVEALONEFREE",0.24015159118542195,38195590 -116449,43843,"work",3,false,3,103,103,4774448,"home",,26,"DRIVEALONEFREE",0.24015159118542195,38195591 -116450,43843,"school",1,true,1,106,103,4774481,"school",,9,"SCHOOL_BUS",4.351044797545671,38195849 -116450,43843,"school",1,false,1,103,106,4774481,"home",,27,"SCHOOL_BUS",4.351044797545671,38195853 -116450,43843,"shopping",1,true,1,101,103,4774483,"shopping",,27,"SHARED2FREE",-0.44410188801130307,38195865 -116450,43843,"shopping",1,false,1,103,101,4774483,"home",,30,"SHARED2FREE",-0.4574908823858229,38195869 -120774,45311,"atwork",1,true,1,101,102,4951738,"atwork",,20,"DRIVEALONEFREE",-0.4112828999996184,39613905 -120774,45311,"atwork",1,false,1,102,101,4951738,"work",,21,"DRIVEALONEFREE",-0.4119254976749421,39613909 -120774,45311,"work",1,true,2,106,105,4951773,"work",10.647318549180723,10,"DRIVEALONEFREE",-0.4328329442501069,39614185 -120774,45311,"work",2,true,2,102,106,4951773,"work",,11,"DRIVEALONEFREE",-0.3480328878879547,39614186 -120774,45311,"work",1,false,1,105,102,4951773,"home",,30,"DRIVEALONEFREE",-0.6046856504917144,39614189 -120775,45311,"work",1,true,1,101,105,4951814,"work",,9,"DRIVEALONEFREE",-0.6009435992240908,39614513 -120775,45311,"work",1,false,3,101,101,4951814,"work",10.767545755383658,26,"DRIVEALONEFREE",-0.35670998854637154,39614517 -120775,45311,"work",2,false,3,107,101,4951814,"othmaint",9.370711100307654,26,"DRIVEALONEFREE",-0.5956824945449828,39614518 -120775,45311,"work",3,false,3,105,107,4951814,"home",,29,"DRIVEALONEFREE",-0.43356653208732604,39614519 -123133,46056,"work",1,true,1,106,106,5048492,"work",,20,"DRIVEALONEFREE",-0.19777291302680963,40387937 -123133,46056,"work",1,false,1,106,106,5048492,"home",,40,"DRIVEALONEFREE",-0.1974023956537246,40387941 -132037,49258,"othmaint",1,true,1,122,110,5413545,"othmaint",,23,"DRIVEALONEFREE",-0.7390051318168641,43308361 -132037,49258,"othmaint",1,false,2,114,122,5413545,"eatout",8.785799297132586,24,"DRIVEALONEFREE",-0.5175821724891663,43308365 -132037,49258,"othmaint",2,false,2,110,114,5413545,"home",,24,"DRIVEALONEFREE",-0.5938398692131043,43308366 -132038,49258,"escort",1,true,1,107,110,5413567,"escort",,10,"SHARED3FREE",-0.002601425939803153,43308537 -132038,49258,"escort",1,false,1,110,107,5413567,"home",,22,"SHARED3FREE",-0.002601425939803153,43308541 -136983,50912,"work",1,true,2,123,112,5616342,"eatout",9.353397383302754,31,"DRIVEALONEFREE",-0.5493329919815063,44930737 -136983,50912,"work",2,true,2,104,123,5616342,"work",,32,"DRIVEALONEFREE",-0.6666110144615174,44930738 -136983,50912,"work",1,false,2,112,104,5616342,"social",11.149774183428809,32,"DRIVEALONEFREE",-0.5499035404682159,44930741 -136983,50912,"work",2,false,2,112,112,5616342,"home",,34,"DRIVEALONEFREE",-0.18331599397659298,44930742 -136984,50912,"work",1,true,2,101,112,5616383,"shopping",9.520915574738705,11,"DRIVEALONEFREE",-0.6129478299617769,44931065 -136984,50912,"work",2,true,2,107,101,5616383,"work",,11,"DRIVEALONEFREE",-0.6193944739341735,44931066 -136984,50912,"work",1,false,3,123,107,5616383,"work",10.775923228059439,27,"DRIVEALONEFREE",-0.7651270068168641,44931069 -136984,50912,"work",2,false,3,104,123,5616383,"escort",9.51963410471578,27,"DRIVEALONEFREE",-0.6666110144615174,44931070 -136984,50912,"work",3,false,3,112,104,5616383,"home",,30,"DRIVEALONEFREE",-0.5499035404682159,44931071 -145187,53716,"othmaint",1,true,3,121,116,5952684,"social",9.947861733897312,8,"SHARED3FREE",-0.41955731333905055,47621473 -145187,53716,"othmaint",2,true,3,112,121,5952684,"othmaint",9.26102871194454,11,"SHARED3FREE",-0.6422730395793916,47621474 -145187,53716,"othmaint",3,true,3,122,112,5952684,"othmaint",,11,"SHARED3FREE",-0.6419082540988923,47621475 -145187,53716,"othmaint",1,false,1,116,122,5952684,"home",,20,"SHARED3FREE",-0.6134629528820534,47621477 -145188,53716,"escort",1,true,1,114,116,5952717,"escort",,29,"DRIVEALONEFREE",-0.15083797590032313,47621737 -145188,53716,"escort",1,false,1,116,114,5952717,"home",,30,"SHARED2FREE",-0.15179813514136692,47621741 -145189,53716,"school",1,true,1,114,116,5952780,"school",,10,"SCHOOL_BUS",4.3079239998221395,47622241 -145189,53716,"school",1,false,1,116,114,5952780,"home",,24,"SCHOOL_BUS",4.3079239998221395,47622245 -145190,53716,"school",1,true,1,114,116,5952821,"school",,9,"SHARED2FREE",-0.20617904920050897,47622569 -145190,53716,"school",1,false,1,116,114,5952821,"home",,24,"SHARED2FREE",-0.20568500752544042,47622573 -147129,54342,"othdiscr",1,true,1,116,117,6032314,"othdiscr",,27,"DRIVEALONEFREE",-0.5246167486667632,48258513 -147129,54342,"othdiscr",1,false,1,117,116,6032314,"home",,33,"DRIVEALONEFREE",-0.4912015503406525,48258517 -147129,54342,"othmaint",1,true,1,114,117,6032317,"othmaint",,34,"DRIVEALONEFREE",-0.6871321834564209,48258537 -147129,54342,"othmaint",1,false,2,114,114,6032317,"shopping",9.148774093624228,37,"DRIVEALONEFREE",-0.4237361037254333,48258541 -147129,54342,"othmaint",2,false,2,117,114,6032317,"home",,38,"DRIVEALONEFREE",-0.6845617927551271,48258542 -171822,63802,"eatout",1,true,1,127,135,7044708,"eatout",,31,"DRIVEALONEFREE",-0.652624578666687,56357665 -171822,63802,"eatout",1,false,1,135,127,7044708,"home",,34,"DRIVEALONEFREE",-0.6343104555130004,56357669 -171822,63802,"escort",1,true,1,135,135,7044711,"escort",,28,"SHARED3FREE",0.07706324792840326,56357689 -171822,63802,"escort",1,false,2,135,135,7044711,"escort",11.356267091092906,28,"SHARED3FREE",0.07706324792840326,56357693 -171822,63802,"escort",2,false,2,135,135,7044711,"home",,28,"SHARED3FREE",0.07706324792840326,56357694 -171822,63802,"othdiscr",1,true,3,131,135,7044717,"othdiscr",12.194779637866755,13,"SHARED2FREE",0.5999776535886836,56357737 -171822,63802,"othdiscr",2,true,3,130,131,7044717,"shopping",13.357506128369907,13,"SHARED2FREE",0.620004705610611,56357738 -171822,63802,"othdiscr",3,true,3,130,130,7044717,"othdiscr",,13,"SHARED2FREE",0.6960546331136191,56357739 -171822,63802,"othdiscr",1,false,1,135,130,7044717,"home",,14,"SHARED2FREE",0.6487159219305315,56357741 -171823,63802,"shopping",1,true,4,131,135,7044776,"othmaint",10.342612763246748,24,"SHARED3FREE",-0.1461983375796215,56358209 -171823,63802,"shopping",2,true,4,131,131,7044776,"social",12.281771137855209,25,"SHARED3FREE",-0.01216970989402637,56358210 -171823,63802,"shopping",3,true,4,131,131,7044776,"shopping",11.556938954932807,26,"SHARED3FREE",-0.01216970989402637,56358211 -171823,63802,"shopping",4,true,4,131,131,7044776,"shopping",,26,"SHARED3FREE",-0.01216970989402637,56358212 -171823,63802,"shopping",1,false,1,135,131,7044776,"home",,27,"DRIVEALONEFREE",-0.1509559426724583,56358213 -171824,63802,"othdiscr",1,true,1,131,135,7044809,"othdiscr",,32,"SHARED2FREE",-0.4602414968534174,56358473 -171824,63802,"othdiscr",1,false,1,135,131,7044809,"home",,37,"SHARED2FREE",-0.45329299490631025,56358477 -171824,63802,"school",1,true,2,135,135,7044815,"escort",11.63502708221396,10,"SHARED2FREE",0.10569338295856193,56358521 -171824,63802,"school",2,true,2,135,135,7044815,"school",,11,"SHARED3FREE",0.10569338295856193,56358522 -171824,63802,"school",1,false,2,135,135,7044815,"othdiscr",11.906310169709501,25,"SHARED3FREE",0.10545807870230886,56358525 -171824,63802,"school",2,false,2,135,135,7044815,"home",,26,"SHARED3FREE",0.10545807870230886,56358526 -171825,63802,"othdiscr",1,true,1,131,135,7044850,"othdiscr",,29,"SHARED3FREE",-0.2817694611797112,56358801 -171825,63802,"othdiscr",1,false,2,132,131,7044850,"social",10.225652936631004,39,"SHARED2FREE",-0.20277182093145324,56358805 -171825,63802,"othdiscr",2,false,2,135,132,7044850,"home",,39,"SHARED3FREE",-0.36521793543833225,56358806 -171825,63802,"othdiscr",1,true,4,135,135,7044851,"othmaint",5.3795880164987455,26,"WALK",-0.7460586428642273,56358809 -171825,63802,"othdiscr",2,true,4,131,135,7044851,"othmaint",5.426687572911977,27,"WALK",-2.039843797683716,56358810 -171825,63802,"othdiscr",3,true,4,130,131,7044851,"othmaint",5.710506288696134,28,"WALK",-1.2828608453273775,56358811 -171825,63802,"othdiscr",4,true,4,130,130,7044851,"othdiscr",,28,"WALK",-0.7807589769363404,56358812 -171825,63802,"othdiscr",1,false,1,135,130,7044851,"home",,28,"WALK",-1.4660018980503084,56358813 -171825,63802,"school",1,true,1,135,135,7044856,"school",,9,"SHARED3FREE",0.10569338295856193,56358849 -171825,63802,"school",1,false,1,135,135,7044856,"home",,24,"SHARED3FREE",0.10569338295856193,56358853 -171826,63802,"school",1,true,1,135,135,7044897,"school",,10,"SHARED3FREE",0.10569338295856193,56359177 -171826,63802,"school",1,false,1,135,135,7044897,"home",,22,"SHARED3FREE",0.10569338295856193,56359181 diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv b/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv deleted file mode 100644 index 629cd63276..0000000000 --- a/activitysim/examples/prototype_arc/test/regress/final_trips_eet.csv +++ /dev/null @@ -1,69 +0,0 @@ -"person_id","household_id","primary_purpose","trip_num","outbound","trip_count","destination","origin","tour_id","purpose","destination_logsum","depart","trip_mode","mode_choice_logsum","trip_id" -116448,43843,"work",1,true,2,103,103,4774407,"othmaint",3.5265435605629167,10,"WALK",-0.9040200710296631,38195257 -116448,43843,"work",2,true,2,103,103,4774407,"work",,15,"WALK",-0.9040200710296631,38195258 -116448,43843,"work",1,false,2,103,103,4774407,"othmaint",3.537335211563138,25,"WALK",-0.9040200710296631,38195261 -116448,43843,"work",2,false,2,103,103,4774407,"home",,29,"WALK",-0.9040200710296631,38195262 -116449,43843,"work",1,true,1,119,103,4774448,"work",,10,"SHARED2FREE",-0.6957122303793248,38195585 -116449,43843,"work",1,false,1,103,119,4774448,"home",,32,"DRIVEALONEFREE",-0.7307911775419529,38195589 -116450,43843,"school",1,true,2,124,103,4774481,"escort",7.408917184315112,10,"SHARED3FREE",-0.18503856868335017,38195849 -116450,43843,"school",2,true,2,124,124,4774481,"school",,10,"SHARED2FREE",0.13215486408443475,38195850 -116450,43843,"school",1,false,1,103,124,4774481,"home",,26,"SHARED3FREE",-0.19368817121573698,38195853 -120774,45311,"atwork",1,true,2,107,111,4951738,"work",7.856506518743053,18,"SHARED2FREE",-0.3133887920340869,39613905 -120774,45311,"atwork",2,true,2,101,107,4951738,"atwork",,18,"SHARED2FREE",-0.4146300033565298,39613906 -120774,45311,"atwork",1,false,1,111,101,4951738,"work",,21,"SHARED2FREE",-0.47849444235549743,39613909 -120774,45311,"work",1,true,1,111,105,4951773,"work",,8,"SHARED3FREE",0.15709575819640942,39614185 -120774,45311,"work",1,false,1,105,111,4951773,"home",,29,"DRIVEALONEFREE",0.15635853653623902,39614189 -120775,45311,"shopping",1,true,1,101,105,4951808,"shopping",,19,"DRIVEALONEFREE",-0.5635545263290407,39614465 -120775,45311,"shopping",1,false,1,105,101,4951808,"home",,26,"DRIVEALONEFREE",-0.5825112173557282,39614469 -120775,45311,"work",1,true,1,122,105,4951814,"work",,11,"DRIVEALONEFREE",-0.6927164334297179,39614513 -120775,45311,"work",1,false,1,105,122,4951814,"home",,19,"DRIVEALONEFREE",-0.6686710088729859,39614517 -123132,46056,"atwork",1,true,2,101,101,5048416,"othdiscr",6.864256935993293,20,"SHARED2FREE",-0.1819504659032103,40387329 -123132,46056,"atwork",2,true,2,101,101,5048416,"atwork",,21,"SHARED2FREE",-0.1819504659032103,40387330 -123132,46056,"atwork",1,false,1,101,101,5048416,"work",,22,"SHARED2FREE",-0.1819504659032103,40387333 -123132,46056,"work",1,true,2,101,106,5048451,"social",9.575912097245201,11,"DRIVEALONEFREE",0.06386075688245799,40387609 -123132,46056,"work",2,true,2,101,101,5048451,"work",,11,"SHARED2FREE",0.07747391487776138,40387610 -123132,46056,"work",1,false,4,101,101,5048451,"othmaint",7.912927519210828,30,"DRIVEALONEFREE",0.08966218523375064,40387613 -123132,46056,"work",2,false,4,101,101,5048451,"othmaint",7.8924110313009,30,"SHARED2FREE",0.08966218523375064,40387614 -123132,46056,"work",3,false,4,101,101,5048451,"eatout",7.830041437016489,30,"SHARED2FREE",0.08966218523375064,40387615 -123132,46056,"work",4,false,4,106,101,5048451,"home",,35,"SHARED2FREE",0.06386075688245799,40387616 -136983,50912,"atwork",1,true,1,132,128,5616307,"atwork",,19,"DRIVEALONEFREE",-0.5008556391239165,44930457 -136983,50912,"atwork",1,false,1,128,132,5616307,"work",,21,"DRIVEALONEFREE",-0.5011769677639006,44930461 -136983,50912,"work",1,true,1,128,112,5616342,"work",,8,"DRIVEALONEFREE",-1.3108586753845215,44930737 -136983,50912,"work",1,false,4,104,128,5616342,"eatout",4.376898428790465,26,"DRIVEALONEFREE",-1.4459948724746707,44930741 -136983,50912,"work",2,false,4,114,104,5616342,"work",7.30416619728054,26,"DRIVEALONEFREE",-0.645403600692749,44930742 -136983,50912,"work",3,false,4,107,114,5616342,"eatout",5.801076923906916,26,"DRIVEALONEFREE",-0.7406740243911744,44930743 -136983,50912,"work",4,false,4,112,107,5616342,"home",,31,"DRIVEALONEFREE",-0.4455707152366638,44930744 -136984,50912,"atwork",1,true,1,100,104,5616344,"atwork",,19,"DRIVEALONEFREE",-0.5630687888145448,44930753 -136984,50912,"atwork",1,false,1,104,100,5616344,"work",,20,"DRIVEALONEFREE",-0.569428646659851,44930757 -136984,50912,"atwork",1,true,1,122,104,5616348,"atwork",,20,"DRIVEALONEFREE",-0.7160756511688232,44930785 -136984,50912,"atwork",1,false,1,104,122,5616348,"work",,21,"DRIVEALONEFREE",-0.7157543225288391,44930789 -136984,50912,"work",1,true,1,104,112,5616383,"work",,17,"DRIVEALONEFREE",-0.5361950384140016,44931065 -136984,50912,"work",1,false,1,112,104,5616383,"home",,32,"DRIVEALONEFREE",-0.5499035404682159,44931069 -145188,53716,"othdiscr",1,true,1,121,116,5952733,"othdiscr",,26,"WALK",-1.9421684741973877,47621865 -145188,53716,"othdiscr",1,false,1,116,121,5952733,"home",,39,"WALK",-1.9421684741973877,47621869 -145188,53716,"shopping",1,true,2,104,116,5952741,"escort",6.936564611277708,24,"DRIVEALONEFREE",-0.35927561314773726,47621929 -145188,53716,"shopping",2,true,2,122,104,5952741,"shopping",,24,"DRIVEALONEFREE",-0.2772136905975358,47621930 -145188,53716,"shopping",1,false,1,116,122,5952741,"home",,25,"DRIVEALONEFREE",-0.18333346470683046,47621933 -147129,54342,"atwork",1,true,2,118,118,6032293,"work",8.812717247468496,24,"SHARED3FREE",-0.145853673602041,48258345 -147129,54342,"atwork",2,true,2,118,118,6032293,"atwork",,25,"WALK",-0.14614682859354086,48258346 -147129,54342,"atwork",1,false,1,118,118,6032293,"work",,27,"SHARED3FREE",-0.14614682859354086,48258349 -147129,54342,"work",1,true,1,118,117,6032328,"work",,24,"DRIVEALONEFREE",0.20514075467216628,48258625 -147129,54342,"work",1,false,2,121,118,6032328,"othmaint",7.591255086332629,45,"DRIVEALONEFREE",-0.05268563765149335,48258629 -147129,54342,"work",2,false,2,117,121,6032328,"home",,46,"SHARED3FREE",-0.02237034338193105,48258630 -168909,62701,"othmaint",1,true,1,131,131,6925297,"othmaint",,25,"WALK",-0.5552063584327697,55402377 -168909,62701,"othmaint",1,false,1,131,131,6925297,"home",,28,"WALK",-0.5552063584327697,55402381 -171822,63802,"shopping",1,true,1,130,135,7044721,"shopping",,29,"SHARED3FREE",-0.3938452305023584,56357769 -171822,63802,"shopping",1,false,3,135,130,7044721,"othmaint",5.98668616675203,30,"SHARED3FREE",-0.39446175334787803,56357773 -171822,63802,"shopping",2,false,3,135,135,7044721,"othmaint",6.756477914457603,30,"SHARED3FREE",-0.2052835117202649,56357774 -171822,63802,"shopping",3,false,3,135,135,7044721,"home",,31,"SHARED3FREE",-0.2052835117202649,56357775 -171823,63802,"escort",1,true,1,135,135,7044752,"escort",,27,"SHARED2FREE",0.19489390061694092,56358017 -171823,63802,"escort",1,false,2,128,135,7044752,"othmaint",8.083601093614876,27,"DRIVEALONEFREE",-0.1812426477593491,56358021 -171823,63802,"escort",2,false,2,135,128,7044752,"home",,27,"DRIVEALONEFREE",-0.18540377166841351,56358022 -171823,63802,"escort",1,true,1,135,135,7044753,"escort",,10,"DRIVEALONEFREE",-0.27614978880882257,56358025 -171823,63802,"escort",1,false,3,135,135,7044753,"escort",6.748884636203162,10,"DRIVEALONEFREE",-0.27614978880882257,56358029 -171823,63802,"escort",2,false,3,135,135,7044753,"eatout",6.425456017109895,10,"DRIVEALONEFREE",-0.27614978880882257,56358030 -171823,63802,"escort",3,false,3,135,135,7044753,"home",,11,"DRIVEALONEFREE",-0.27614978880882257,56358031 -171824,63802,"school",1,true,1,135,135,7044815,"school",,10,"SCHOOL_BUS",4.381191198921203,56358521 -171824,63802,"school",1,false,1,135,135,7044815,"home",,25,"SCHOOL_BUS",4.381191198921203,56358525 -171826,63802,"school",1,true,1,135,135,7044897,"school",,8,"SCHOOL_BUS",4.381191198921203,56359177 -171826,63802,"school",1,false,1,135,135,7044897,"home",,24,"SCHOOL_BUS",4.381191198921203,56359181 diff --git a/activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv b/activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv deleted file mode 100644 index 0bd93ac3e8..0000000000 --- a/activitysim/examples/prototype_arc/test/regress/final_trips_sh.csv +++ /dev/null @@ -1,91 +0,0 @@ -trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,destination,origin,tour_id,purpose,destination_logsum,depart,trip_mode,mode_choice_logsum -37314161,113762,42730,othmaint,1,TRUE,1,106,103,4664270,othmaint,,10,DRIVEALONEFREE,-0.356781572 -37314165,113762,42730,othmaint,1,FALSE,1,103,106,4664270,home,,11,DRIVEALONEFREE,-0.356460303 -38194977,116448,43843,atwork,1,TRUE,1,106,101,4774372,atwork,,20,DRIVEALONEFREE,-0.321751714 -38194981,116448,43843,atwork,1,FALSE,1,101,106,4774372,work,,21,DRIVEALONEFREE,-0.321751714 -38195065,116449,43843,othdiscr,1,TRUE,1,106,103,4774383,othdiscr,,32,SHARED2FREE,0.7593915111282218 -38195069,116449,43843,othdiscr,1,FALSE,1,103,106,4774383,home,,37,SHARED2FREE,0.7593915111282218 -38195257,116448,43843,work,1,TRUE,2,107,103,4774407,othmaint,9.244319,10,DRIVEALONEFREE,-0.667137086 -38195258,116448,43843,work,2,TRUE,2,101,107,4774407,work,,10,DRIVEALONEFREE,-0.589384019 -38195261,116448,43843,work,1,FALSE,1,103,101,4774407,home,,30,DRIVEALONEFREE,-0.501271665 -38195585,116449,43843,work,1,TRUE,2,106,103,4774448,othmaint,10.644734,12,DRIVEALONEFREE,0.050863062 -38195586,116449,43843,work,2,TRUE,2,102,106,4774448,work,,13,DRIVEALONEFREE,0.032545052 -38195589,116449,43843,work,1,FALSE,3,103,102,4774448,othmaint,10.796497,23,SHARED2FREE,0.09835211 -38195590,116449,43843,work,2,FALSE,3,103,103,4774448,work,12.367123,24,DRIVEALONEFREE,0.24223826711784288 -38195591,116449,43843,work,3,FALSE,3,103,103,4774448,home,,26,DRIVEALONEFREE,0.2401515927071465 -38195849,116450,43843,school,1,TRUE,1,106,103,4774481,school,,9,SCHOOL_BUS,4.351044654846191 -38195853,116450,43843,school,1,FALSE,1,103,106,4774481,home,,27,SCHOOL_BUS,4.351044654846191 -38195865,116450,43843,shopping,1,TRUE,1,101,103,4774483,shopping,,27,SHARED2FREE,-0.444101901 -38195869,116450,43843,shopping,1,FALSE,1,103,101,4774483,home,,30,SHARED2FREE,-0.457490895 -39613905,120774,45311,atwork,1,TRUE,1,101,102,4951738,atwork,,20,DRIVEALONEFREE,-0.411282897 -39613909,120774,45311,atwork,1,FALSE,1,102,101,4951738,work,,21,DRIVEALONEFREE,-0.411925524 -39614185,120774,45311,work,1,TRUE,2,106,105,4951773,work,10.647319,10,DRIVEALONEFREE,-0.432832956 -39614186,120774,45311,work,2,TRUE,2,102,106,4951773,work,,11,DRIVEALONEFREE,-0.348032892 -39614189,120774,45311,work,1,FALSE,1,105,102,4951773,home,,30,DRIVEALONEFREE,-0.604685664 -39614513,120775,45311,work,1,TRUE,1,101,105,4951814,work,,9,DRIVEALONEFREE,-0.600943565 -39614517,120775,45311,work,1,FALSE,3,101,101,4951814,work,10.767546,28,DRIVEALONEFREE,-0.356709987 -39614518,120775,45311,work,2,FALSE,3,107,101,4951814,othmaint,9.370711,28,DRIVEALONEFREE,-0.595682502 -39614519,120775,45311,work,3,FALSE,3,105,107,4951814,home,,29,DRIVEALONEFREE,-0.43356654 -40387937,123133,46056,work,1,TRUE,1,106,106,5048492,work,,20,DRIVEALONEFREE,-0.19777289 -40387941,123133,46056,work,1,FALSE,1,106,106,5048492,home,,40,DRIVEALONEFREE,-0.197402388 -43308361,132037,49258,othmaint,1,TRUE,1,122,110,5413545,othmaint,,23,DRIVEALONEFREE,-0.739005089 -43308365,132037,49258,othmaint,1,FALSE,2,114,122,5413545,eatout,8.7858,24,DRIVEALONEFREE,-0.517582178 -43308366,132037,49258,othmaint,2,FALSE,2,110,114,5413545,home,,24,DRIVEALONEFREE,-0.593839884 -43308537,132038,49258,escort,1,TRUE,1,107,110,5413567,escort,,10,SHARED3FREE,-0.00260142 -43308541,132038,49258,escort,1,FALSE,1,110,107,5413567,home,,22,SHARED3FREE,-0.00260142 -44930737,136983,50912,work,1,TRUE,2,123,112,5616342,eatout,9.353397,31,DRIVEALONEFREE,-0.549332976 -44930738,136983,50912,work,2,TRUE,2,104,123,5616342,work,,32,DRIVEALONEFREE,-0.666611016 -44930741,136983,50912,work,1,FALSE,2,112,104,5616342,social,11.149774,34,DRIVEALONEFREE,-0.530267 -44930742,136983,50912,work,2,FALSE,2,112,112,5616342,home,,34,DRIVEALONEFREE,-0.183315992 -44931065,136984,50912,work,1,TRUE,2,101,112,5616383,shopping,9.520916,11,DRIVEALONEFREE,-0.612947822 -44931066,136984,50912,work,2,TRUE,2,107,101,5616383,work,,12,DRIVEALONEFREE,-0.619394481 -44931069,136984,50912,work,1,FALSE,3,123,107,5616383,work,10.775923,28,DRIVEALONEFREE,-0.765127003 -44931070,136984,50912,work,2,FALSE,3,104,123,5616383,escort,9.519634,29,DRIVEALONEFREE,-0.666611016 -44931071,136984,50912,work,3,FALSE,3,112,104,5616383,home,,30,DRIVEALONEFREE,-0.549903512 -47621473,145187,53716,othmaint,1,TRUE,3,121,116,5952684,social,9.947862,8,SHARED3FREE,-0.419557299 -47621474,145187,53716,othmaint,2,TRUE,3,112,121,5952684,othmaint,9.261029,11,SHARED3FREE,-0.642273009 -47621475,145187,53716,othmaint,3,TRUE,3,122,112,5952684,othmaint,,11,SHARED3FREE,-0.641908288 -47621477,145187,53716,othmaint,1,FALSE,1,116,122,5952684,home,,20,SHARED3FREE,-0.613462986 -47621737,145188,53716,escort,1,TRUE,1,114,116,5952717,escort,,29,DRIVEALONEFREE,-0.150837969 -47621741,145188,53716,escort,1,FALSE,1,116,114,5952717,home,,30,SHARED2FREE,-0.151798129 -47622241,145189,53716,school,1,TRUE,1,114,116,5952780,school,,10,SCHOOL_BUS,4.3079237937927255 -47622245,145189,53716,school,1,FALSE,1,116,114,5952780,home,,24,SCHOOL_BUS,4.3079237937927255 -47622569,145190,53716,school,1,TRUE,1,114,116,5952821,school,,9,SHARED2FREE,-0.206179043 -47622573,145190,53716,school,1,FALSE,1,116,114,5952821,home,,24,SHARED2FREE,-0.205685001 -48258513,147129,54342,othdiscr,1,TRUE,1,116,117,6032314,othdiscr,,27,DRIVEALONEFREE,-0.524616778 -48258517,147129,54342,othdiscr,1,FALSE,1,117,116,6032314,home,,33,DRIVEALONEFREE,-0.49120158 -48258537,147129,54342,othmaint,1,TRUE,1,114,117,6032317,othmaint,,34,DRIVEALONEFREE,-0.68713218 -48258541,147129,54342,othmaint,1,FALSE,2,114,114,6032317,shopping,9.148774,37,DRIVEALONEFREE,-0.423736095 -48258542,147129,54342,othmaint,2,FALSE,2,117,114,6032317,home,,38,DRIVEALONEFREE,-0.684561789 -56357665,171822,63802,eatout,1,TRUE,1,127,135,7044708,eatout,,31,DRIVEALONEFREE,-0.652624547 -56357669,171822,63802,eatout,1,FALSE,1,135,127,7044708,home,,34,DRIVEALONEFREE,-0.634310424 -56357689,171822,63802,escort,1,TRUE,1,135,135,7044711,escort,,28,SHARED3FREE,0.077063243 -56357693,171822,63802,escort,1,FALSE,2,135,135,7044711,escort,11.356267,28,SHARED3FREE,0.077063243 -56357694,171822,63802,escort,2,FALSE,2,135,135,7044711,home,,28,SHARED3FREE,0.077063243 -56357737,171822,63802,othdiscr,1,TRUE,3,131,135,7044717,othdiscr,12.194779,13,SHARED2FREE,0.599977655 -56357738,171822,63802,othdiscr,2,TRUE,3,130,131,7044717,shopping,13.357507,14,SHARED2FREE,0.6200047250329787 -56357739,171822,63802,othdiscr,3,TRUE,3,130,130,7044717,othdiscr,,14,SHARED2FREE,0.6960546579187884 -56357741,171822,63802,othdiscr,1,FALSE,1,135,130,7044717,home,,14,SHARED2FREE,0.6487159186367744 -56358209,171823,63802,shopping,1,TRUE,4,131,135,7044776,othmaint,10.342613,24,SHARED3FREE,-0.146198375 -56358210,171823,63802,shopping,2,TRUE,4,131,131,7044776,social,12.281772,25,SHARED3FREE,-0.012169709 -56358211,171823,63802,shopping,3,TRUE,4,131,131,7044776,shopping,11.556939,26,SHARED3FREE,-0.012169709 -56358212,171823,63802,shopping,4,TRUE,4,131,131,7044776,shopping,,26,SHARED3FREE,-0.012169709 -56358213,171823,63802,shopping,1,FALSE,1,135,131,7044776,home,,27,DRIVEALONEFREE,-0.150955944 -56358473,171824,63802,othdiscr,1,TRUE,1,131,135,7044809,othdiscr,,32,SHARED2FREE,-0.460241494 -56358477,171824,63802,othdiscr,1,FALSE,1,135,131,7044809,home,,37,SHARED2FREE,-0.453292992 -56358521,171824,63802,school,1,TRUE,2,135,135,7044815,escort,11.635028,10,SHARED2FREE,0.10569338088788001 -56358522,171824,63802,school,2,TRUE,2,135,135,7044815,school,,10,SHARED3FREE,0.10569338088788001 -56358525,171824,63802,school,1,FALSE,2,135,135,7044815,othdiscr,11.906311,25,SHARED3FREE,0.10545807803885715 -56358526,171824,63802,school,2,FALSE,2,135,135,7044815,home,,26,SHARED3FREE,0.10545807803885715 -56358801,171825,63802,othdiscr,1,TRUE,1,131,135,7044850,othdiscr,,29,SHARED3FREE,-0.281769477 -56358805,171825,63802,othdiscr,1,FALSE,2,132,131,7044850,social,10.225653,35,SHARED2FREE,-0.202771856 -56358806,171825,63802,othdiscr,2,FALSE,2,135,132,7044850,home,,39,SHARED3FREE,-0.365217932 -56358809,171825,63802,othdiscr,1,TRUE,4,135,135,7044851,othmaint,5.3795877,26,WALK,-0.746058583 -56358810,171825,63802,othdiscr,2,TRUE,4,131,135,7044851,othmaint,5.4266872,27,WALK,-2.039843559 -56358811,171825,63802,othdiscr,3,TRUE,4,130,131,7044851,othmaint,5.7105064,28,WALK,-1.282860875 -56358812,171825,63802,othdiscr,4,TRUE,4,130,130,7044851,othdiscr,,28,WALK,-0.780758977 -56358813,171825,63802,othdiscr,1,FALSE,1,135,130,7044851,home,,28,WALK,-1.466001987 -56358849,171825,63802,school,1,TRUE,1,135,135,7044856,school,,9,SHARED3FREE,0.10569338088788001 -56358853,171825,63802,school,1,FALSE,1,135,135,7044856,home,,24,SHARED3FREE,0.10569338088788001 -56359177,171826,63802,school,1,TRUE,1,135,135,7044897,school,,10,SHARED3FREE,0.10569338088788001 -56359181,171826,63802,school,1,FALSE,1,135,135,7044897,home,,22,SHARED3FREE,0.10569338088788001 diff --git a/activitysim/examples/prototype_arc/test/simulation.py b/activitysim/examples/prototype_arc/test/simulation.py deleted file mode 100644 index 70cf3457fd..0000000000 --- a/activitysim/examples/prototype_arc/test/simulation.py +++ /dev/null @@ -1,16 +0,0 @@ -# ActivitySim -# See full license in LICENSE.txt. - -from __future__ import annotations - -import argparse -import sys - -from activitysim.cli.run import add_run_args, run - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - add_run_args(parser) - args = parser.parse_args() - - sys.exit(run(args)) diff --git a/activitysim/examples/prototype_arc/test/test_arc.py b/activitysim/examples/prototype_arc/test/test_arc.py deleted file mode 100644 index 140c67a313..0000000000 --- a/activitysim/examples/prototype_arc/test/test_arc.py +++ /dev/null @@ -1,93 +0,0 @@ -from __future__ import annotations - -# ActivitySim -# See full license in LICENSE.txt. -import importlib.resources -import os -import subprocess -import sys - -import pandas as pd -import pandas.testing as pdt - -from activitysim.core.test import assert_frame_substantively_equal - - -def _test_arc(recode=False, sharrow=False, eet=False): - def example_path(dirname): - resource = os.path.join("examples", "prototype_arc", dirname) - return str(importlib.resources.files("activitysim").joinpath(resource)) - - def test_path(dirname): - return os.path.join(os.path.dirname(__file__), dirname) - - def regress(): - if sharrow: - # sharrow results in tiny changes (one trip moving one time period earlier) - regress_trips_df = pd.read_csv( - test_path(f"regress/final_trips{'_eet' if eet else ''}_sh.csv") - ) - else: - regress_trips_df = pd.read_csv( - test_path(f"regress/final_trips{'_eet' if eet else ''}.csv") - ) - final_trips_df = pd.read_csv(test_path("output/final_trips.csv")) - - # person_id,household_id,tour_id,primary_purpose,trip_num,outbound,trip_count,purpose, - # destination,origin,destination_logsum,depart,trip_mode,mode_choice_logsum - # compare_cols = [] - assert_frame_substantively_equal(final_trips_df, regress_trips_df) - - file_path = os.path.join(os.path.dirname(__file__), "simulation.py") - - test_configs = [] - if eet: - test_configs.extend(["-c", test_path("configs_eet")]) - - if recode: - test_configs.extend(["-c", test_path("configs_recode")]) - elif sharrow: - test_configs.extend(["-c", test_path("configs_sharrow")]) - else: - test_configs.extend(["-c", test_path("configs")]) - - run_args = [ - *test_configs, - "-c", - example_path("configs"), - "-d", - example_path("data"), - "-o", - test_path("output"), - ] - - if os.environ.get("GITHUB_ACTIONS") == "true": - subprocess.run(["coverage", "run", "-a", file_path] + run_args, check=True) - else: - subprocess.run([sys.executable, file_path] + run_args, check=True) - - regress() - - -def test_arc(): - _test_arc() - - -def test_arc_eet(): - _test_arc(eet=True) - - -def test_arc_recode(): - _test_arc(recode=True) - - -# TODO: update regress trips for sharrow and re-enable test. -# def test_arc_sharrow(): -# _test_arc(sharrow=True) - - -if __name__ == "__main__": - _test_arc() - _test_arc(eet=True) - _test_arc(recode=True) - # _test_arc(sharrow=True) From d439c7f9698da9972f5c552dc3b79a44428e1f21 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 27 Jun 2026 18:59:05 +1000 Subject: [PATCH 297/298] adding skip_failed_choices into test, removing dead function parameter --- activitysim/core/logit.py | 18 +++++++----------- .../core/test/test_interaction_sample.py | 3 +++ activitysim/core/test/test_logit.py | 13 +++++++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/activitysim/core/logit.py b/activitysim/core/logit.py index 86f5ca22a3..78f1ca114d 100644 --- a/activitysim/core/logit.py +++ b/activitysim/core/logit.py @@ -256,6 +256,7 @@ def validate_utils( state, zero_probs, utils, + state.settings.skip_failed_choices, trace_label=tracing.extend_trace_label(trace_label, "zero_prob_utils"), msg="all probabilities are zero", trace_choosers=trace_choosers, @@ -274,7 +275,6 @@ def utils_to_probs( allow_zero_probs=False, trace_choosers=None, overflow_protection: bool = True, - skip_failed_choices: bool = True, return_logsums: bool = False, ): """ @@ -315,15 +315,12 @@ def utils_to_probs( overflow_protection will have no benefit but impose a modest computational overhead cost. - skip_failed_choices : bool, default True - If True, when bad choices are detected (all zero probabilities or infinite - probabilities), the entire household that's causing bad choices will be skipped instead of - being masked by overflow protection or causing an error. - A counter will be incremented for each skipped household. This is useful when running large - simulations where occasional bad choices are encountered and should not halt the process. - The counter can be accessed via `state.get("num_skipped_households", 0)`. - The number of skipped households and their IDs will be logged at the end of the simulation. - When `skip_failed_choices` is True, `overflow_protection` will be forced to False to avoid conflicts. + skip_failed_choices behavior + Bad-choice handling is controlled by `state.settings.skip_failed_choices`. + If enabled, choosers with bad probabilities (all zero or infinite + exponentiated utilities) are skipped instead of raising immediately. + The number of skipped households and their IDs are tracked on `state`, and + `overflow_protection` is forced off so those failures are not masked. Returns ------- @@ -352,7 +349,6 @@ def utils_to_probs( utils_arr.dtype == np.float32 and utils_arr.max() > 85 ) - # get skip_failed_choices from state skip_failed_choices = state.settings.skip_failed_choices # when skipping failed choices, we cannot use overflow protection # because it would mask the underlying issue causing bad choices diff --git a/activitysim/core/test/test_interaction_sample.py b/activitysim/core/test/test_interaction_sample.py index 9fd6b267ae..a25ebc5f32 100644 --- a/activitysim/core/test/test_interaction_sample.py +++ b/activitysim/core/test/test_interaction_sample.py @@ -3,6 +3,8 @@ from __future__ import annotations +from types import SimpleNamespace + import numpy as np import pandas as pd import pytest @@ -602,6 +604,7 @@ def log_df(self, *_args, **_kwargs): class _DummyState: def __init__(self, rng): self._rng = rng + self.settings = SimpleNamespace(skip_failed_choices=False) def get_rn_generator(self): return self._rng diff --git a/activitysim/core/test/test_logit.py b/activitysim/core/test/test_logit.py index f1746fc3eb..e8a5b51439 100644 --- a/activitysim/core/test/test_logit.py +++ b/activitysim/core/test/test_logit.py @@ -3,6 +3,7 @@ from __future__ import annotations import os.path +from types import SimpleNamespace import numpy as np import pandas as pd @@ -99,6 +100,7 @@ def test_validate_utils_replaces_unavailable_values(): def test_validate_utils_raises_when_all_unavailable(): state = workflow.State().default_settings() + state.settings.skip_failed_choices = False utils = pd.DataFrame([[logit.UTIL_MIN - 1.0, logit.UTIL_MIN - 2.0]]) with pytest.raises(InvalidTravelError) as excinfo: @@ -122,6 +124,7 @@ def test_validate_utils_allows_zero_probs(): # def test_utils_to_probs_logsums_with_overflow_protection(): state = workflow.State().default_settings() + state.settings.skip_failed_choices = False utils = pd.DataFrame( [[1000.0, 1001.0, 999.0], [-1000.0, -1001.0, -999.0]], columns=["a", "b", "c"], @@ -310,6 +313,8 @@ def random_for_df(self, df, n=1): return np.array([[0.05], [0.6], [0.95]]) class DummyState: + settings = SimpleNamespace(skip_failed_choices=False) + @staticmethod def get_rn_generator(): return DummyRNG() @@ -610,6 +615,8 @@ def random_for_df(self, df, n=1): return mc_rng.random((len(df), n)) class MCDummyState: + settings = SimpleNamespace(skip_failed_choices=False) + @staticmethod def get_rn_generator(): return MCDummyRNG() @@ -641,6 +648,8 @@ def gumbel_choice_positions_for_df( ) class EETDummyState: + settings = SimpleNamespace(skip_failed_choices=False) + @staticmethod def get_rn_generator(): return EETDummyRNG() @@ -690,6 +699,8 @@ def random_for_df(self, df, n=1): return mc_rng.random((len(df), n)) class MCDummyState: + settings = SimpleNamespace(skip_failed_choices=False) + @staticmethod def get_rn_generator(): return MCDummyRNG() @@ -718,6 +729,8 @@ def gumbel_for_df(self, df, n): return eet_rng.gumbel(size=(len(df), n)) class EETDummyState: + settings = SimpleNamespace(skip_failed_choices=False) + @staticmethod def get_rn_generator(): return EETDummyRNG() From c93d307b359558dd0e81e58fad6023a2e8a0fb77 Mon Sep 17 00:00:00 2001 From: Jan Zill Date: Sat, 27 Jun 2026 19:31:11 +1000 Subject: [PATCH 298/298] fix eet trip_dest presample test --- .../abm/test/test_misc/test_trip_destination_sampling.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activitysim/abm/test/test_misc/test_trip_destination_sampling.py b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py index e8aae5164d..62bc4492b5 100644 --- a/activitysim/abm/test/test_misc/test_trip_destination_sampling.py +++ b/activitysim/abm/test/test_misc/test_trip_destination_sampling.py @@ -278,6 +278,10 @@ def fake_choose_maz_for_taz( state = workflow.State().default_settings() state.settings.use_explicit_error_terms = True + state.add_table( + "land_use_taz", + pd.DataFrame(index=pd.Index([1, 2, 3], name="zone_id")), + ) trips = pd.DataFrame( {"origin": [101], "tour_leg_dest": [103]}, index=pd.Index([7001], name="trip_id"),