class TimeSeriesInst(Institution): """ This institution deploys facilities based on demand curves using time series methods. """ commodities = ts.VectorString( doc="A list of commodities that the institution will manage. " + "commodity_prototype_capacity format" + " where the commoditity is what the facility supplies", tooltip="List of commodities in the institution.", uilabel="Commodities", uitype="oneOrMore") demand_eq = ts.String( doc= "This is the string for the demand equation of the driving commodity. " + "The equation should use `t' as the dependent variable", tooltip="Demand equation for driving commodity", uilabel="Demand Equation") calc_method = ts.String( doc= "This is the calculated method used to determine the supply and demand " + "for the commodities of this institution. Currently this can be ma for " + "moving average, or arma for autoregressive moving average.", tooltip="Calculation method used to predict supply/demand", uilabel="Calculation Method") record = ts.Bool( doc= "Indicates whether or not the institution should record it's output to text " + "file outputs. The output files match the name of the demand commodity of the " + "institution.", tooltip= "Boolean to indicate whether or not to record output to text file.", uilabel="Record to Text", default=False) driving_commod = ts.String( doc="Sets the driving commodity for the institution. That is the " + "commodity that no_inst will deploy against the demand equation.", tooltip="Driving Commodity", uilabel="Driving Commodity", default="POWER") steps = ts.Int( doc="The number of timesteps forward to predict supply and demand", tooltip="The number of predicted steps forward", uilabel="Timesteps for Prediction", default=1) back_steps = ts.Int( doc="This is the number of steps backwards from the current time step" + "that will be used to make the prediction. If this is set to '0'" + "then the calculation will use all values in the time series.", tooltip="", uilabel="Back Steps", default=10) supply_std_dev = ts.Double( doc="The standard deviation adjustment for the supple side.", tooltip="The standard deviation adjustment for the supple side.", uilabel="Supply Std Dev", default=0) demand_std_dev = ts.Double( doc="The standard deviation adjustment for the demand side.", tooltip="The standard deviation adjustment for the demand side.", uilabel="Demand Std Dev", default=0) demand_std_dev = ts.Double( doc="The standard deviation adjustment for the demand side.", tooltip="The standard deviation adjustment for the demand side.", uilabel="Demand Std Dev", default=0) degree = ts.Int( doc="The degree of the fitting polynomial.", tooltip="The degree of the fitting polynomial, if using calc methods" + " poly, fft, holtz-winter and exponential smoothing." + " Additionally, degree is used to as the 'period' input to " + "the stepwise_seasonal method.", uilabel="Degree Polynomial Fit / Period for stepwise_seasonal", default=1) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.commodity_supply = {} self.commodity_demand = {} self.rev_commodity_supply = {} self.rev_commodity_demand = {} self.fresh = True CALC_METHODS['ma'] = no.predict_ma CALC_METHODS['arma'] = no.predict_arma CALC_METHODS['arch'] = no.predict_arch CALC_METHODS['poly'] = do.polyfit_regression CALC_METHODS['exp_smoothing'] = do.exp_smoothing CALC_METHODS['holt_winters'] = do.holt_winters CALC_METHODS['fft'] = do.fft CALC_METHODS['sw_seasonal'] = ml.stepwise_seasonal def print_variables(self): print('commodities: %s' % self.commodity_dict) print('demand_eq: %s' % self.demand_eq) print('calc_method: %s' % self.calc_method) print('record: %s' % str(self.record)) print('steps: %i' % self.steps) print('back_steps: %i' % self.back_steps) print('supply_std_dev: %f' % self.supply_std_dev) print('demand_std_dev: %f' % self.demand_std_dev) def parse_commodities(self, commodities): """ This function parses the vector of strings commodity variable and replaces the variable as a dictionary. This function should be deleted after the map connection is fixed.""" temp = commodities commodity_dict = {} for entry in temp: # commodity, prototype, capacity, preference, constraint_commod, constraint z = entry.split('_') if len(z) < 3: raise ValueError( 'Input is malformed: need at least commodity_prototype_capacity' ) else: # append zero for all other values if not defined while len(z) < 6: z.append(0) if z[0] not in commodity_dict.keys(): commodity_dict[z[0]] = {} commodity_dict[z[0]].update({ z[1]: { 'cap': float(z[2]), 'pref': str(z[3]), 'constraint_commod': str(z[4]), 'constraint': float(z[5]) } }) else: commodity_dict[z[0]].update({ z[1]: { 'cap': float(z[2]), 'pref': str(z[3]), 'constraint_commod': str(z[4]), 'constraint': float(z[5]) } }) return commodity_dict def enter_notify(self): super().enter_notify() if self.fresh: # convert list of strings to dictionary self.commodity_dict = self.parse_commodities(self.commodities) commod_list = list(self.commodity_dict.keys()) for key, val in self.commodity_dict.items(): for key2, val2 in val.items(): if val2['constraint_commod'] != '0': commod_list.append(val2['constraint_commod']) commod_list = list(set(commod_list)) for commod in commod_list: lib.TIME_SERIES_LISTENERS["supply" + commod].append( self.extract_supply) lib.TIME_SERIES_LISTENERS["demand" + commod].append( self.extract_demand) self.commodity_supply[commod] = defaultdict(float) self.commodity_demand[commod] = defaultdict(float) self.fresh = False def decision(self): """ This is the tock method for decision the institution. Here the institution determines the difference in supply and demand and makes the the decision to deploy facilities or not. """ time = self.context.time for commod, proto_dict in self.commodity_dict.items(): diff, supply, demand = self.calc_diff(commod, time) lib.record_time_series('calc_supply' + commod, self, supply) lib.record_time_series('calc_demand' + commod, self, demand) if diff < 0: deploy_dict = solver.deploy_solver(self.commodity_supply, self.commodity_dict, commod, diff, time) for proto, num in deploy_dict.items(): for i in range(num): self.context.schedule_build(self, proto) if self.record: out_text = "Time " + str(time) + \ " Deployed " + str(len(self.children)) out_text += " supply " + \ str(self.commodity_supply[commod][time]) out_text += " demand " + \ str(self.commodity_demand[commod][time]) + "\n" with open(commod + ".txt", 'a') as f: f.write(out_text) def calc_diff(self, commod, time): """ This function calculates the different in supply and demand for a given facility Parameters ---------- time : int This is the time step that the difference is being calculated for. Returns ------- diff : double This is the difference between supply and demand at [time] supply : double The calculated supply of the supply commodity at [time]. demand : double The calculated demand of the demand commodity at [time] """ if time not in self.commodity_demand[commod]: t = 0 self.commodity_demand[commod][time] = eval(self.demand_eq) if time not in self.commodity_supply[commod]: self.commodity_supply[commod][time] = 0.0 supply = self.predict_supply(commod) demand = self.predict_demand(commod, time) diff = supply - demand return diff, supply, demand def predict_supply(self, commod): if self.calc_method in ['arma', 'ma', 'arch']: supply = CALC_METHODS[self.calc_method]( self.commodity_supply[commod], steps=self.steps, std_dev=self.supply_std_dev, back_steps=self.back_steps) elif self.calc_method in [ 'poly', 'exp_smoothing', 'holt_winters', 'fft' ]: supply = CALC_METHODS[self.calc_method]( self.commodity_supply[commod], back_steps=self.back_steps, degree=self.degree) elif self.calc_method in ['sw_seasonal']: supply = CALC_METHODS[self.calc_method]( self.commodity_supply[commod], period=self.degree) else: raise ValueError( 'The input calc_method is not valid. Check again.') return supply def predict_demand(self, commod, time): if commod == self.driving_commod: demand = self.demand_calc(time + 1) self.commodity_demand[commod][time + 1] = demand else: if self.calc_method in ['arma', 'ma', 'arch']: demand = CALC_METHODS[self.calc_method]( self.commodity_demand[commod], steps=self.steps, std_dev=self.supply_std_dev, back_steps=self.back_steps) elif self.calc_method in [ 'poly', 'exp_smoothing', 'holt_winters', 'fft' ]: demand = CALC_METHODS[self.calc_method]( self.commodity_demand[commod], back_steps=self.back_steps, degree=self.degree) elif self.calc_method in ['sw_seasonal']: demand = CALC_METHODS[self.calc_method]( self.commodity_demand[commod], period=self.degree) else: raise ValueError( 'The input calc_method is not valid. Check again.') return demand def extract_supply(self, agent, time, value, commod): """ Gather information on the available supply of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ commod = commod[6:] self.commodity_supply[commod][time] += value # update commodities # self.commodity_dict[commod] = {agent.prototype: value} def extract_demand(self, agent, time, value, commod): """ Gather information on the demand of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ commod = commod[6:] self.commodity_demand[commod][time] += value def demand_calc(self, time): """ Calculate the electrical demand at a given timestep (time). Parameters ---------- time : int The timestep that the demand will be calculated at. Returns ------- demand : The calculated demand at a given timestep. """ t = time demand = eval(self.demand_eq) return demand
class SupplyDrivenDeploymentInst(Institution): """ This institution deploys facilities based on demand curves using time series methods. """ facility_commod = ts.MapStringString( doc="A map of facilities and each of their corresponding" + " output commodities", tooltip="Map of facilities and output commodities in the " + "institution", alias=['facility_commod', 'facility', 'commod'], uilabel="Facility and Commodities") facility_capacity = ts.MapStringDouble( doc="A map of facilities and each of their corresponding" + " capacities", tooltip="Map of facilities and capacities in the " + "institution", alias=['facility_capacity', 'facility', 'capacity'], uilabel="Facility and Capacities") facility_pref = ts.MapStringString( doc="A map of facilities and each of their corresponding" + " preferences", tooltip="Map of facilities and preferences in the " + "institution", alias=['facility_pref', 'facility', 'pref'], uilabel="Facility and Preferences", default={}) facility_constraintcommod = ts.MapStringString( doc="A map of facilities and each of their corresponding" + " constraint commodity", tooltip="Map of facilities and constraint commodities in the " + "institution", alias=['facility_constraintcommod', 'facility', 'constraintcommod'], uilabel="Facility and Constraint Commodities", default={}) facility_constraintval = ts.MapStringDouble( doc="A map of facilities and each of their corresponding" + " constraint values", tooltip="Map of facilities and constraint values in the " + "institution", alias=['facility_constraintval', 'facility', 'constraintval'], uilabel="Facility and Constraint Commodity Values", default={}) calc_method = ts.String( doc="This is the calculated method used to determine " + "the supply and capacity for the commodities of " + "this institution. Currently this can be ma for " + "moving average, or arma for autoregressive " + "moving average.", tooltip="Calculation method used to predict supply/capacity", uilabel="Calculation Method") record = ts.Bool( doc="Indicates whether or not the institution " + "should record it's output to text file outputs." + "The output files match the name of the " + "demand commodity of the institution.", tooltip= "Boolean to indicate whether or not to record output to text file.", uilabel="Record to Text", default=False) installed_cap = ts.Bool( doc="True if facility deployment is governed by installed capacity. " + "False if deployment is governed by actual commodity capacity", tooltip="Boolean to indicate whether or not to use installed" + "capacity as supply", uilabel="installed cap", default=False) steps = ts.Int( doc="The number of timesteps forward to predict supply and capacity", tooltip="The number of predicted steps forward", uilabel="Timesteps for Prediction", default=1) back_steps = ts.Int(doc="This is the number of steps backwards " + "from the current time step that will " + "be used to make the prediction. If " + "this is set to '0' then the calculation " + "will use all values in the time series.", tooltip="", uilabel="Back Steps", default=10) capacity_std_dev = ts.Double( doc="The standard deviation adjustment for the capacity side.", tooltip="The standard deviation adjustment for the capacity side.", uilabel="capacity Std Dev", default=0) buffer_type = ts.MapStringString( doc= "Indicates whether the buffer is in percentage or float form, perc: %," + "float: float for each commodity", tooltip= "Capacity buffer in Percentage or float form for each commodity", alias=['buffer_type', 'commod', 'type'], uilabel="Capacity Buffer type", default={}) capacity_buffer = ts.MapStringDouble( doc="Capacity buffer size: % or float amount", tooltip="Capacity buffer amount", alias=['capacity_buffer', 'commod', 'buffer'], uilabel="Capacity Buffer", default={}) degree = ts.Int( doc="The degree of the fitting polynomial.", tooltip="The degree of the fitting polynomial, if using calc methods" + " poly, fft, holtz-winter and exponential smoothing." + " Additionally, degree is used to as the 'period' input to " + "the stepwise_seasonal method.", uilabel="Degree Polynomial Fit / Period for stepwise_seasonal", default=1) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.commodity_capacity = {} self.commodity_supply = {} self.installed_capacity = {} self.fac_commod = {} self.fresh = True CALC_METHODS['ma'] = no.predict_ma CALC_METHODS['arma'] = no.predict_arma CALC_METHODS['arch'] = no.predict_arch CALC_METHODS['poly'] = do.polyfit_regression CALC_METHODS['exp_smoothing'] = do.exp_smoothing CALC_METHODS['holt_winters'] = do.holt_winters CALC_METHODS['fft'] = do.fft CALC_METHODS['sw_seasonal'] = ml.stepwise_seasonal def print_variables(self): print('commodities: %s' % self.commodity_dict) print('calc_method: %s' % self.calc_method) print('record: %s' % str(self.record)) print('steps: %i' % self.steps) print('back_steps: %i' % self.back_steps) print('capacity_std_dev: %f' % self.capacity_std_dev) def enter_notify(self): super().enter_notify() if self.fresh: # convert list of strings to dictionary self.commodity_dict = di.build_dict(self.facility_commod, self.facility_capacity, self.facility_pref, self.facility_constraintcommod, self.facility_constraintval) for commod, proto_dict in self.commodity_dict.items(): protos = proto_dict.keys() for proto in protos: self.fac_commod[proto] = commod commod_list = list(self.commodity_dict.keys()) for commod in commod_list: self.installed_capacity[commod] = defaultdict(float) self.installed_capacity[commod][0] = 0. self.buffer_dict = di.build_buffer_dict(self.capacity_buffer, commod_list) self.buffer_type_dict = di.build_buffer_type_dict( self.buffer_type, commod_list) for commod in self.commodity_dict: # swap supply and demand for supply_inst # change demand into capacity lib.TIME_SERIES_LISTENERS["supply" + commod].append( self.extract_supply) lib.TIME_SERIES_LISTENERS["demand" + commod].append( self.extract_capacity) self.commodity_capacity[commod] = defaultdict(float) self.commodity_supply[commod] = defaultdict(float) for child in self.children: itscommod = self.fac_commod[child.prototype] self.installed_capacity[itscommod][0] += self.commodity_dict[ itscommod][child.prototype]['cap'] self.fresh = False def decision(self): """ This is the tock method for decision the institution. Here the institution determines the difference in supply and capacity and makes the the decision to deploy facilities or not. """ time = self.context.time for commod, proto_dict in self.commodity_dict.items(): diff, capacity, supply = self.calc_diff(commod, time) lib.record_time_series('calc_supply' + commod, self, supply) lib.record_time_series('calc_capacity' + commod, self, capacity) if diff < 0: if self.installed_cap: deploy_dict = solver.deploy_solver(self.installed_capacity, self.commodity_dict, commod, diff, time) else: deploy_dict = solver.deploy_solver(self.commodity_supply, self.commodity_dict, commod, diff, time) for proto, num in deploy_dict.items(): for i in range(num): self.context.schedule_build(self, proto) # update installed capacity dict for proto, num in deploy_dict.items(): self.installed_capacity[commod][time + 1] = \ self.installed_capacity[commod][time] + \ self.commodity_dict[commod][proto]['cap'] * num else: self.installed_capacity[commod][ time + 1] = self.installed_capacity[commod][time] if self.record: out_text = "Time " + str(time) + \ " Deployed " + str(len(self.children)) out_text += " capacity " + \ str(self.commodity_capacity[commod][time]) out_text += " supply " + \ str(self.commodity_supply[commod][time]) + "\n" with open(commod + ".txt", 'a') as f: f.write(out_text) for child in self.children: if child.exit_time == time: itscommod = self.fac_commod[child.prototype] self.installed_capacity[itscommod][ time + 1] -= self.commodity_dict[itscommod][ child.prototype]['cap'] def calc_diff(self, commod, time): """ This function calculates the different in capacity and supply for a given facility Parameters ---------- time : int This is the time step that the difference is being calculated for. Returns ------- diff : double This is the difference between capacity and supply at [time] capacity : double The calculated capacity of the capacity commodity at [time]. supply : double The calculated supply of the supply commodity at [time] """ if time not in self.commodity_supply[commod]: t = 0 self.commodity_supply[commod][time] = 0 if time not in self.commodity_capacity[commod]: self.commodity_capacity[commod][time] = 0.0 capacity = self.predict_capacity(commod) if self.buffer_type_dict[commod] == 'perc': supply = self.predict_supply(commod, time) * (1 + self.buffer_dict[commod]) elif self.buffer_type_dict[commod] == 'float': supply = self.predict_supply(commod, time) + self.buffer_dict[commod] else: raise Exception( 'You can only choose perc (%) or float (double) for buffer size' ) diff = capacity - supply return diff, capacity, supply def predict_capacity(self, commod): def target(incommod): if self.installed_cap: return self.installed_capacity[incommod] else: return self.commodity_capacity[incommod] if self.calc_method in ['arma', 'ma', 'arch']: capacity = CALC_METHODS[self.calc_method]( target(commod), steps=self.steps, std_dev=self.capacity_std_dev, back_steps=self.back_steps) elif self.calc_method in [ 'poly', 'exp_smoothing', 'holt_winters', 'fft' ]: capacity = CALC_METHODS[self.calc_method]( target(commod), back_steps=self.back_steps, degree=self.degree) elif self.calc_method in ['sw_seasonal']: capacity = CALC_METHODS[self.calc_method](target(commod), period=self.degree) else: raise ValueError( 'The input calc_method is not valid. Check again.') return capacity def predict_supply(self, commod, time): if self.calc_method in ['arma', 'ma', 'arch']: supply = CALC_METHODS[self.calc_method]( self.commodity_supply[commod], steps=self.steps, std_dev=self.capacity_std_dev, back_steps=self.back_steps) elif self.calc_method in [ 'poly', 'exp_smoothing', 'holt_winters', 'fft' ]: supply = CALC_METHODS[self.calc_method]( self.commodity_supply[commod], back_steps=self.back_steps, degree=self.degree) elif self.calc_method in ['sw_seasonal']: supply = CALC_METHODS[self.calc_method]( self.commodity_supply[commod], period=self.degree) else: raise ValueError( 'The input calc_method is not valid. Check again.') return supply def extract_capacity(self, agent, time, value, commod): """ Gather information on the available capacity of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ commod = commod[6:] self.commodity_capacity[commod][time] += value # update commodities # self.commodity_dict[commod] = {agent.prototype: value} def extract_supply(self, agent, time, value, commod): """ Gather information on the capacity of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ commod = commod[6:] self.commodity_supply[commod][time] += value
class udb_reactor(Facility): reactor_id = ts.Int( doc="This variable lists the reactor id of the reactors in the database ", tooltip="Reactor Id in database", uilabel="Reactor ID" ) outcommod = ts.String( doc="The commodity this institution will output", tooltip="Output commodity", uilabel="Output Commodity" ) db_path = ts.String( doc="Path to the sqlite file of udb data", tooltip="Absolute path to the udb sqlite data" ) recipe_name = ts.String( doc="if using recipe, this parameter holds the recipe name", tooltip="recipe to be used for recipe composition", default='' ) startyear = ts.Int( doc="Startyear of simulation", tooltip="Simulation Startyear" ) startmonth = ts.Int( doc="Startmonth of simulation", tooltip="Simulation Startmonth" ) use_rom = ts.Bool( doc="Boolean to use reduced-order-model for depletion" tooltip="ROM bool" default=0 ) inventory = ts.ResBufMaterialInv() ## Import pickle file into `depletion_model_dict' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def write(self, string): # for debugging purposes. with open('log.txt', 'a+') as f: f.write(string + '\n') def enter_notify(self): super().enter_notify() df = pd.read_table(self.db_path) self.reactor_assems = df.loc[df['reactor_id'] == self.reactor_id] self.reactor_assems['discharge_date'] = self.reactor_assems['discharge_date'].str[:7] self.assembly_dates = self.reactor_assems['discharge_date'].unique() # for memory del df def tick(self): year_month = self.find_year_month() if year_month in self.assembly_dates: assembly_id_list = self.reactor_assems.loc[self.reactor_assems['discharge_date'] == year_month]['assembly_id'].unique() for assembly in assembly_id_list: assem_mass = np.array( self.reactor_assems.loc[self.reactor_assems['assembly_id'] == assembly_id]['initial_uranium_kg'])[0] if self.recipe_name != '': composition = self.context.get_recipe(self.recipe_name) elif use_rom: composition = self.rom_depletion(assembly) else: composition = self.data_depletion(assembly) material = ts.Material.create(self, assem_mass, composition) self.inventory.push(material) def get_material_bids(self, requests): """ Gets material bids that want its `outcommod' an returns bid portfolio """ if self.outcommod not in requests: return reqs = requests[self.outcommod] bids = [] for req in reqs: qty = min(req.target.quantity, self.inventory.quantity) # returns if the inventory is empty if self.inventory.empty(): return # get the composition of the material next in line next_in_line = self.inventory.peek() mat = ts.Material.create_untracked(qty, next_in_line.comp()) bids.append({'request': req, 'offer': mat}) port = {"bids": bids} return port def get_material_trades(self, trades): responses = {} for trade in trades: mat_list = self.inventory.pop_n(self.inventory.count) # absorb all materials # best way is to do it separately, but idk how to do it :( for mat in mat_list[1:]: mat_list[0].absorb(mat) responses[trade] = mat_list[0] return responses def find_year_month(self): time = self.context.time year = self.startyear + time // 12 month = self.startmonth + time % 12 if month < 10: return (str(year) + '-0' + str(month)) else: return (str(year) + '-' + str(month)) def rom_depletion(self, assembly_id): enr_bu = self.reactor_assems.loc[self.reactor_assems['assembly_id'] == assembly_id][[ 'initial_enrichment', 'discharge_burnup']] enr_bu = np.array(enr_bu)[0] composition = {} for iso, model in self.depletion_model_dict.items(): composition[iso] = model.predict(enr_bu)[0] def data_depletion(self, assembly_id): assem_data = self.reactor_assems.loc[self.reactor_assems['assembly_id'] == assembly_id][['name', 'total_mass_g']] assem_data['comp'] = assem_data['total_mass_g'] / sum(assem_data['total_mass_g']) composition = {} for indx, row in assem_data.iterrows(): composition[row['name']] = row['comp'] return composition
class NOInst(Institution): """ This institution deploys facilities based on demand curves using Non Optimizing (NO) methods. """ prototypes = ts.VectorString( doc="A list of prototypes that the institution will draw upon to fit" + "the demand curve", tooltip="List of prototypes the institution can use to meet demand", uilabel="Prototypes", uitype="oneOrMore") growth_rate = ts.Double( doc="This value represents the growth rate that the institution is " + "attempting to meet.", tooltip="Growth rate of growth commodity", uilabel="Growth Rate", default="0.02") supply_commod = ts.String( doc= "The commodity this institution will be monitoring for supply growth.", tooltip="Supply commodity", uilabel="Supply Commodity") demand_commod = ts.String( doc= "The commodity this institution will be monitoring for demand growth.", tooltip="Growth commodity", uilabel="Growth Commodity") initial_demand = ts.Double(doc="The initial power of the facility", tooltip="Initital demand", uilabel="Initial demand") calc_method = ts.String( doc= "This is the calculated method used to determine the supply and demand " + "for the commodities of this institution. Currently this can be ma for " + "moving average, or arma for autoregressive moving average.", tooltip="Calculation method used to predict supply/demand", uilabel="Calculation Method") record = ts.Bool( doc= "Indicates whether or not the institution should record it's output to text " + "file outputs. The output files match the name of the demand commodity of the " + "institution.", tooltip= "Boolean to indicate whether or not to record output to text file.", uilabel="Record to Text", default=False) supply_std_dev = ts.Double( doc="The number of standard deviations off mean for ARMA predictions", tooltip="Std Dev off mean for ARMA", uilabel="Supple Std Dev", default=0.) demand_std_dev = ts.Double( doc="The number of standard deviations off mean for ARMA predictions", tooltip="Std Dev off mean for ARMA", uilabel="Demand Std Dev", default=0.) steps = ts.Int( doc="The number of timesteps forward for ARMA or order of the MA", tooltip="Std Dev off mean for ARMA", uilabel="Demand Std Dev", default=1) back_steps = ts.Int( doc="This is the number of steps backwards from the current time step" + "that will be used to make the prediction. If this is set to '0'" + "then the calculation will use all values in the time series.", tooltip="", uilabel="", default="5") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.commodity_supply = defaultdict(float) self.commodity_demand = defaultdict(float) self.fac_supply = {} CALC_METHODS['ma'] = self.moving_avg CALC_METHODS['arma'] = self.predict_arma CALC_METHODS['arch'] = self.predict_arch def enter_notify(self): super().enter_notify() lib.TIME_SERIES_LISTENERS[self.supply_commod].append( self.extract_supply) lib.TIME_SERIES_LISTENERS[self.demand_commod].append( self.extract_demand) def tock(self): """ This is the tock method for the institution. Here the institution determines the difference in supply and demand and makes the the decision to deploy facilities or not. """ time = self.context.time diff, supply, demand = self.calc_diff(time) if diff < 0: proto = random.choice(self.prototypes) prod_rate = self.commodity_supply[time] / len(self.children) number = np.ceil(-1 * diff / prod_rate) i = 0 while i < number: self.context.schedule_build(self, proto) i += 1 if self.record: with open(self.demand_commod + ".txt", 'a') as f: f.write("Time " + str(time) + " Deployed " + str(len(self.children)) + " supply " + str(self.commodity_supply[time]) + " demand " + str(self.commodity_demand[time]) + "\n") def calc_diff(self, time): """ This function calculates the different in supply and demand for a given facility Parameters ---------- time : int This is the time step that the difference is being calculated for. Returns ------- diff : double This is the difference between supply and demand at [time] supply : double The calculated supply of the supply commodity at [time]. demand : double The calculated demand of the demand commodity at [time] """ try: supply = CALC_METHODS[self.calc_method]( self.commodity_supply, steps=self.steps, std_dev=self.supply_std_dev) except (ValueError, np.linalg.linalg.LinAlgError): supply = CALC_METHODS['ma'](self.commodity_supply) if not self.commodity_demand: self.commodity_demand[time] = self.initial_demand if self.demand_commod == 'power': demand = self.demand_calc(time + 1) self.commodity_demand[time] = demand try: demand = CALC_METHODS[self.calc_method]( self.commodity_demand, steps=self.steps, std_dev=self.demand_std_dev) except (np.linalg.linalg.LinAlgError, ValueError): demand = CALC_METHODS['ma'](self.commodity_demand) diff = supply - demand return diff, supply, demand def extract_supply(self, agent, time, value): """ Gather information on the available supply of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ self.commodity_supply[time] += value def extract_demand(self, agent, time, value): """ Gather information on the demand of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ self.commodity_demand[time] += value def demand_calc(self, time): """ Calculate the electrical demand at a given timestep (time). Parameters ---------- time : int The timestep that the demand will be calculated at. Returns ------- demand : The calculated demand at a given timestep. """ timestep = self.context.dt time = time * timestep demand = self.initial_demand * ( (1.0 + self.growth_rate)**(time / 3.154e+7)) return demand def moving_avg(self, ts, steps=1, std_dev=0, back_steps=5): """ Calculates the moving average of a previous [order] entries in timeseries [ts]. It will automatically reduce the order if the length of ts is shorter than the order. Parameters: ----------- ts : Array of doubles An array of time series data to be used for the arma prediction order : int The number of values used for the moving average. Returns ------- x : The moving average calculated by the function. """ supply = np.array(list(ts.values())) if steps >= len(supply): steps = len(supply) * -1 else: steps *= -1 x = np.average(supply[steps:]) return x def predict_arma(self, ts, steps=1, std_dev=0, back_steps=5): """ Predict the value of supply or demand at a given time step using the currently available time series data. This method impliments an ARMA calculation to perform the prediciton. Parameters: ----------- ts : Array of doubles An array of time series data to be used for the arma prediction time: int The number of timesteps to predict forward. Returns: -------- x : Predicted value for the time series at chosen timestep (time). """ v = list(ts.values()) fit = sm.tsa.ARMA(v, (1, 0)).fit(disp=-1) forecast = fit.forecast(steps) x = fit[0][steps - 1] + fit[1][steps - 1] * std_dev return x def predict_arch(self, ts, steps=1, std_dev=0, back_steps=5): """ Predict the value of supply or demand at a given time step using the currently available time series data. This method impliments an ARCH calculation to perform the prediciton. """ f_obs = len(ts) - back_steps if f_obs < 0 or back_steps == 0: f_obs = 0 model = arch_model(ts) fit = model.fit(disp='nothing', update_freq=0, show_warning=False, first_obs=f_obs) forecast = fit.forecast(horizon=steps) x = forecast.mean.get('h.1')[len(ts) - 1] std_dev = math.sqrt( forecast.variance.get('h.1')[len(ts) - 1]) * std_dev return x + std_dev
class DemandDrivenDeploymentInst(Institution): """ This institution deploys facilities based on demand curves using time series methods. """ facility_commod = ts.MapStringString( doc="A map of facilities and each of their corresponding" + " output commodities", tooltip="Map of facilities and output commodities in the " + "institution", alias=['facility_commod', 'facility', 'commod'], uilabel="Facility and Commodities" ) facility_capacity = ts.MapStringDouble( doc="A map of facilities and each of their corresponding" + " capacities", tooltip="Map of facilities and capacities in the " + "institution", alias=['facility_capacity', 'facility', 'capacity'], uilabel="Facility and Capacities" ) facility_pref = ts.MapStringString( doc="A map of facilities and each of their corresponding" + " preferences", tooltip="Map of facilities and preferences in the " + "institution", alias=['facility_pref', 'facility', 'pref'], uilabel="Facility and Preferences", default={} ) facility_constraintcommod = ts.MapStringString( doc="A map of facilities and each of their corresponding" + " constraint commodity", tooltip="Map of facilities and constraint commodities in the " + "institution", alias=['facility_constraintcommod', 'facility', 'constraintcommod'], uilabel="Facility and Constraint Commodities", default={} ) facility_constraintval = ts.MapStringDouble( doc="A map of facilities and each of their corresponding" + " constraint values", tooltip="Map of facilities and constraint values in the " + "institution", alias=['facility_constraintval', 'facility', 'constraintval'], uilabel="Facility and Constraint Commodity Values", default={} ) facility_sharing = ts.MapStringDouble( doc="A map of facilities that share a commodity", tooltip="Map of facilities and percentages of sharing", alias=['facility_sharing', 'facility', 'percentage'], uilabel="Facility and Percentages", default={} ) demand_eq = ts.String( doc="This is the string for the demand equation of the driving commodity. " + "The equation should use `t' as the dependent variable", tooltip="Demand equation for driving commodity", uilabel="Demand Equation") calc_method = ts.String( doc="This is the calculated method used to determine the supply and demand " + "for the commodities of this institution. Currently this can be ma for " + "moving average, or arma for autoregressive moving average.", tooltip="Calculation method used to predict supply/demand", uilabel="Calculation Method") record = ts.Bool( doc="Indicates whether or not the institution should record it's output to text " + "file outputs. The output files match the name of the demand commodity of the " + "institution.", tooltip="Boolean to indicate whether or not to record output to text file.", uilabel="Record to Text", default=False) driving_commod = ts.String( doc="Sets the driving commodity for the institution. That is the " + "commodity that no_inst will deploy against the demand equation.", tooltip="Driving Commodity", uilabel="Driving Commodity", default="POWER" ) installed_cap = ts.Bool( doc="True if facility deployment is governed by installed capacity. " + "False if deployment is governed by actual commodity supply", tooltip="Boolean to indicate whether or not to use installed" + "capacity as supply", uilabel="installed cap", default=False) steps = ts.Int( doc="The number of timesteps forward to predict supply and demand", tooltip="The number of predicted steps forward", uilabel="Timesteps for Prediction", default=1 ) back_steps = ts.Int( doc="This is the number of steps backwards from the current time step" + "that will be used to make the prediction. If this is set to '0'" + "then the calculation will use all values in the time series.", tooltip="", uilabel="Back Steps", default=5) supply_std_dev = ts.Double( doc="The standard deviation adjustment for the supple side.", tooltip="The standard deviation adjustment for the supple side.", uilabel="Supply Std Dev", default=0 ) buffer_type = ts.MapStringString( doc="Indicates whether the buffer is a relative or absolute value," + "rel: % value, abs: double value, for each commodity", tooltip="Supply buffer as a relative or absolute value for," + "each commodity", alias=[ 'buffer_type', 'commod', 'type'], uilabel="Supply Buffer type", default={}) supply_buffer = ts.MapStringDouble( doc="Supply buffer size: relative or absolute value ", tooltip="Supply buffer Amount.", alias=['supply_buffer', 'commod', 'buffer'], uilabel="Supply Buffer", default={} ) degree = ts.Int( doc="The degree of the fitting polynomial.", tooltip="The degree of the fitting polynomial, if using calc methods" + " poly, fft, holtz-winter and exponential smoothing." + " Additionally, degree is used to as the 'period' input to " + "the stepwise_seasonal method.", uilabel="Degree Polynomial Fit / Period for stepwise_seasonal", default=1 ) os_time = ts.Int( doc="The number of oversupply timesteps before decommission", tooltip="", uilabel="Oversupply Time Limit", default=120 ) os_int = ts.Int( doc="The number of facilities over capacity " + "for a given commodity that is allowed. i.e If this" + " value is 1. One facility capacity over demand is considered" + " an oversupplied situtation.", tooltip="", uilabel="Oversupply Fac Limit", default=1 ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.commodity_supply = {} self.commodity_demand = {} self.installed_capacity = {} self.fac_commod = {} self.commod_os = {} self.fresh = True CALC_METHODS['ma'] = no.predict_ma CALC_METHODS['arma'] = no.predict_arma CALC_METHODS['arch'] = no.predict_arch CALC_METHODS['poly'] = do.polyfit_regression CALC_METHODS['exp_smoothing'] = do.exp_smoothing CALC_METHODS['holt_winters'] = do.holt_winters CALC_METHODS['fft'] = do.fft CALC_METHODS['sw_seasonal'] = ml.stepwise_seasonal def print_variables(self): print('commodities: %s' % self.commodity_dict) print('demand_eq: %s' % self.demand_eq) print('calc_method: %s' % self.calc_method) print('record: %s' % str(self.record)) print('steps: %i' % self.steps) print('back_steps: %i' % self.back_steps) print('supply_std_dev: %f' % self.supply_std_dev) def enter_notify(self): super().enter_notify() if self.fresh: # convert input into dictionary self.commodity_dict = di.build_dict( self.facility_commod, self.facility_capacity, self.facility_pref, self.facility_constraintcommod, self.facility_constraintval, self.facility_sharing) for commod, proto_dict in self.commodity_dict.items(): self.commod_os[commod] = 0 protos = proto_dict.keys() for proto in protos: self.fac_commod[proto] = commod self.commod_list = list(self.commodity_dict.keys()) for commod in self.commod_list: self.installed_capacity[commod] = defaultdict(float) self.installed_capacity[commod][0] = 0. for commod, commod_dict in self.commodity_dict.items(): for proto, proto_dict in commod_dict.items(): if proto_dict['constraint_commod'] != '0': self.commod_list.append( proto_dict['constraint_commod']) for commod, commod_dict in self.commodity_dict.items(): tot = 0 for proto, proto_dict in commod_dict.items(): tot += proto_dict['share'] if tot != 0 and tot != 100: print("Share preferences do not add to 100") raise Exception() self.buffer_dict = di.build_buffer_dict(self.supply_buffer, self.commod_list) self.buffer_type_dict = di.build_buffer_type_dict( self.buffer_type, self.commod_list) for commod in self.commod_list: lib.TIME_SERIES_LISTENERS["supply" + commod].append(self.extract_supply) lib.TIME_SERIES_LISTENERS["demand" + commod].append(self.extract_demand) self.commodity_supply[commod] = defaultdict(float) self.commodity_demand[commod] = defaultdict(float) self.commod_mins = solver.find_mins(self.commodity_dict) for child in self.children: if child.prototype not in self.fac_commod: continue itscommod = self.fac_commod[child.prototype] self.installed_capacity[itscommod][0] += \ self.commodity_dict[itscommod][child.prototype]['cap'] self.fresh = False def decision(self): """ This is the tock method for decision the institution. Here the institution determines the difference in supply and demand and makes the the decision to deploy facilities or not. """ time = self.context.time for commod, proto_dict in self.commodity_dict.items(): diff, supply, demand = self.calc_diff(commod, time) lib.record_time_series('calc_supply' + commod, self, supply) lib.record_time_series('calc_demand' + commod, self, demand) if diff < 0: if self.installed_cap: deploy_dict, self.commodity_dict = solver.deploy_solver( self.installed_capacity, self.commodity_dict, commod, diff, time) else: deploy_dict, self.commodity_dict = solver.deploy_solver( self.commodity_supply, self.commodity_dict, commod, diff, time) for proto, num in deploy_dict.items(): for i in range(num): self.context.schedule_build(self, proto) # update installed capacity dict self.installed_capacity[commod][time + 1] = \ self.installed_capacity[commod][time] for proto, num in deploy_dict.items(): self.installed_capacity[commod][time + 1] += \ self.commodity_dict[commod][proto]['cap'] * num else: self.installed_capacity[commod][time + 1] = self.installed_capacity[commod][time] os_limit = self.commod_mins[commod] * self.os_int if diff > os_limit: self.commod_os[commod] += 1 else: self.commod_os[commod] = 0 if diff > os_limit and self.commod_os[commod] > self.os_time: solver.decommission_oldest(self, self.commodity_dict[commod], diff, commod, time) if self.record: out_text = "Time " + str(time) + \ " Deployed " + str(len(self.children)) out_text += " supply " + \ str(self.commodity_supply[commod][time]) out_text += " demand " + \ str(self.commodity_demand[commod][time]) + "\n" with open(commod + ".txt", 'a') as f: f.write(out_text) for child in self.children: if child.exit_time == time: itscommod = self.fac_commod[child.prototype] self.installed_capacity[itscommod][time + 1] -= \ self.commodity_dict[itscommod][child.prototype]['cap'] def calc_diff(self, commod, time): """ This function calculates the different in supply and demand for a given facility Parameters ---------- time : int This is the time step that the difference is being calculated for. Returns ------- diff : double This is the difference between supply and demand at [time] supply : double The calculated supply of the supply commodity at [time]. demand : double The calculated demand of the demand commodity at [time] """ if time not in self.commodity_demand[commod]: if commod == self.driving_commod: t = time self.commodity_demand[commod][time] = eval(self.demand_eq) else: self.commodity_demand[commod][time] = 0.0 if time not in self.commodity_supply[commod]: self.commodity_supply[commod][time] = 0.0 supply = self.predict_supply(commod) if self.buffer_type_dict[commod] == 'rel': demand = self.predict_demand( commod, time) * (1 + self.buffer_dict[commod]) elif self.buffer_type_dict[commod] == 'abs': demand = self.predict_demand( commod, time) + self.buffer_dict[commod] else: raise Exception( 'You can only choose rel or abs types for buffer type') diff = supply - demand return diff, supply, demand def predict_supply(self, commod): def target(incommod): if self.installed_cap: return self.installed_capacity[incommod] else: return self.commodity_supply[incommod] if self.calc_method in ['arma', 'ma', 'arch']: supply = CALC_METHODS[self.calc_method](target(commod), steps=self.steps, std_dev=self.supply_std_dev, back_steps=self.back_steps) elif self.calc_method in ['poly', 'exp_smoothing', 'holt_winters', 'fft']: supply = CALC_METHODS[self.calc_method](target(commod), back_steps=self.back_steps, degree=self.degree, steps=self.steps) elif self.calc_method in ['sw_seasonal']: supply = CALC_METHODS[self.calc_method]( target(commod), period=self.degree) else: raise ValueError( 'The input calc_method is not valid. Check again.') return supply def predict_demand(self, commod, time): if commod == self.driving_commod: demand = self.demand_calc(time + 1) self.commodity_demand[commod][time + 1] = demand else: if self.calc_method in ['arma', 'ma', 'arch']: demand = CALC_METHODS[self.calc_method](self.commodity_demand[commod], steps=self.steps, std_dev=self.supply_std_dev, back_steps=self.back_steps) elif self.calc_method in ['poly', 'exp_smoothing', 'holt_winters', 'fft']: demand = CALC_METHODS[self.calc_method](self.commodity_demand[commod], back_steps=self.back_steps, degree=self.degree, steps=self.steps) elif self.calc_method in ['sw_seasonal']: demand = CALC_METHODS[self.calc_method]( self.commodity_demand[commod], period=self.degree) else: raise ValueError( 'The input calc_method is not valid. Check again.') return demand def extract_supply(self, agent, time, value, commod): """ Gather information on the available supply of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ commod = commod[6:] self.commodity_supply[commod][time] += value # update commodities # self.commodity_dict[commod] = {agent.prototype: value} def extract_demand(self, agent, time, value, commod): """ Gather information on the demand of a commodity over the lifetime of the simulation. Parameters ---------- agent : cyclus agent This is the agent that is making the call to the listener. time : int Timestep that the call is made. value : object This is the value of the object being recorded in the time series. """ commod = commod[6:] self.commodity_demand[commod][time] += value def demand_calc(self, time): """ Calculate the electrical demand at a given timestep (time). Parameters ---------- time : int The timestep that the demand will be calculated at. Returns ------- demand : The calculated demand at a given timestep. """ t = time demand = eval(self.demand_eq) return demand