def building_match(idx_hhs, idx_blds): resunits = np.array([np.maximum(np.ones(idx_blds.size),(building_set['vacant_units'][idx_blds])),]*idx_hhs.size) row_sums = resunits.sum(axis=1) probabilities = (resunits) / (np.array([(row_sums),]*idx_blds.size).transpose()) resources = Resources({"capacity":building_set['vacant_units'][idx_blds],"lottery_max_iterations":50}) choices = lottery_choices().run(probabilities, resources=resources) counter = 0 for choice in choices: if choice == -1: household_set['building_id'][idx_hhs[counter]] = -1 else: household_set['building_id'][idx_hhs[counter]] = building_set['building_id'][idx_blds[choice]] counter += 1
def mate_match(self, choosers, available_mates, person_set): available_mates_age = array([(person_set['age'][available_mates]),]*choosers.size) choosers_age = array([(person_set['age'][choosers]),]*available_mates.size).transpose() available_mates_edu = array([(person_set['education'][available_mates]),]*choosers.size) #TODO: calc education in terms of years instead of level choosers_edu = array([(person_set['education'][choosers]),]*available_mates.size).transpose() age_diffs = available_mates_age - choosers_age edu_diffs = available_mates_edu - choosers_edu match_scores = exp((sqrt((age_diffs**2) + (edu_diffs**2)))*(-.5)) row_sums = match_scores.sum(axis=1) probabilities = (match_scores) / (array([(row_sums),]*available_mates.size).transpose()) #Select mates according to 'lottery choices' so that capacity can be taken into account (each person can only be chosen once) resources = Resources({"capacity":ones(available_mates.size),"lottery_max_iterations":50}) choices = lottery_choices().run(probabilities, resources=resources) #Set each chosen person's household_id to equal the household_id of the chooser counter = 0 for choice in choices: person_set['household_id'][available_mates[choice]] = person_set['household_id'][choosers[counter]] counter += 1 logger.log_status("Mate matching complete.")
def run(self, probability, resources=None): """ Compute choices according to given probability -- Constrain Location Choice procedure. 'probability' is a 2D numpy array (nobservation x nequations). The returned value is a 1D array of choice indices [0, nequations-1] of the length nobservations). The argument 'resources' must contain an entry 'capacity'. It is 1D array whose number of elements corresponds to the number of choices. Optional entry 'index' (1D or 2D array) gives indices of the choices. """ if probability.ndim < 2: raise StandardError, "Argument 'probability' must be a 2D numpy array." resources.check_obligatory_keys(["capacity"]) supply = resources["capacity"] if not isinstance(supply, ndarray): supply = array(supply) nsupply = supply.size # logger.log_status('Supply.shape:',supply.shape) # logger.log_status('supply.sum:', supply.sum()) max_iter = resources.get("max_iterations", None) if max_iter == None: max_iter = 100 # default index = resources.get("index", None) if index == None: index = arange(nsupply) # logger.log_status('index.shape:',index.shape) neqs = probability.shape[1] nobs = probability.shape[0] if supply.sum < nobs: raise StandardError, "Aggregate Supply Must be Greater than Aggregate Demand." if index.ndim <= 1: index = repeat(reshape(index, (1,index.shape[0])), nobs) resources.merge({"index":index}) # logger.log_status('index.shape:',index.shape) flat_index = index.ravel() unique_index = unique(flat_index) # logger.log_status('flat_index.shape:',flat_index.shape) # logger.log_status('unique_index.shape',unique_index.shape) # logger.log_status(unique_index) l = flat_index + 1 demand = array(ndimage_sum(probability.ravel(), labels=l, index=arange(nsupply)+1)) # logger.log_status('demand.shape:',demand.shape) # logger.log_status('demand.sum:', demand.sum()) # logger.log_status('probability.sum:',probability.sum()) #initial calculations sdratio = ma.filled(supply/ma.masked_where(demand==0, demand),1.0) # logger.log_status('sdratio.shape:',sdratio.shape) constrained_locations = where(sdratio<1,1,0) unconstrained_locations = 1-constrained_locations # Compute the iteration zero omegas sdratio_matrix = sdratio[index] constrained_locations_matrix = constrained_locations[index] unconstrained_locations_matrix = unconstrained_locations[index] prob_sum = 1-(probability*constrained_locations_matrix).sum(axis=1) omega = (1-(probability*constrained_locations_matrix*sdratio_matrix).sum(axis=1))/ \ ma.masked_where(prob_sum ==0, prob_sum) pi = sdratio_matrix / ma.resize(omega, (nobs,1)) * constrained_locations_matrix + unconstrained_locations_matrix average_omega = ma.filled((ma.resize(omega,(nobs,1))*probability).sum(axis=0)/\ ma.masked_where(demand[index]==0, demand[index]),0.0) number_constrained_locations=zeros((max_iter,)) # Iterative Constrained Location Procedure for i in range(max_iter): logger.log_status('Iteration ',i+1, 'Average Omega:',average_omega[0:4]) # Recompute the constrained locations using iteration zero value of Omega constrained_locations_matrix = where(supply[index]<(average_omega*demand[index]),1,0) unconstrained_locations_matrix = 1-constrained_locations_matrix # Update values of Omega using new Constrained Locations prob_sum = 1-(probability*constrained_locations_matrix).sum(axis=1) omega = (1-(probability*constrained_locations_matrix*sdratio_matrix).sum(axis=1))/\ ma.masked_where(prob_sum ==0, prob_sum) # pi = sdratio_matrix / ma.resize(omega, (nobs,1)) * constrained_locations_matrix + unconstrained_locations_matrix # logger.log_status('sdratio_matrix',sdratio_matrix.shape) # logger.log_status('constrained_locations_matrix',constrained_locations_matrix.shape) # logger.log_status('omega',omega.shape) # logger.log_status('unconstrained_locations_matrix',unconstrained_locations_matrix.shape) # pi_ta = (sdratio_matrix*constrained_locations_matrix) # logger.log_status('pi+ta',pi_ta.shape) # pi_tb = ma.resize(omega,(nobs,neqs))*unconstrained_locations_matrix # logger.log_status('pi_tb',pi_tb.shape) pi_t = (sdratio_matrix*constrained_locations_matrix)+ma.resize(omega,(nobs,neqs))*unconstrained_locations_matrix # logger.log_status('pi_tilde:',pi_t.shape) # Update the values of average Omegas per alternative average_omega = ma.filled((ma.resize(omega,(nobs,1))*probability).sum(axis=0)/ ma.masked_where(demand[index]==0, demand[index]),0.0) number_constrained_locations[i]= constrained_locations_matrix.sum() # Test for Convergence and if Reached, Exit if i > 0: if number_constrained_locations[i] == number_constrained_locations[i-1]: break # update probabilities # new_probability = ma.filled(probability*ma.resize(omega,(nobs,1))*pi,0.0) new_probability = ma.filled(probability*pi_t,0.0) choices = lottery_choices().run(new_probability, resources) return choices
def run(self, probability, resources=None): """ Compute choices according to given probability -- Constrain Location Choice procedure. 'probability' is a 2D numpy array (nobservation x nequations). The returned value is a 1D array of choice indices [0, nequations-1] of the length nobservations). The argument 'resources' must contain an entry 'capacity'. It is 1D array whose number of elements corresponds to the number of choices. Optional entry 'index' (1D or 2D array) gives indices of the choices. """ if probability.ndim < 2: raise StandardError, "Argument 'probability' must be a 2D numpy array." resources.check_obligatory_keys(["capacity"]) supply = resources["capacity"] if not isinstance(supply, ndarray): supply = array(supply) nsupply = supply.size max_iter = resources.get("max_iterations", None) if max_iter == None: max_iter = 100 # default index = resources.get("index", None) if index == None: index = arange(nsupply) neqs = probability.shape[1] nobs = probability.shape[0] if index.ndim <= 1: index = repeat(reshape(index, (1,index.shape[0])), nobs) resources.merge({"index":index}) flat_index = index.ravel() unique_index = unique(flat_index) l = flat_index + 1 demand = array(ndimage_sum(probability.ravel(), labels=l, index=arange(nsupply)+1)) #initial calculations sdratio = ma.filled(supply/ma.masked_where(demand==0, demand),2.0) constrained_locations = logical_and(sdratio<1,demand-supply>0.1).astype("int8") unconstrained_locations = 1-constrained_locations excess_demand = (demand-supply)*constrained_locations global_excess_demand = excess_demand.sum() # Compute the iteration zero omegas sdratio_matrix = sdratio[index] constrained_locations_matrix = constrained_locations[index] # Would like to include following print statements in debug printing # logger.log_status('Total demand:',demand.sum()) # logger.log_status('Total supply:',supply.sum()) logger.log_status('Global excess demand:',global_excess_demand) # logger.log_status('Constrained locations:',constrained_locations.sum()) unconstrained_locations_matrix = unconstrained_locations[index] prob_sum = 1-(probability*constrained_locations_matrix).sum(axis=1) # The recoding of prob_sum and omega are to handle extreme values of omega and zero divide problems # A complete solution involves stratifying the choice set in the initialization to ensure that # there are always a mixture of constrained and unconstrained alternatives in each choice set. prob_sum = where(prob_sum==0,-1,prob_sum) omega = (1-(probability*constrained_locations_matrix*sdratio_matrix).sum(axis=1))/prob_sum omega = where(omega>5,5,omega) omega = where(omega<.5,5,omega) omega = where(prob_sum<0,5,omega) # Debug print statements # logger.log_status('Minimum omega',minimum(omega)) # logger.log_status('Maximum omega',maximum(omega)) # logger.log_status('Median omega',median(omega)) # logger.log_status('Omega < 0',(where(omega<0,1,0)).sum()) # logger.log_status('Omega < 1',(where(omega<1,1,0)).sum()) # logger.log_status('Omega > 30',(where(omega>30,1,0)).sum()) # logger.log_status('Omega > 100',(where(omega>100,1,0)).sum()) # logger.log_status('Omega histogram:',histogram(omega,0,30,30)) # logger.log_status('Excess demand max:',maximum(excess_demand)) # logger.log_status('Excess demand 0-1000:',histogram(excess_demand,0,1000,20)) # logger.log_status('Excess demand 0-10:',histogram(excess_demand,0,10,20)) pi = sdratio_matrix / ma.resize(omega, (nobs,1)) * constrained_locations_matrix + unconstrained_locations_matrix omega_prob = ma.filled(ma.resize(omega,(nobs,1))*probability,0.0) average_omega_nom = array(ndimage_sum(omega_prob, labels=index+1, index=arange(nsupply)+1)) average_omega = ma.filled(average_omega_nom/ ma.masked_where(demand==0, demand), 0.0) # logger.log_status('Total demand:',new_demand.sum()) # logger.log_status('Excess demand:',excess_demand) number_constrained_locations=zeros((max_iter,)) # Iterative Constrained Location Procedure for i in range(max_iter): logger.log_status() logger.log_status('Constrained location choice iteration ',i+1) # Recompute the constrained locations using preceding iteration value of Omega constrained_locations = where((average_omega*demand-supply>0.1),1,0) unconstrained_locations = 1-constrained_locations constrained_locations_matrix = constrained_locations[index] unconstrained_locations_matrix = unconstrained_locations[index] # logger.log_status('supply.shape,average_omega.shape,demand.shape',supply.shape,average_omega.shape,demand.shape) # logger.log_status('constrained_locations_matrix',constrained_locations_matrix) # logger.log_status('constrained_locations_matrix.shape',constrained_locations_matrix.shape) # logger.log_status('unconstrained_locations_matrix',unconstrained_locations_matrix) # Update values of Omega using new Constrained Locations prob_sum = 1-(probability*constrained_locations_matrix).sum(axis=1) prob_sum = where(prob_sum==0,-1,prob_sum) omega = (1-(probability*constrained_locations_matrix*sdratio_matrix).sum(axis=1))/prob_sum omega = where(omega>5,5,omega) omega = where(omega<.5,5,omega) omega = where(prob_sum<0,5,omega) pi = sdratio_matrix / ma.resize(omega, (nobs,1)) * constrained_locations_matrix + unconstrained_locations_matrix # Update the values of average Omegas per alternative omega_prob = ma.filled(ma.resize(omega,(nobs,1)), 1.0)*probability average_omega_num = array(ndimage_sum(omega_prob, labels=index+1, index=arange(nsupply)+1)) average_omega = ma.filled(average_omega_num/ ma.masked_where(demand==0, demand), 0.0) number_constrained_locations[i] = constrained_locations.sum() new_probability = ma.filled(probability*ma.resize(omega,(nobs,1))*pi,0.0) new_demand = array(ndimage_sum(new_probability.ravel(), labels=l, index=arange(nsupply)+1)) excess_demand = (new_demand-supply)*constrained_locations global_excess_demand = excess_demand.sum() # logger.log_status('Total demand:',new_demand.sum()) logger.log_status('Global excess demand:',global_excess_demand) # logger.log_status('Constrained locations:', number_constrained_locations[i]) # logger.log_status('Minimum omega',minimum(omega)) # logger.log_status('Maximum omega',maximum(omega)) # logger.log_status('Median omega',median(omega)) # logger.log_status('Omega < 0',(where(omega<0,1,0)).sum()) # logger.log_status('Omega < 1',(where(omega<1,1,0)).sum()) # logger.log_status('Omega > 30',(where(omega>30,1,0)).sum()) # logger.log_status('Omega > 100',(where(omega>100,1,0)).sum()) # logger.log_status('Omega histogram:',histogram(omega,0,30,30)) # logger.log_status('Excess demand max:',maximum(excess_demand)) # logger.log_status('Excess demand 0-5:',histogram(excess_demand,0,5,20)) # logger.log_status('Excess demand 0-1:',histogram(excess_demand,0,1,20)) # Test for Convergence and if Reached, Exit if i > 0: if number_constrained_locations[i] == number_constrained_locations[i-1]: logger.log_status() logger.log_status('Constrained choices converged.') break # update probabilities new_probability = ma.filled(probability*ma.resize(omega,(nobs,1))*pi,0.0) choices = lottery_choices().run(new_probability, resources) return choices