Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 81 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,39 @@
Simulate Oregon (SimOR) - Oregon's Jointly Estimated ActivitySim Model
![image](SimOR.png)

> [!WARNING]
> This model is under active development, and the following instructions are subject to change.

Please see this repo's Wiki page for more detailed information on this model.

## Repository structure

The repository is organized into the following major components:

| Path | Purpose |
|------|---------|
| `runSIMOR.bat` | Top-level batch script that runs environment setup, skimming, preprocessing, and the current ActivitySim example run |
| `setup_environment.bat` | Installs UV if needed, clones and updates external dependencies, creates Python environments, and installs required Visum Python packages |
| `ext_dependencies/` | Cloned external repositories and their virtual environments, including ActivitySim, maz_skimming, and optionally sandag_parking |
| `resident/` | ActivitySim model code, configurations, test cases, model input data, and outputs |
| `resident/configs/` | Shared ActivitySim settings used across regions |
| `resident/configs_*` | Region-specific ActivitySim settings, constants, and overrides such as `configs_skats`; additional region folders such as `configs_metro` or `configs_lcog` may be added over time |
| `resident/model_data/` | Region-specific model inputs, including cropped example datasets and full datasets |
| `skimming_and_assignment/visum/` | Visum runner scripts, procedure sequences, and version files used to build motorized skims and export network inputs |
| `skimming_and_assignment/maz_maz_stop_skims/` | Non-motorized skim preprocessor and skim settings |
| `misc/` | Miscellaneous analysis and support scripts |

## Running the model

### Prerequisites

Before running the model, make sure the following are available:

1. PTV Visum 2026 with its bundled Python installation
2. Git available on your system PATH or installed in a standard Windows location
3. PowerShell with permission to run the UV installer invoked by `setup_environment.bat`
4. Local write access to the Visum Python `site-packages` directory, or Administrator privileges if package installation there is restricted

### Setup

1. **Configure `setup_environment.bat`**
Expand All @@ -12,27 +43,38 @@ Simulate Oregon (SimOR) - Oregon's Jointly Estimated ActivitySim Model

| Variable | Description | Default |
|----------|-------------|---------|
| `VISUM_PYTHON_DIR` | Folder containing your Visum 2026 Python interpreter | `C:\Program Files\PTV Vision\PTV Visum 2026\Exe\Junction_Preview\Python` |
| `INSTALL_PARKING` | Clone and install sandag_parking (`Y` or `N`) | `Y` |
| `VISUM_PYTHON_DIR` | Folder containing your Visum 2026 Python interpreter | `C:\Program Files\PTV Vision\PTV Visum 2026\Exe\Python` |
| `INSTALL_PARKING` | Clone and install sandag_parking (`Y` or `N`) | `N` |

You can run this script on its own to install all dependencies without running the model:
```
setup_environment.bat
```
It will install UV (if needed), clone and build the required repositories into `ext_dependencies/`, create the MAZ skimming Python environment from `ext_dependencies/maz_skimming/pyproject.toml`, and install the necessary Python packages into Visum's Python environment. On subsequent runs it detects existing installs and pulls the latest changes instead of re-cloning.
It will install UV (if needed), verify Git is available, clone and build the required repositories into `ext_dependencies/`, create the ActivitySim and MAZ skimming Python environments, and install the necessary Python packages into Visum's Python environment. On subsequent runs it detects existing installs and pulls the latest changes instead of re-cloning.

`sandag_parking` is not required for a typical model run. It is only needed when preparing land use inputs that require expected parking costs. In that workflow, it can be run once to generate the parking cost input file used by the preprocessor.

If permissions errors arise, try running the setup with Administrator privileges. The Visum Python package install in particular may require elevated permissions.

2. **Place the Visum version file**

Copy your Visum network version file (`.ver`) into `skimming_and_assignment/visum/`.

3. **Update configuration files**

Edit the following files to match your local data paths and project settings:
Edit the following files to match your local data paths, region, and project settings:

| File | Purpose |
|------|---------|
| `skimming_and_assignment/maz_maz_stop_skims/2zoneSkim_params.yaml` | Non-motorized skim settings and file paths |
| `resident/preprocessor_settings.yaml` | Land use preprocessor input/output paths and network settings |
| `skimming_and_assignment/maz_maz_stop_skims/2zoneSkim_params.yaml` | Non-motorized skim settings, Visum export locations, network inputs, and skim outputs |
| `resident/preprocessor_settings.yaml` | Land use preprocessor input/output paths, network shapefiles, skim inputs, and optional parking or fare inputs |

