# -*- coding: utf-8 -*-
import numpy as np
import equilibrium.sub.functions_solver as eqsol
[docs]def compute_outputs(housing_type,
utility,
amenities,
param,
income_net_of_commuting_costs,
fraction_capital_destroyed,
grid,
income_class_by_housing_type,
options,
housing_limit,
agricultural_rent,
interest_rate,
coeff_land,
minimum_housing_supply,
construction_param,
housing_in,
param_pockets,
param_backyards_pockets):
"""
Compute equilibrium outputs from theoretical formulas.
From optimality conditions on supply and demand (see technical
documentation for math formulas), this function computes, for a given
housing type, the following outputs. First, the demanded dwelling size in
each place per income group. Then, the bid-rent function / willingness to
pay (per m² of housing) in each place per income group. By selecting the
highest bid, we recover the final simulated dwweling size and market rent.
From there, we also compute the housing supply per unit of available land,
and the total number of households in each location, per income group.
To do so, it leverages the equilibrium.sub.functions_solver module.
Parameters
----------
housing_type : str
Endogenous housing type considered in the function: should be set to
"formal", "backyard", or "informal"
utility : ndarray(float64)
Utility levels for each income group (4) considered in a given
iteration
amenities : ndarray(float64)
Normalized amenity index (relative to the mean) for each grid cell
(24,014)
param : dict
Dictionary of default parameters
income_net_of_commuting_costs : ndarray(float64, ndim=2)
Expected annual income net of commuting costs (in rands, for
one household), for each geographic unit, by income group (4)
fraction_capital_destroyed : DataFrame
Data frame of expected fractions of capital destroyed, for housing
structures and contents in different housing types, in each
grid cell (24,014)
grid : DataFrame
Table yielding, for each grid cell (24,014), its x and y
(centroid) coordinates, and its distance (in km) to the city centre
income_class_by_housing_type : DataFrame
Set of dummies coding for housing market access (across 4 housing
submarkets) for each income group (4, from poorest to richest)
options : dict
Dictionary of default options
housing_limit : Series
Maximum housing supply (in m² per km²) in each grid cell (24,014)
agricultural_rent : float64
Annual housing rent below which it is not profitable for formal private
developers to urbanize (agricultural) land: endogenously limits urban
sprawl
interest_rate : float64
Real interest rate for the overall economy, corresponding to an average
over past years
coeff_land : ndarray(float64, ndim=2)
Updated land availability for each grid cell (24,014) and each
housing type (4: formal private, informal backyards, informal
settlements, formal subsidized)
minimum_housing_supply : ndarray(float64)
Minimum housing supply (in m²) for each grid cell (24,014), allowing
for an ad hoc correction of low values in Mitchells Plain
construction_param : ndarray(float64)
(Calibrated) scale factor for the construction function of formal
private developers
housing_in : ndarray(float64)
Theoretical minimum housing supply when formal private developers do
not adjust (not used in practice), per grid cell (24,014)
param_pockets : ndarray(float64)
(Calibrated) disamenity index for living in an informal settlement,
per grid cell (24,014)
param_backyards_pockets : ndarray(float64)
(Calibrated) disamenity index for living in an informal backyard,
per grid cell (24,014)
Returns
-------
job_simul : ndarray(float64)
Simulated number of households per income group (4) for a given housing
type, at a given iteration
R : ndarray(float64)
Simulated average annual rent (in rands/m²) for a given housing type,
at a given iteration, for each selected pixel (4,043)
people_init : ndarray(float64)
Simulated number of households for a given housing type, at a given
iteration, for each selected pixel (4,043)
people_center : ndarray(float64, ndim=2)
Simulated number of households for a given housing type, at a given
iteration, for each selected pixel (4,043) and each income group (4)
housing_supply : ndarray(float64)
Simulated housing supply per unit of available land (in m² per km²)
for a given housing type, at a given iteration, for each selected pixel
(4,043)
dwelling_size : ndarray(float64)
Simulated average dwelling size (in m²) for a given housing type, at a
given iteration, for each selected pixel (4,043)
R_mat : ndarray(float64, ndim=2)
Simulated willingness to pay / bid-rents (in rands/m²) for a given
housing type, at a given iteration, for each selected pixel (4,043) and
each income group (4)
"""
# %% Dwelling size in selected pixels per (endogenous) housing type
if housing_type == 'formal':
dwelling_size = eqsol.compute_dwelling_size_formal(
utility, amenities, param, income_net_of_commuting_costs,
fraction_capital_destroyed)
# Here, we introduce the minimum lot size
dwelling_size = np.maximum(dwelling_size, param["mini_lot_size"])
# And we make sure we do not consider cases where some income groups
# would have no access to formal housing
dwelling_size[income_class_by_housing_type.formal == 0, :] = np.nan
elif housing_type == 'backyard':
# Defined exogenously
dwelling_size = param["shack_size"] * np.ones((4, len(grid.dist)))
# As before
dwelling_size[income_class_by_housing_type.backyard == 0, :] = np.nan
elif housing_type == 'informal':
# Defined exogenously
dwelling_size = param["shack_size"] * np.ones((4, len(grid.dist)))
# As before
dwelling_size[income_class_by_housing_type.settlement == 0, :] = np.nan
# %% Bid-rent functions in selected pixels per (endogenous) housing type
if housing_type == 'formal':
# See technical documentation for math formula
R_mat = (param["beta"] * (income_net_of_commuting_costs)
/ (dwelling_size - (param["alpha"] * param["q0"])))
R_mat[income_net_of_commuting_costs < 0] = 0
R_mat[income_class_by_housing_type.formal == 0, :] = 0
elif housing_type == 'backyard':
# See technical documentation for math formula
if options["actual_backyards"] == 1:
R_mat = (
(1 / param["shack_size"])
* (income_net_of_commuting_costs
- ((1 + np.array(
fraction_capital_destroyed.contents_backyard)[None, :]
* param["fraction_z_dwellings"])
* ((utility[:, None]
/ (amenities[None, :]
* param_backyards_pockets[None, :]
* ((dwelling_size - param["q0"])
** param["beta"])))
** (1 / param["alpha"])))
- (param["informal_structure_value"]
* (interest_rate + param["depreciation_rate"]))
- (np.array(
fraction_capital_destroyed.structure_backyards
)[None, :] * param["informal_structure_value"]))
)
elif options["actual_backyards"] == 0:
R_mat = (
(1 / param["shack_size"])
* (income_net_of_commuting_costs
- ((1 + np.array(
fraction_capital_destroyed.contents_backyard)[None, :]
* param["fraction_z_dwellings"])
* ((utility[:, None]
/ (amenities[None, :]
* param_backyards_pockets[None, :]
* ((dwelling_size - param["q0"])
** param["beta"])))
** (1 / param["alpha"])))
- (param["informal_structure_value"]
* (interest_rate + param["depreciation_rate"]))
- (np.array(
fraction_capital_destroyed.structure_informal_backyards
)[None, :] * param["informal_structure_value"]))
)
R_mat[income_class_by_housing_type.backyard == 0, :] = 0
elif housing_type == 'informal':
# See technical documentation for math formula
R_mat = (
(1 / param["shack_size"])
* (income_net_of_commuting_costs
- ((1 + np.array(fraction_capital_destroyed.contents_informal)[
None, :] * param["fraction_z_dwellings"])
* ((utility[:, None] / (amenities[None, :]
* param_pockets[None, :]
* ((dwelling_size - param["q0"])
** param["beta"])))
** (1 / param["alpha"])))
- (param["informal_structure_value"]
* (interest_rate + param["depreciation_rate"]))
- (np.array(
fraction_capital_destroyed.structure_informal_settlements
)[None, :] * param["informal_structure_value"]))
)
R_mat[income_class_by_housing_type.settlement == 0, :] = 0
# We clean the results just in case
R_mat[R_mat < 0] = 0
R_mat[np.isnan(R_mat)] = 0
# We select highest bidder (income group) in each location
proba = (R_mat == np.nanmax(R_mat, 0))
# We correct the matrix if binding budget constraint
# (and other precautions)
limit = ((income_net_of_commuting_costs > 0)
& (proba > 0)
& (~np.isnan(income_net_of_commuting_costs))
& (R_mat > 0))
proba = proba * limit
# Yields directly the selected income group for each location
which_group = np.nanargmax(R_mat, 0)
# Then we recover rent and dwelling size associated with the selected
# income group in each location
R = np.empty(len(which_group))
R[:] = np.nan
dwelling_size_temp = np.empty(len(which_group))
dwelling_size_temp[:] = np.nan
for i in range(0, len(which_group)):
R[i] = R_mat[int(which_group[i]), i]
dwelling_size_temp[i] = dwelling_size[int(which_group[i]), i]
dwelling_size = dwelling_size_temp
# %% Housing supply (per unit of available land)
if housing_type == 'formal':
housing_supply = eqsol.compute_housing_supply_formal(
R, options, housing_limit, param, agricultural_rent, interest_rate,
fraction_capital_destroyed, minimum_housing_supply,
construction_param, housing_in, dwelling_size)
housing_supply[R == 0] = 0
elif housing_type == 'backyard':
housing_supply = eqsol.compute_housing_supply_backyard(
R, param, income_net_of_commuting_costs,
fraction_capital_destroyed, grid, income_class_by_housing_type)
housing_supply[R == 0] = 0
elif housing_type == 'informal':
# We simply take a supply equal to the available constructible land,
# hence ones when considering supply per land unit (informal
# settlements are assumed not costly to build), then convert to m²
housing_supply = 1000000 * np.ones(len(which_group))
housing_supply[R == 0] = 0
# %% Outputs
# Yields population density in each selected pixel
people_init = housing_supply / dwelling_size * (np.nansum(limit, 0) > 0)
people_init[np.isnan(people_init)] = 0
# Yields number of people per pixel, as 0.25 is the area of a pixel
# (0.5*0.5 km) and coeff_land reduces it to inhabitable area
people_init_land = people_init * coeff_land * 0.25
# We associate people in each selected pixel to the highest bidding income
# group
people_center = np.array(people_init_land)[None, :] * proba
people_center[np.isnan(people_center)] = 0
# Then we sum across pixels and get the number of people in each income
# group for given housing type
job_simul = np.nansum(people_center, 1)
# We also put a floor equal to the agricultural rent for rents in the
# formal private sector
if housing_type == 'formal':
R = np.maximum(R, agricultural_rent)
return (job_simul, R, people_init, people_center, housing_supply,
dwelling_size, R_mat)