The Great Retrofit¶

J M Applegate
May 23, 2024

This model is list-structured, where buildings reside in one of the appropriate lists:
to_do, all buildings yet to be retrofitted,
queue, building having requested a retrofit and awaiting assignment to a retrofit co-op,
projects, in progress and in one of the co-op's project list, or
finished, self expanatory.

Each building has a time and cost for retrofit, where times are randomly assigned based on a mean and variance, and costs are calculated as mean weekly cost multiplied by retrofit time.
*Could also calculate time from labour cost . . .

Each co-op has an account, initially populated by the endowment value.
Co-ops take on as many projects as they can fit in their budget given current account values.
*Indicates labour is unlimited

In [6]:
# import packages
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import random
import copy
plt.rc('font',**{'family':'sans-serif','sans-serif':['Futura']})
rng = np.random.default_rng()
Model parameters, currently invented by jma.¶
In [39]:
runs = 10
n = 1000 #number of building to retrofit
mean_time = 12 #mean weeks for retrofit
time_var = 4 #variance in retrofit times
week_cost = 1000 #mean weekly cost per retrofit
run_time = 100 * 52 #max number of weeks for model to run
tau = 10 #average new project requests per week (poisson)que
n_co_ops = 10 #number of retrofit co-ops
surplus = .2 #percentage surplus for co-op
E = 30000 #initial co-op endowment

results_labels = ['run', 'n', 'n_co_ops', 'E', 'mean_time', 'week_cost', 'tau', 'to_do', 'queued', 'with_capacity', 
                  'in_progress', 'finished', 'time', 'accounts']
The Model¶
In [41]:
#results = []
step_results = []

#for n in [100, 1000, 10000, 100000, 200000]:
#for n_coops in [10, 20, 40, 60, 80, 100, 200]:
for E in [30000]:#[10000, 20000, 40000, 60000, 80000, 100000]:
    
    for r in range(runs):
        
        #print(r, end =" ")
        
        to_do = list(range(n)) #list of buildings needing retrofit
        queued = [] #list of buildings awaiting retrofit work
        times = rng.choice(2 * time_var, n) - time_var + mean_time #time to retrofit for each building
        costs = week_cost * times #cost of retrofit for each building
        projects = [[] for i in range(n_co_ops)] #list of each co-ops retrofit projects
        finished = [] #buildings with completed retrofits
        accounts = [E] * n_co_ops #account balances for each co-op
        
        for t in range(run_time):

            #print('t', t)
            
            #Buildings decide to retrofit
            #print('to_do', len(to_do))
            if to_do:
                n_requests = min(rng.poisson(tau), len(to_do))
                queued = queued + list(rng.choice(to_do, size = n_requests, replace = False))
                to_do = list(set(to_do).difference(queued))
            #print('queued', queued)
            
            #Buildings wanting retrofits are assigned to retrofit co-ops.
            #print('accounts', accounts)
            for b in queued.copy():
                estimate = costs[b]
                #print('p', b, 'estimate', estimate)
                with_capacity = [c for c in range(n_co_ops) if sum([times[x] * week_cost for x in projects[c]]) + estimate <= accounts[c]]
                #print('with_capacity', with_capacity)
                if not with_capacity: 
                    #print('no extra capacity')
                    break
                c = rng.choice(with_capacity)
                projects[c].append(queued.pop(0))
                #print('projects', projects)
            
            #Co-ops do work on buildings
            for c in range(n_co_ops):
                for p in copy.deepcopy(projects)[c]:
                    #print('project', p, 'time', times[p])
                    if times[p] == 0: 
                        projects[c].remove(p)
                        finished.append(p)
                        accounts[c] += (1 + surplus) * costs[p] 
                    else: 
                        if accounts[c] >= week_cost:
                            times[p] -= 1
                            accounts[c] -= week_cost
                    #print('projects', projects)
                            
            #print('finished', finished)

            step_results.append([r, n, n_co_ops, E, mean_time, week_cost, tau, len(to_do), len(queued), len(with_capacity), 
                                 len([b for c in projects for b in c]), len(finished), t, sum(accounts)])

            #print(len(finished))
            if len(finished) == n: break

        #run_results.append([r, n, n_co_ops, E, mean_time, week_cost, tau, len(to_do), len(queued), len(with_capacity), 
                                 #len([b for c in projects for b in c]), len(finished), t, sum(accounts)])
print('complete')
complete
In [42]:
results_frame = pd.DataFrame(step_results, columns = results_labels)
In [50]:
results_frame.to_csv('results_E30k_n1000_nc10_tau10_steps_v2.csv', index = False)
#results_frame = pd.read_csv('results_E30k_n1000_nc10_tau10_steps_v2.csv')
In [34]:
plot_data = results_frame[['run', 'time', 'in_progress', 'finished', 'queued', 'accounts', 'to_do']]
In [47]:
sns.lineplot(x = 'time', y = 'in_progress', data = plot_data, legend = True, color = 'midnightblue', linewidth = 2, label = 'in progress')
sns.lineplot(x = 'time', y = 'finished', data = plot_data, legend = True, color = 'darkred', linewidth = 2, label = 'finished')
sns.lineplot(x = 'time', y = 'queued', data = plot_data, legend = True, color = 'darkorange', linewidth = 2, label = 'queued')
sns.lineplot(x = 'time', y = 'to_do', data = plot_data, legend = True, color = 'hotpink', linewidth = 2, label = 'to do')
plt.legend(frameon=False)
sns.despine(left = True, bottom = True)
No description has been provided for this image
In [48]:
sns.lineplot(x = 'time', y = 'accounts', data = plot_data, legend = True, color = 'green', linewidth = 2, label = 'accounts')
plt.legend(frameon=False)
sns.despine(left = True, bottom = True)
No description has been provided for this image
In [ ]: