def __init__(self, probabilities = "opus_core.upc.rate_based_probabilities", choices = "opus_core.random_choices", model_name = None, debuglevel=0, resources=None ): if model_name is not None: self.model_name = model_name self.debug = DebugPrinter(debuglevel) self.upc_sequence = None if probabilities is not None: self.upc_sequence = UPCFactory().get_model(utilities=None, probabilities=probabilities, choices=choices, debuglevel=debuglevel) self.resources = merge_resources_if_not_None(resources)
def run(self, data, upc_sequence, resources=None): CLOSE = 0.01 self.mnl_probabilities=upc_sequence.probability_class self.bhhh_estimation = bhhh_mnl_estimation() modified_upc_sequence = UPCFactory().get_model( utilities=None, probabilities="opus_core.mnl_probabilities", choices=None) modified_upc_sequence.utility_class = upc_sequence.utility_class result = self.bhhh_estimation.run(data, modified_upc_sequence, resources) probability = modified_upc_sequence.get_probabilities() probability_0 = probability 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}) # WARNING: THE SCALING OF DEMAND IS HARD CODED AND NEEDS TO BE MADE AN ARGUMENT # scale demand to represent 100% from a 0.2% sample demand = self.mnl_probabilities.get_demand(index, probability, nsupply)*50 #initial calculations sdratio = ma.filled(supply/ma.masked_where(demand==0, demand),2.0) sdratio = _round(sdratio, 1.0, atol=CLOSE) constrained_locations = logical_and(sdratio<1.0,demand-supply>CLOSE).astype("int8") unconstrained_locations = 1-constrained_locations excess_demand = (demand-supply)*constrained_locations global_excess_demand = excess_demand.sum() sdratio_matrix = sdratio[index] constrained_locations_matrix = constrained_locations[index] constrained_ex_ante = constrained_locations_matrix # 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] # omega = ones(nobs,type=float32) # pi = self.constrain_probabilities.get_pi(sdratio_matrix, omega, constrained_locations_matrix, unconstrained_locations_matrix, nobs) omega = self.mnl_probabilities.get_omega(probability, constrained_locations_matrix, unconstrained_locations_matrix, sdratio_matrix) omega = _round(omega, 1.0, CLOSE) print 'Num of constrainted locations: ', constrained_locations.sum() print 'Num of unconstrainted locations: ', unconstrained_locations.sum() print 'Min Ex Ante Constraints:',min(constrained_ex_ante.sum(axis=1)) print 'Max Ex Ante Constraints:',max(constrained_ex_ante.sum(axis=1)) #print 'Omega shape',omega.shape #print 'Omega histogram',histogram(omega,0,4,40) print 'Minimum Omega',min(omega) print 'Maximum Omega',max(omega) print 'Mean Omega:',mean(omega) print 'Median Omega:',median(omega) print 'Sum Omega:',omega.sum() print 'Standard Deviation Omega:',standard_deviation(omega) print 'Count of Negative Omega',(where(omega<0,1,0).sum()) print 'Count of Omega < 1',(where(omega<1,1,0).sum()) print 'Count of Omega > 2',(where(omega>2,1,0).sum()) print 'Count of Omega > 4',(where(omega>4,1,0).sum()) average_omega = self.mnl_probabilities.get_average_omega(omega, probability, index, nsupply, nobs, demand) average_omega=_round(average_omega, 1.0, CLOSE) coef_names = resources.get("coefficient_names", None) if coef_names is not None: coef_names = array(coef_names.tolist()+["ln_pi"]) resources.merge({"coefficient_names":coef_names}) data=concatenate((data,ones((nobs,neqs,1),dtype=float32)), axis=2) prev_omega = omega prev_constrained_locations_matrix = constrained_locations_matrix for i in range(max_iter): print print 'Iteration',i pi = self.mnl_probabilities.get_pi(sdratio_matrix, omega, constrained_locations_matrix, unconstrained_locations_matrix, nobs) #print 'pi shape',pi.shape #print 'data shape', data.shape #print 'min_pi',min(pi,axis=1) #print 'max_pi',max(pi,axis=1) #print 'min_data',min(data,axis=1) #print 'max_data',max(data,axis=1) data[:,:,-1] = ln(pi) #data = concatenate((data,(pi[:,:,newaxis])),axis=-1) #print 'data shape after contatenating pi', data.shape result = self.bhhh_estimation.run(data, modified_upc_sequence, resources) #print #print 'result',result probability = modified_upc_sequence.get_probabilities() prob_hat = ma.filled(probability / pi, 0.0) # HARD CODED # scale new_demand from 0.2% to 100% demand_new = self.mnl_probabilities.get_demand(index, prob_hat, nsupply)*50 ##update supply-demand ratio sdratio = ma.filled(supply/ma.masked_where(demand_new==0, demand_new),2.0) sdratio = _round(sdratio, 1.0, CLOSE) sdratio_matrix = sdratio[index] constrained_locations = where(((average_omega*demand_new - supply) > CLOSE),1,0) unconstrained_locations = 1-constrained_locations constrained_locations_matrix = constrained_locations[index] unconstrained_locations_matrix = unconstrained_locations[index] constrained_ex_post = constrained_locations_matrix constrained_ex_post_not_ex_ante = where((constrained_ex_post - constrained_ex_ante)==1,1,0) constrained_ex_ante_not_ex_post = where((constrained_ex_post - constrained_ex_ante)==-1,1,0) #Assumption 5: if j belongs to constrained ex post and unconstrained ex ante, then p^i_j <= D_j / S_j print 'Number of individual violating Assumption 5: ', where((probability > 1 / sdratio_matrix)*constrained_ex_post_not_ex_ante)[0].size #Assumption 6: pi of constrained locations should be less than 1 print 'Number of individual violating Assumption 6: ', where((probability * constrained_ex_post).sum(axis=1) > (prob_hat * constrained_ex_post).sum(axis=1))[0].size ##OR ? #print 'Assumption 6: ', where(pi[where(constrained_locations_matrix)] > 1)[0].size print 'number of constrainted locations: ', constrained_locations.sum() print 'number of unconstrainted locations: ', unconstrained_locations.sum() print 'Min Ex Post Constraints:',min(constrained_ex_post.sum(axis=1)) print 'Max Ex Post Constraints:',max(constrained_ex_post.sum(axis=1)) print 'At Least 1 Constrained Ex Ante Not Ex Post*:',where(constrained_ex_ante_not_ex_post.sum(axis=1))[0].size print 'At Least 1 Constrained Ex Post Not Ex Ante:',where(constrained_ex_post_not_ex_ante.sum(axis=1))[0].size omega = self.mnl_probabilities.get_omega(prob_hat, constrained_locations_matrix, unconstrained_locations_matrix, sdratio_matrix) omega = _round(omega, 1.0, CLOSE) #print 'Omega histogram',histogram(omega,0,4,40) print 'Minimum Omega',min(omega) print 'Maximum Omega',max(omega) print 'Mean Omega:',mean(omega) print 'Median Omega:',median(omega) print 'Sum Omega:',omega.sum() print 'Standard Deviation Omega:',standard_deviation(omega) print 'Count of Negative Omega',(where(omega<0,1,0).sum()) print 'Count of Omega < 1: ',(where(omega<1,1,0).sum()) print 'Count of Omega > 2: ',(where(omega>2,1,0).sum()) print 'Count of Omega > 4: ',(where(omega>4,1,0).sum()) average_omega = self.mnl_probabilities.get_average_omega(omega, prob_hat, index, nsupply, nobs, demand_new) average_omega = _round(average_omega, 1.0, CLOSE) excess_demand = (demand_new-supply)*constrained_locations global_excess_demand = excess_demand.sum() #print 'Omega [i], [i-1]',prev_omega, omega, #print 'Constrained locations [i], [i-1]',constrained_locations_matrix, prev_constrained_locations_matrix print 'Global Excess Demand',global_excess_demand if ma.allclose(omega, prev_omega, atol=1e-3) or not any(constrained_locations_matrix - prev_constrained_ex_ante): print 'omega or constrained ex post unchanged: Convergence criterion achieved' break #if global_excess_demand < 1: #print 'Global excess demand < 1: Convergence criterion achieved' #break return result
class RateBasedModel(Model): """Chooses agents for relocation (according to probabilities computed by the probabilities class). It includes all jobs that are unplaced. If probabilities is set to None, only unplaced agents are chosen. The run method returns indices of the chosen agents. """ model_name = 'Rate Based Model' def __init__(self, probabilities = "opus_core.upc.rate_based_probabilities", choices = "opus_core.random_choices", model_name = None, debuglevel=0, resources=None ): if model_name is not None: self.model_name = model_name self.debug = DebugPrinter(debuglevel) self.upc_sequence = None if probabilities is not None: self.upc_sequence = UPCFactory().get_model(utilities=None, probabilities=probabilities, choices=choices, debuglevel=debuglevel) self.resources = merge_resources_if_not_None(resources) def run(self, agent_set, resources=None, reset_attribute_value={}): self.resources.merge(resources) if agent_set.size()<=0: agent_set.get_id_attribute() if agent_set.size()<= 0: self.debug.print_debug("Nothing to be done.",2) return array([], dtype='int32') if self.upc_sequence and (self.upc_sequence.probability_class.rate_set or self.resources.get('rate_set', None)): self.resources.merge({agent_set.get_dataset_name():agent_set}) #to be compatible with old-style one-relocation_probabilities-module-per-model self.resources.merge({'agent_set':agent_set}) choices = self.upc_sequence.run(resources=self.resources) # choices have value 1 for agents that should be relocated, otherwise 0. movers_indices = where(choices>0)[0] else: movers_indices = array([], dtype='int32') if reset_attribute_value and movers_indices.size > 0: for key, value in reset_attribute_value.items(): agent_set.modify_attribute(name=key, data=resize(asarray(value), movers_indices.size), index=movers_indices) logger.log_status("Number of agents sampled based on rates: " + str(movers_indices.size)) return movers_indices def prepare_for_run(self, what=None, rate_dataset_name="rate", rate_storage=None, rate_table=None, probability_attribute=None, sample_rates=False, n=100, multiplicator=1, flush_rates=True): """ what - unused, argument kept to be compatible with old code """ from opus_core.datasets.dataset_factory import DatasetFactory from opus_core.session_configuration import SessionConfiguration if (rate_storage is None) or ((rate_table is None) and (rate_dataset_name is None)): return self.resources if not rate_dataset_name: rate_dataset_name = DatasetFactory().dataset_name_for_table(rate_table) rates = DatasetFactory().search_for_dataset(rate_dataset_name, package_order=SessionConfiguration().package_order, arguments={'in_storage':rate_storage, 'in_table_name':rate_table, } ) if probability_attribute is not None: rates.probability_attribute = probability_attribute if sample_rates: cache_storage=None if flush_rates: cache_storage=rate_storage rates.sample_rates(n=n, cache_storage=cache_storage, multiplicator=multiplicator) self.resources.merge({rate_dataset_name:rates}) #to be compatible with old-style one-relocation_probabilities-module-per-model self.resources.merge({'rate_set':rates}) return self.resources