At minimum, confirm that these files point to the correct local locations for:

1. Household, person, and land use CSV inputs
2. MAZ, node, and link shapefiles
3. Non-motorized skim inputs and outputs
4. Optional expected parking cost and transit fare inputs

4. **Set user-defined variables in `runSIMOR.bat`**

Expand All @@ -42,6 +84,22 @@ Simulate Oregon (SimOR) - Oregon's Jointly Estimated ActivitySim Model
|----------|-------------|
| `VISUM_VERSION_FILE` | Filename of the Visum version file |
| `PROCEDURE_SEQ` | Path to the Visum procedure sequence XML |
| `VISUM_PED_VERSION_FILE` | Optional pedestrian network Visum version file used when a separate pedestrian export is needed |
| `PED_PROCEDURE_SEQ` | Optional Visum procedure sequence for the pedestrian network export |

### ActivitySim configuration layout

ActivitySim settings are split into a shared configuration folder and optional region-specific configuration folders.

- `resident/configs/` contains shared settings used across model regions.
- Region-specific folders such as `resident/configs_skats/`, and future folders such as `resident/configs_metro/` or `resident/configs_lcog/`, contain region-specific constants and overrides.
- When running a region-specific model, pass the region-specific config directory first and then the shared config directory so region overrides are applied before shared settings.

For example:

```bat
python resident\simulation.py -c resident\configs_skats -c resident\configs -d resident\model_data\skats\data_cropped -o resident\outputs\test
```

### Running the Pipeline

Expand All @@ -52,8 +110,23 @@ runSIMOR.bat

The script automatically calls `setup_environment.bat` to ensure all dependencies are installed and Python paths are set, then runs the following steps in sequence:

1. **Motorized skims** — Using Visum `Visum_Runner.py`. Automatically outputs required files to run non-motorized skims.
1. **Motorized skims** — Using Visum `Visum_Runner.py`. Automatically outputs required files to run non-motorized skims.
2. **Non-motorized skim preprocessor** — Prepares walk network inputs via `2zoneSkim_preprocessor.py`.
3. **Non-motorized skims** — Computes MAZ-to-MAZ and MAZ-to-stop walk skims via `2zoneSkim.py`.
4. **Land use preprocessor** — Builds ActivitySim-ready land use table via `preprocessor.py`.
5. **Run ActivitySim** -- Runs ActivitySim -- (not yet implemented)
5. **Run ActivitySim** — Runs the current ActivitySim example configured in `runSIMOR.bat`.

At present, the ActivitySim command in `runSIMOR.bat` runs the Metro cropped example dataset:

```bat
python resident\simulation.py -c resident\configs -d resident\model_data\metro\data_cropped -o outputs\cropped
```

This is a smoke-test style example run, not yet a generalized full-region launcher for all supported scenarios.

### Expected inputs and outputs

