********************************************************************** *** Gradient solver for *** *** Codes for Ahlfeldt & Barr (2022): The economics of skyscrapers *** *** Version 0.93 *** *** (c) Gabriel M Ahlfeldt, 02/2024 *** ********************************************************************** /************* *** Syntax *** ************** Once you have copied AB2022.ado file to your ado folder, Stata will generate floor space, height, and land rent gradients under the baseline parameterization. Below are a brief description and canonical parameter values from Ahlfeldt & Barr (2022) theta_C Commercial construction cost elasticity of height 0.5 theta_R Residential construction cost elasticity of height 0.55 omega_C Commercial rent elasticity of height 0.03 omega_R Residential rent elasticity of height 0.07 beta_C Agglomeration elasticity 0.03 a_bar_C Fundamental production amenity 2 a_bar_R Fundamental residential amenity 1 tau_C Commercial amenity decay 0.01 tau_R Residential amenity decay 0.005 c_C Commerical baseline construction factor 1.4 c_R Residential baseline construction factor 1.4 r_a Agricultural land rents 150 S_bar_C Commercial height limit 999 S_bar_R Residential height limit 999 x_1_bar Urban growth boundary (max. radius of urban area) 999 You can change any of the parameters by adding "parameter=value" as an argument. For example, to add a 50 floor height limit for commercial buildings, you can enter: AB2022 "S_bar_C=50" You can enter multiple alterations to baseline parameter values. For example, to add to add a 50 floor height limit for commercial buildings and a 10-floor height limit for residential buildings, you can enter: AB2022 "S_bar_R=100" And so forth... Enjoy using the toolkit */ *********************************************** *** Define programmes for solving the model *** *********************************************** ******************************************************************************** // Solver that computes all endogenous variables, including aggregate labour demand and supply capture program drop SOLVER program SOLVER // Start defineing SOLVER program // Clear any pre-existing value qui foreach var of varlist A_tilde_x_C A_tilde_x_R a_x_C a_x_R r_x_C r_x_R U S_x_C S_x_R p_bar_x_C p_bar_x_R L_x_C f_bar_x_R n_x { replace `var' = . } // Compute Amenity and floor space shifters derived on p. 7 qui replace A_tilde_x_C = a_bar_C * a_rand_C * L^beta_C * exp(-tau_C*D) // Commercial production amenity shifter qui replace A_tilde_x_R = a_bar_R * a_rand_R * L^beta_R * exp(-tau_R*D) // Residential amenity shifter qui replace a_x_C = A_tilde_x_C^(1/(1-alpha_C))*y^(alpha_C/(alpha_C-1)) // Commercial floor space shifter qui replace a_x_R = A_tilde_x_R^(1/(1-alpha_R))*y^(1/(1-alpha_R)) // Residential floor space shifter // Land rent according to Eq. (7) qui replace r_x_C = a_x_C / (1+omega_C)*(a_x_C / (c_C*(1+omega_C)))^((1+omega_C)/(theta_C-omega_C))-c_C*(a_x_C/(c_C*(1+theta_C)))^((1+theta_C)/(theta_C-omega_C)) qui replace r_x_R = a_x_R / (1+omega_R)*(a_x_R / (c_R*(1+omega_R)))^((1+omega_R)/(theta_R-omega_R))-c_R*(a_x_R/(c_R*(1+theta_R)))^((1+theta_R)/(theta_R-omega_R)) // Define land use qui replace U = . // Clear any pre-existing value qui replace U = 3 if r_a > r_x_C & r_a > r_x_R // Agrictultural land rent is highest qui replace U = 2 if r_x_R > r_x_C & r_x_R > r_a // Commercial land rent is highest qui replace U = 1 if r_x_C > r_x_R & r_x_C > r_a // Residential land rent is highest // Outer edge of residential zone qui sum x if U < 3 & x >= 0 // Take the inner margin of the agricultural zone qui scalar x1 = r(max) qui local x1 = x1 // Outer edge of residential zone qui sum x if U != 1 & x >= 0 // Take the inner margin of any zone that is not commerical qui scalar x0 = r(min) qui local x0 = x0 // Profit maximizing height defined just above Eq. (6) qui replace S_star_x_C = ( (a_x_C / (c_C*(1+theta_C)))^(1/(theta_C-omega_C)) ) if U == 1 qui replace S_star_x_R = ( (a_x_R / (c_R*(1+theta_R)))^(1/(theta_R-omega_R)) ) if U == 2 // Compute actual height according to Eq. (6) qui replace S_x_C = min(S_bar_C, (a_x_C / (c_C*(1+theta_C)))^(1/(theta_C-omega_C)) ) if U == 1 qui replace S_x_R = min(S_bar_R, (a_x_R / (c_R*(1+theta_R)))^(1/(theta_R-omega_R)) ) if U == 2 // Comute bid rents qui replace p_bar_x_C = a_x_C * 1/(1-omega_C)*S_x_C^omega_C // According to Eq. (2) qui replace p_bar_x_R = a_x_R * 1/(1-omega_R)*S_x_R^omega_R // According to Eq. (4) // Compute labour demand qui replace L_x_C = alpha_C/(1-alpha_C)*p_bar_x_C / y * S_x_C if U == 1 // using MRS in Eq. (12) qui sum L_x_C // Read values across locations qui scalar L_hat_demand = r(sum) // Sum across locations to get aggregate labour demand accordin gto Eq. (14) // Compute labour supply qui replace f_bar_x_R = (1-alpha_R)/p_bar_x_R*y if U == 2 // Floor space per capita from Marshallian demand function qui replace n_x = S_x_R / f_bar_x_R // Get number of workers from total floor space and floor space per capita qui sum n_x // Read number of workers across locations qui return list qui scalar L_hat_supply = r(sum) // Sum across locations to get aggregate labour supply // Compute some final statistics qui sum L // Summarize employment qui scalar sL = r(mean) // Save employment as scalar qui sum y // Summarize wage qui scalar sy = r(mean) // Save wage as scalar qui replace S_x = max(S_x_C,S_x_R) // Save highest possible building of any potential use at a given location scalar list L_hat_demand L_hat_supply sL sy // Output wage adjustement factor, labour demand & supply, and wage for inspection of the user // Land use qui replace URBAN = U < 3 // Update indicator for urban use qui replace COM = U == 1 // Update indicator for commercial use // Solver ends end // SOLVER programme completed ******************************************************************************** ******************************************************************************** // Algorithm that implmenents iterative procedure to find market-clearing wage capture program drop WAGE program define WAGE qui local obj_int = abs((L_hat_demand+0)/(L_hat_supply+0)-1) // Define value in the objective function of the internal loop, the percentage difference between demand and supply in the model. // When this relative difference approaches zero, we have found the WAGE that clears the market while `obj_int' > 0.01 { // Keep iterating while objective is larger than tolerance level // Compute the wage adjustment factor to be used in the iterative procedure to solve for the wage qui if L_hat_supply == 0 { // If no labour supply increase wage by 20% scalar y_factor = 1.2 } else { if L_hat_demand == 0 { // If no labour demand decrease wage by 10% scalar y_factor = 0.8 } else { scalar y_factor = (L_hat_demand / L_hat_supply)^0.01 // Adjust depending on the ration of labour demand to labour supply => if labour demand exceeds supply, increase the wage and vice versa } } // Now adjust the wage accordingly qui replace y = 0.5 * y + 0.5*y*y_factor // Use the average of the old wage and a new wage, updated by the wage adjustment factor // Update the model solutions with the new wage qui SOLVER // Compute the new internal objective value and report it alongside the current external objective value to the user qui local obj_int = abs((L_hat_demand+0.0001)/(L_hat_supply+0.0001)-1) display "internal objective " round(`obj_int', 0.001) " external objective " round( `obj_ext', 0.001) // Close the internal loop, if we pass beyond the point, we have found the market-clearing wage conditional on our guess of total employment } end // Programme completed ******************************************************************************** ******************************************************************************** // Graph height capture program drop GHEIGHT program GHEIGHT qui sum S_x_C qui local top = (int(r(max)/25)+1)*25 qui replace SHADE = URBAN*COM*(int(`top'/1)+1) qui replace SHADEU = URBAN*(int(`top')+1) qui sum S_x_C qui local max = round(r(max),25) qui local step = round(`max'/4, 5) twoway (area SHADEU x, color(gs14)) (area SHADE x, color(gs12)) (line S_x_C x , color(red) lpattern(longdash)) (line S_x_R x if x < 0, color(blue) lpattern(shortdash)) (line S_x_R x if x > 0, color(blue) lpattern(shortdash)), /// graphregion(color(white)) ylabel(0[`step']`max') xlabel(-50[25]50) xtitle("") ytitle("Building height") legend(order(3 "Commercial" 4 "Residential") cols(2) pos(6)) title("Building height") name("`1'", replace) xsize(5) ysize(5) nodraw end // Solver ends ******************************************************************************** ******************************************************************************** // Graph bid rent capture program drop GBIDRENT program GBIDRENT *local urban_max = urban_max *local urban_min = urban_min *local com_max = com_max qui sum p_bar_x_C qui local top = (int(r(max)/5)+1)*5 qui replace SHADE = URBAN*COM*(int(`top'/1)) qui replace SHADEU = URBAN*(int(`top')) twoway (area SHADEU x, color(gs14)) (area SHADE x, color(gs12)) (line p_bar_x_C x , color(red) lpattern(longdash)) (line p_bar_x_R x if x > 0, color(blue) lpattern(shortdash)) (line p_bar_x_R x if x < 0, color(blue) lpattern(shortdash)) /// , graphregion(color(white)) xlabel(-50[25]50) xtitle("") ytitle("Floor space rent") legend(order(1 "Urban area" 2 "CBD area" 3 "Commercial" 4 "Residential") cols(4) pos(6)) title("Floor space rent") name(`1', replace) nodraw end // Solver ends ******************************************************************************** ******************************************************************************** // Graph land rent capture program drop GLANDRENT program GLANDRENT qui sum r_x_C qui local top = (int(r(max)/500)+1)*500 qui replace SHADE = URBAN*COM*(int(`top')+1) qui replace SHADEU = URBAN*(int(`top')+1) twoway (area SHADEU x, color(gs14)) (area SHADE x, color(gs12)) (line r_x_C x , color(red) lpattern(longdash)) (line r_x_R x , color(blue) lpattern(shortdash)) (line r_a x, color(black) lpattern(solid)), /// graphregion(color(white)) xlabel(-50[25]50) xtitle("") ytitle("Land bid rent") legend(order(3 "Commercial" 4 "Residential" 5 "Agricultural" 1 "Urban area" 2 "CBD") size(vsmall) cols(5) pos(6)) title("Land bid rent") name(`1', replace) nodraw end ******************************************************************************** // Bar height capture program drop GHEIGHTB program GHEIGHTB qui sum S_x qui gen bin = int(D) qui egen mS = mean(S_x), by(bin) qui egen sdS = sd(S_x), by(bin) qui gen cvS = sdS/mS qui sum cvS qui local CV = round(r(mean), 0.001) qui sum cvS if bin <= 5 qui local CVcbd = round(r(mean), 0.001) qui drop bin mS sdS cvS qui sum S_x_C qui local top = (int(r(max)/25)+1)*25 qui replace SHADE = URBAN*COM*(int(`top'/1)+1) qui replace SHADEU = URBAN*(int(`top')+1) qui sum S_x_C qui local max = round(r(max),5) qui local step = round(`max'/4, 5) twoway (bar S_x_C x if U == 1, color(red) lcolor(none) barwidth(0.1) bargap(1) lpattern(solid)) (bar S_x_R x if U == 2, barwidth(0.01) lcolor(none) color(blue) lpattern(solid)) (bar S_x_R x if U == 2, barwidth(0.01) color(blue) lcolor(none) lpattern(solid)), /// graphregion(color(white)) ylabel(0[`step']`max') xlabel(-50[25]50) xtitle("") ytitle("Building height") legend(order(1 "Commercial" 2 "Residential") cols(2) pos(6)) name("`1'", replace) xsize(10) ysize(5) note("Mean coefficent of variation within one-distance-unit bins = `CV'") nodraw // (area SHADEU x, color(gs14)) (area SHADE x, color(gs12)) end // Solver ends ******************************************************************************** // Iterative programme that nests SOLVER to find equilibrium conditional on parameter choices program FINDEQ // Syntax theta_C theta_R omega_C omega_R beta_C a_bar_C a_bar_R tau_C tau_R c_C c_R r_a S_bar_C S_bar_R // Set scalars based on parameter choices by the user scalar theta_C = `1' scalar theta_R = `2' scalar omega_C = `3' scalar omega_R = `4' scalar beta_C = `5' scalar a_bar_C = `6' scalar a_bar_R = `7' scalar tau_C = `8' scalar tau_R = `9' scalar c_C = `10' scalar c_R = `11' qui replace r_a = `12' scalar S_bar_C = `13' scalar S_bar_R = `14' // Initialization SOLVER // Run initial round of the solver local obj_ext = abs(L/(0.5*(L_hat_demand+L_hat_supply))-1) // Define value in the objective function of the external loop, the percentage difference between total employment L (here the set value) and hte average of current demand and supply in the model // When this relative difference approaches zero, we have have found TOTAL EMPLOYMENT // Start external loop until we converged while `obj_ext' > 0.01{ // Keep iterating while objective is larger than tolerance level // Initialization for the inner loop qui SOLVER // Use SOLVER to solve for endogenous objects // Solve for market clearing wage // Use WAGE programme WAGE // Test if the city is sustainable local test = L_hat_demand+L_hat_supply // Check if labour demand and supply converged to zero if `test' == 0 { // If so, stop display "Error: City does not reach critical size." display "Increase productivity to make city more attractive" stop } else { // Else just continue } // Update the value of the external objective function local obj_ext = abs(L/(0.5*(L_hat_demand+L_hat_supply))-1) // Percentage difference between the guess of total employment and the average of demand and supply within the model // Update our guess of total employment qui replace L = 0.5 * L + 0.5*0.5*(L_hat_demand + L_hat_supply) // We use the average of the old guess of total employment and the average of updated demand and supply within the model // Close outer loop, if we pass this point, we have found a wage that clears the labour market (internal loop) and total employment (outer loop) } // Report key labour market outcomes to reader scalar list L_hat_demand L_hat_supply sL sy x0 x1 // Solver ends end // Programme completed ******************************************************************************** ******************************************************************************** // Iterative programme that nests SOLVER to find equilibrium conditional on parameter choices - ADO file version capture program drop AB2022 program AB2022 // Syntax theta_C theta_R omega_C omega_R beta_C a_bar_C a_bar_R tau_C tau_R c_C c_R r_a S_bar_C S_bar_R version 17.0 // The program has not been tested with earlier version, but is likely to run on them capture set scheme s1color capture set scheme stcolor ************************************ *** Generate artificial data set *** ************************************ local large = 0 // Reading user choice of city size clear // Drop all data program drop _all // Drop all programs * Residential parameters scalar alpha_R = 0.66 // Set expenditure share on non-housing goodes scalar beta_R = 0.00 // Set residential agglomeration elasticity to zero scalar tau_R = 0.01 // Set residential amenity decay scalar omega_R = 0.1 // Set residential height elasticity of rents scalar theta_R = 0.5 // Set residential height elasticity of construction cost scalar c_R = 1 // Set residential baseline construction cost scalar a_bar_R = 1 // Set residential fundamental amenity * Commercial parameters scalar alpha_C = 0.85 // Set input share of floor space in production scalar beta_C = 0.04 // Set commercial aggloemration elasticity scalar tau_C = 0.01 // Set commercial productivity decay scalar omega_C = 0.1 // Set commerical height elasticity of rents scalar theta_C = 0.5 // Set commerical height elasticity of construction cost scalar c_C = 1 // Set commercial baseline construction cost scalar a_bar_C = 1.5 // Set commerical fundamental productivity if `large' == 1 { // If city is large qui set obs 20001 // Generate 20001 observations, one for each location along a line. 1 for the center. 10000 in either direction qui gen x = _n -10001 // Generate running variable, 0 at center qui replace x = x / 100 // Rescale running variable so that is can be thought of being in km qui gen D = abs(x) // Generate distance from the centre as absolute value of the running variable } else { // city is small qui set obs 10001 // Generate 10001 observations, one for each location along a line. 1 for the center. 5000 in either direction qui gen x = _n -5001 // Generate running variable, 0 at center qui replace x = x / 100 // Rescale running variable so that is can be thought of being in km qui gen D = abs(x) // Generate distance from the centre as absolute value of the running variable } // Starting values qui gen y = 2.5 // Starting value for wage qui gen L = 1000000 // Starting value for employment // Generate placeholders for objects to be set by user qui gen r_a = 100 // Exogenous agricutural land rent qui gen a_x_C = . // Fudnamental production amenity qui gen a_x_R = . // Fundamental residential amenity // Generate placeholders for objects to be solved qui gen a_rand_C = 1 // Location specific component in production amenity that can be used to generate fuzzy height gradients (not relevant when working with stylized cities) qui gen a_rand_R = 1 // Location specific component in residential amenity that can be used to generate fuzzy height gradients (not relevant when working with stylized cities) qui gen A_tilde_x_C = . // Compositive productivity shifter qui gen A_tilde_x_R = . // Compositive amenity shifter qui gen r_x_C= . // Commercial land rent qui gen r_x_R= . // Residential land rent qui gen U = . // Auxilliary variable for land use category qui gen S_star_x_C = . // Profit-maximizing commerical height qui gen S_star_x_R = . // Profit-maximizing residential height qui gen S_x_C = . // Realized commercial height qui gen S_x_R = . // Realized residential height qui gen S_x = . // Height qui gen p_bar_x_C = . // Commericial horizontal bid rent qui gen p_bar_x_R = . // Residential horizontal bid rent qui gen L_x_C = . // Labour input qui gen f_bar_x_R = . // Residential floor space demand per capita qui gen n_x = . // Labour supply qui gen URBAN = . // Auxiliary idicator for urban user qui gen COM = . // Auxiliary indicator for commerical use qui gen SHADE = . // Auxiliary indicator used in graphs qui gen SHADEU = . // Auxiliary indicator used in graphs // Set scalars based on parameter choices by the user scalar theta_C = 0.5 scalar theta_R = 0.55 scalar omega_C = 0.03 scalar omega_R = 0.07 scalar beta_C = 0.03 scalar a_bar_C = 2 scalar a_bar_R = 1 scalar tau_C = 0.01 scalar tau_R = 0.005 scalar c_C = 1.4 scalar c_R = 1.4 scalar r_a = 150 scalar S_bar_C = 999 scalar S_bar_R = 999 scalar x_1_bar = 999 // Override with user entry capture scalar `1' capture scalar `2' capture scalar `3' capture scalar `4' capture scalar `5' capture scalar `6' capture scalar `7' capture scalar `8' capture scalar `9' capture scalar `10' capture scalar `11' capture scalar `12' capture scalar `13' capture scalar `14' capture scalar `15' qui drop r_a qui gen r_a = r_a // Initialization display "<<< solving for the equilibrium >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" qui drop if D > x_1_bar // Dropping anything outside the user-specified urban growth boundary qui SOLVER // Run initial round of the solver local obj_ext = abs(L/(0.5*(L_hat_demand+L_hat_supply))-1) // Define value in the objective function of the external loop, the percentage difference between total employment L (here the set value) and hte average of current demand and supply in the model // When this relative difference approaches zero, we have have found TOTAL EMPLOYMENT // Start external loop until we converged while `obj_ext' > 0.01{ // Keep iterating while objective is larger than tolerance level // Initialization for the inner loop qui SOLVER // Use SOLVER to solve for endogenous objects // Solve for market clearing wage // Use WAGE programme qui WAGE // Test if the city is sustainable local test = L_hat_demand+L_hat_supply // Check if labour demand and supply converged to zero if `test' == 0 { // If so, stop display "Error: City does not reach critical size." display "Increase productivity to make city more attractive" stop } else { // Else just continue } // Update the value of the external objective function local obj_ext = abs(L/(0.5*(L_hat_demand+L_hat_supply))-1) // Percentage difference between the guess of total employment and the average of demand and supply within the model // Update our guess of total employment qui replace L = 0.5 * L + 0.5*0.5*(L_hat_demand + L_hat_supply) // We use the average of the old guess of total employment and the average of updated demand and supply within the model // Close outer loop, if we pass this point, we have found a wage that clears the labour market (internal loop) and total employment (outer loop) } // Report key labour market outcomes to reader foreach name in L_hat_demand L_hat_supply sL sy x0 x1 { local `name' = round(`name',0.01) } display "Labour demand: `L_hat_demand'" display "Labour supply: `L_hat_supply'" display "Total employment: `sL'" display "Wage: `sy'" display "CBD radius: `x0'" display "Urban radius: `x1'" * Create graph display "<<< drawing graphs >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" qui GBIDRENT name1 qui GHEIGHT name0 qui GLANDRENT name3 local sL = round(`sL',1) grc1leg name1 name0 name3, cols(3) leg(name3) xsize(15) ysize(5) note("Total employment: `sL'" "Wage: `sy'" "CBD radius: `x0'" "Urban area radius: `x1'") display "<<< all done >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" // Solver ends end // Programme completed ********************************************************************************