Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Move CarUseModel and TourCombinationModel into own modules #477

Merged
merged 5 commits into from
Jan 13, 2023
Merged
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
38 changes: 21 additions & 17 deletions Scripts/demand/trips.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import utils.log as log
import parameters.zone as param
from datatypes.purpose import TourPurpose, SecDestPurpose
from models import logit, linear
from models import car_use, linear, tour_combinations
from datatypes.person import Person


Expand Down Expand Up @@ -50,9 +50,10 @@ def __init__(self, zone_data, resultdata, is_agent_model=False):
(50, 64),
(65, 99),
)
self.cm = logit.CarUseModel(
self.car_use_model = car_use.CarUseModel(
zone_data, bounds, self.age_groups, self.resultdata)
self.gm = logit.TourCombinationModel(self.zone_data)
self.tour_generation_model = tour_combinations.TourCombinationModel(
self.zone_data)
# Income models used only in agent modelling
self._incmod1 = linear.IncomeModel(
self.zone_data, slice(0, self.zone_data.first_not_helsinki_zone),
Expand All @@ -75,18 +76,19 @@ def create_population_segments(self):
Car user (car_user/no_car) : pandas Series
Zone array with number of people belonging to segment
"""
self.zone_data["car_users"] = self.cm.calc_prob()
cm = self.car_use_model
self.zone_data["car_users"] = cm.calc_prob()
self.segments = {}
first_peripheral_zone = self.zone_data.first_peripheral_zone
pop = self.zone_data["population"][:first_peripheral_zone]
ubound = self.zone_data.first_peripheral_zone
pop = self.zone_data["population"][:ubound]
for age_group in self.age_groups:
age = "age_" + str(age_group[0]) + "-" + str(age_group[1])
self.segments[age] = {}
age_share = self.zone_data["share_" + age][:first_peripheral_zone]
age_share = self.zone_data["share_" + age][:ubound]
car_share = 0
for gender in self.cm.genders:
car_share += ( self.zone_data["share_" + gender][:first_peripheral_zone]
* self.cm.calc_individual_prob(age, gender))
for gender in cm.genders:
car_share += (self.zone_data["share_" + gender][:ubound]
* cm.calc_individual_prob(age, gender))
self.segments[age]["car_users"] = car_share * age_share * pop
self.segments[age]["no_car"] = (1-car_share) * age_share * pop

Expand Down Expand Up @@ -132,8 +134,8 @@ def create_population(self):
# Group -1 is under-7-year-olds and they have weights[0]
person = Person(
self.zone_data.zones[zone_number],
self.age_groups[group], self.gm,
self.cm, incmod)
self.age_groups[group], self.tour_generation_model,
self.car_use_model, incmod)
self.population.append(person)
self.zone_population[zone_number] += 1
numpy.random.seed(None)
Expand All @@ -154,11 +156,12 @@ def generate_tours(self):
purpose.gen_model.add_tours()
bounds = slice(0, self.zone_data.first_peripheral_zone)
result_data = pandas.DataFrame() # For printing of results
gm = self.tour_generation_model
for age_group in self.age_groups:
age = "age_" + str(age_group[0]) + "-" + str(age_group[1])
segment = self.segments[age]
prob_c = self.gm.calc_prob(age, is_car_user=True, zones=bounds)
prob_n = self.gm.calc_prob(age, is_car_user=False, zones=bounds)
prob_c = gm.calc_prob(age, is_car_user=True, zones=bounds)
prob_n = gm.calc_prob(age, is_car_user=False, zones=bounds)
nr_tours_sums = pandas.Series()
for combination in prob_c:
# Each combination is a tuple of tours performed during a day
Expand Down Expand Up @@ -195,9 +198,10 @@ def generate_tour_probs(self):

def _get_probs(self, age, is_car_user):
bounds = slice(0, self.zone_data.first_peripheral_zone)
prob_dict = self.gm.calc_prob(age, is_car_user, bounds)
gm = self.tour_generation_model
prob_dict = gm.calc_prob(age, is_car_user, bounds)
probs = numpy.empty(
[bounds.stop - bounds.start, len(self.gm.tour_combinations)])
for i, tour_combination in enumerate(self.gm.tour_combinations):
[bounds.stop - bounds.start, len(gm.tour_combinations)])
for i, tour_combination in enumerate(gm.tour_combinations):
probs[:, i] = prob_dict[tour_combination]
return probs.cumsum(axis=1)
145 changes: 145 additions & 0 deletions Scripts/models/car_use.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import numpy
import pandas

from models.logit import LogitModel
from parameters.car import car_usage
from utils.zone_interval import ZoneIntervals


class CarUseModel(LogitModel):
"""Binary logit model for car use.

Parameters
----------
zone_data : ZoneData
Data used for all demand calculations
bounds : slice
Zone bounds
age_groups : tuple
tuple
int
Age intervals
resultdata : ResultData
Writer object to result directory
"""

def __init__(self, zone_data, bounds, age_groups, resultdata):
self.resultdata = resultdata
self.zone_data = zone_data
self.bounds = bounds
self.genders = ("female", "male")
self.age_groups = age_groups
self.param = car_usage
for i in self.param["individual_dummy"]:
self._check(i)

def _check(self, dummy):
try:
age_interval = dummy.split('_')[1]
except AttributeError:
# If the dummy is for a compound segment (age + gender)
age_interval = dummy[0].split('_')[1]
if dummy[1] not in self.genders:
raise AttributeError(
"Car use dummy name {} not valid".format(dummy[1]))
if tuple(map(int, age_interval.split('-'))) not in self.age_groups:
raise AttributeError(
"Car use dummy name {} not valid".format(age_interval))

def calc_basic_prob(self):
"""Calculate car user probabilities without individual dummies.

Returns
-------
numpy.ndarray
Choice probabilities
"""
b = self.param
utility = numpy.zeros(self.bounds.stop)
self._add_constant(utility, b["constant"])
self._add_zone_util(utility, b["generation"], True)
self.exps = numpy.exp(utility)
self._add_log_zone_util(self.exps, b["log"], True)
prob = self.exps / (self.exps+1)
return prob

def calc_prob(self):
"""Calculate car user probabilities with individual dummies included.

Returns
-------
pandas.Series
Choice probabilities
"""
prob = self.calc_basic_prob()
no_dummy_share = 1
dummy_prob = 0
b = self.param
for i in b["individual_dummy"]:
try:
dummy_share = self.zone_data.get_data(
"share_"+i, self.bounds, generation=True)
except TypeError:
# If the dummy is for a compound segment (age + gender)
dummy_share = numpy.ones_like(prob)
for j in i:
dummy_share *= self.zone_data.get_data(
"share_"+j, self.bounds, generation=True)
no_dummy_share -= dummy_share
ind_exps = numpy.exp(b["individual_dummy"][i]) * self.exps
ind_prob = ind_exps / (ind_exps+1)
dummy_prob += dummy_share * ind_prob
no_dummy_prob = no_dummy_share * prob
prob = no_dummy_prob + dummy_prob
prob = pandas.Series(
prob, self.zone_data.zone_numbers[self.bounds])
self.print_results(prob)
return prob

def calc_individual_prob(self, age_group, gender, zone=None):
"""Calculate car user probability with individual dummies included.

Uses results from previously run `calc_basic_prob()`.

Parameters
----------
age_group : str
Agent/segment age group
gender : str
Agent/segment gender (female/male)
zone : int (optional)
Index of zone where the agent lives, if no zone index is given,
calculation is done for all zones

Returns
-------
numpy.ndarray
Choice probabilities
"""
self._check((age_group, gender))
if zone is None:
exp = self.exps
else:
exp = self.exps[self.zone_data.zone_index(zone)]
b = self.param
if age_group in b["individual_dummy"]:
exp = numpy.exp(b["individual_dummy"][age_group]) * exp
if (age_group, gender) in b["individual_dummy"]:
exp = numpy.exp(b["individual_dummy"][(age_group, gender)]) * exp
prob = exp / (exp+1)
return prob

def print_results(self, prob, population_7_99=None):
""" Print results, mainly for calibration purposes"""
# Print car user share by zone
self.resultdata.print_data(prob, "car_use.txt", "car_use")
if population_7_99 is None:
# Comparison data has car user shares of population
# over 6 years old (from HEHA)
population_7_99 = (self.zone_data["population"][self.bounds]
* self.zone_data["share_age_7-99"])
# print car use share by municipality and area
for area_type in ("municipalities", "areas"):
prob_area = ZoneIntervals(area_type).averages(prob, population_7_99)
self.resultdata.print_data(
prob_area, "car_use_{}.txt".format(area_type), "car_use")
Loading