- Visum produces the motorized skims and exported network files consumed by the non-motorized skim workflow.
- The non-motorized skim tools produce files such as `maz_maz_walk.csv` and `maz_stop_walk.csv` for use by the land use preprocessor and ActivitySim.
- The land use preprocessor reads the configured raw model inputs and writes updated ActivitySim-ready tables to the configured output directory.
- ActivitySim writes model outputs to the output folder passed on the command line.
2 changes: 1 addition & 1 deletion resident/configs/park_and_ride_lot_choice.csv
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ util_ivtt,in vehcile transit time,@(ldt_skims['WTW_TIV'] + dlt_skims['WTW_TIV'])
util_xfer_penalty,transfer penalty,"@(-23+23*np.exp(0.414*np.clip(ldt_skims['WTW_XFR'], a_min=None,a_max=4))) + (-23+23*np.exp(0.414*np.clip(dlt_skims['WTW_XFR'], a_min=None,a_max=4)))",coef_xfer_work
util_stop_type_constant,stop type constant,@(ldt_skims['WTW_RST'] + dlt_skims['WTW_RST']),coef_ivt_work
util_vehicle_type_constant,vehicle type constant,@(ldt_skims['WTW_VTC'] + dlt_skims['WTW_VTC']),coef_ivt_work
util_fare,transit fare,"@df.transitSubsidyPassDiscount * (ldt_skims['fare'] + dlt_skims['fare']) * 100*df.num_participants/df.cost_sensitivity",coef_income_work
util_fare,transit fare,"@df.transitSubsidyPassDiscount * df.get('TRANSIT_FF', 1) * (ldt_skims['fare'] + dlt_skims['fare']) * 100*df.num_participants/df.cost_sensitivity",coef_income_work
util_small_lot,Small lot penalty -- 10 min penalty,"@np.where(df['PNR_SPACES'] < 100,10,0)",coef_ivt_work
#util_med_lot,med lot penalty -- no penalty,"@np.where((df['PNR_SPACES'] >= 100) & (df['PNR_SPACES'] < 500),1,0) * 0",coef_ivt_work
util_large_lot,large lot penalty -- 10 min back,"@np.where(df['PNR_SPACES'] >= 500,-10,0)",coef_ivt_work
Expand Down
8 changes: 4 additions & 4 deletions resident/configs/tour_mode_choice.csv
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ util_WALK_Walk_egress_time,WALK_LOC - egress time,"@np.where(df.nev_local_egress
util_WALK_wait_egress_time,WALK_LOC - Egress MT/NEV wait time,"@np.where(df.nev_local_egress_available, 2*nevWaitTime, np.where(df.microtransit_local_egress_available, 2*microtransitWaitTime, 0)) * df.time_factor",,,,,,coef_wait,,,,,,,,,
util_WALK_stop_type_constant,WALK_LOC - Stop type constant,@(odt_skims['WTW_RST'] + dot_skims['WTW_RST'])* df.time_factor,,,,,,coef_ivt,,,,,,,,,
util_WALK_vehicle_type_constant,WALK_LOC - Vehicle type constant,@(odt_skims['WTW_VTC'] + dot_skims['WTW_VTC'])* df.time_factor,,,,,,coef_ivt,,,,,,,,,
util_WTW_FARE,WALK_LOC - Fare,@df.transitSubsidyPassDiscount*(odt_skims['fare'] + dot_skims['fare'])*100*df.num_participants/df.cost_sensitivity,,,,,,coef_income,,,,,,,,,
util_WTW_FARE,WALK_LOC - Fare,"@df.transitSubsidyPassDiscount*df.get('TRANSIT_FF', 1)*(odt_skims['fare'] + dot_skims['fare'])*100*df.num_participants/df.cost_sensitivity",,,,,,coef_income,,,,,,,,,
util_WALK_LOC_Age 16 to 24,WALK_LOC - Age 16 to 24,@(df.age > 15) & (df.age < 25),,,,,,coef_age1624_tran,,,,,,,,,
util_WALK_LOC_Age 41 to 55,WALK_LOC - Age 41 to 55,@(df.age > 40) & (df.age < 56),,,,,,coef_age4155_tran,,,,,,,,,
util_WALK_LOC_Age 56 to 64,WALK_LOC - Age 56 to 64,@(df.age > 55) & (df.age < 65),,,,,,coef_age5664_tran,,,,,,,,,
Expand All @@ -95,7 +95,7 @@ util_PNR_Walk_other_time,PNR_LOC - Walk other time,@df.xfer_walk_pnr * df.time_f
util_PNR_stop_type_constant,PNR_LOC - Stop type constant,@df.stop_type_pnr * df.time_factor,,,,,,,coef_ivt,,,,,,,,
util_PNR_vehicle_type_constant,PNR_LOC - Vehicle type constant,@df.vehicle_type_pnr * df.time_factor,,,,,,,coef_ivt,,,,,,,,
util_PNR_parking_cost,PNR_LOC - Parking cost,@df.parking_cost_pnr * 100 / df.cost_sensitivity,,,,,,,coef_income,,,,,,,,
util_PNR_Fare_and_operating_cost,PNR_LOC - Fare ,@df.transitSubsidyPassDiscount * df.fare_pnr * 100 * df.num_participants / df.cost_sensitivity,,,,,,,coef_income,,,,,,,,
util_PNR_Fare_and_operating_cost,PNR_LOC - Fare,"@df.transitSubsidyPassDiscount * df.get('TRANSIT_FF', 1) * df.fare_pnr * 100 * df.num_participants / df.cost_sensitivity",,,,,,coef_income,,,,,,,,
util_PNR_LOC - Age 16 to 24,PNR_LOC - Age 16 to 24,@(df.age > 15) & (df.age < 25),,,,,,,coef_age1624_tran,,,,,,,,
util_PNR_LOC - Age 41 to 55,PNR_LOC - Age 41 to 55,@(df.age > 40) & (df.age < 56),,,,,,,coef_age4155_tran,,,,,,,,
util_PNR_LOC - Age 56 to 64,PNR_LOC - Age 56 to 64,@(df.age > 55) & (df.age < 65),,,,,,,coef_age5664_tran,,,,,,,,
Expand All @@ -116,7 +116,7 @@ util_KNR_wait_egress_time_(at_attraction_end),KNR_LOC - Egress mt/nev wait time
util_KNR_Walk_other_time,KNR_LOC - Walk other time,@df.knr_other_walk_time*df.time_factor,,,,,,,,coef_xwalk,,,,,,,
util_KNR_stop_type_constant,KNR_LOC - Stop type constant,@df.knr_stop_constant* df.time_factor,,,,,,,,coef_ivt,,,,,,,
util_KNR_vehicle_type_constant,KNR_LOC - Vehicle type constant,@df.knr_veh_constant* df.time_factor,,,,,,,,coef_ivt,,,,,,,
util_KNR_Fare_and_operating_cost,KNR_LOC - Fare ,@df.transitSubsidyPassDiscount*(odt_skims['fare'] + dot_skims['fare'])*100*df.num_participants/df.cost_sensitivity,,,,,,,,coef_income,,,,,,,
util_KNR_Fare_and_operating_cost,KNR_LOC - Fare ,"@df.transitSubsidyPassDiscount*df.get('TRANSIT_FF', 1)*(odt_skims['fare'] + dot_skims['fare'])*100*df.num_participants/df.cost_sensitivity",,,,,,,,coef_income,,,,,,,
util_KNR_LOC - Age 16 to 24,KNR_LOC - Age 16 to 24,@(df.age > 15) & (df.age < 25),,,,,,,,coef_age1624_tran,,,,,,,
util_KNR_LOC - Age 41 to 55,KNR_LOC - Age 41 to 55,@(df.age > 40) & (df.age < 56),,,,,,,,coef_age4155_tran,,,,,,,
util_KNR_LOC - Age 56 to 64,KNR_LOC - Age 56 to 64,@(df.age > 55) & (df.age < 65),,,,,,,,coef_age5664_tran,,,,,,,
Expand All @@ -138,7 +138,7 @@ util_TNC_wait_egress_time_(at_attraction_end),TNC_LOC - Egress mt/nev wait time
util_TNC_Walk_other_time,TNC_LOC - Walk other time,@(df.knr_other_walk_time) *df.time_factor,,,,,,,,,coef_xwalk,,,,,,
util_TNC_stop_type_constant,TNC_LOC - Stop type constant,@(df.knr_stop_constant)* df.time_factor,,,,,,,,,coef_ivt,,,,,,
util_TNC_vehicle_type_constant,TNC_LOC - Vehicle type constant,@(df.knr_veh_constant)* df.time_factor,,,,,,,,,coef_ivt,,,,,,
util_TNC_Fare_and_operating_cost,TNC_LOC - Fare ,@df.transitSubsidyPassDiscount*(odt_skims['fare'] + dot_skims['fare'])*100*df.num_participants/df.cost_sensitivity,,,,,,,,,coef_income,,,,,,
util_TNC_Fare_and_operating_cost,TNC_LOC - Fare ,"@df.transitSubsidyPassDiscount*df.get('TRANSIT_FF', 1)*(odt_skims['fare'] + dot_skims['fare'])*100*df.num_participants/df.cost_sensitivity",,,,,,coef_income,,,,,,
util_TNC_LOC - Age 16 to 24,TNC_LOC - Age 16 to 24,@(df.age > 15) & (df.age < 25),,,,,,,,,coef_age1624_tran,,,,,,
util_TNC_LOC - Age 41 to 55,TNC_LOC - Age 41 to 55,@(df.age > 40) & (df.age < 56),,,,,,,,,coef_age4155_tran,,,,,,
util_TNC_LOC - Age 56 to 64,TNC_LOC - Age 56 to 64,@(df.age > 55) & (df.age < 65),,,,,,,,,coef_age5664_tran,,,,,,
Expand Down
Loading
Loading