How to use is_rps method in yandex-tank

Best Python code snippet using yandex-tank

grid_sim_linear_program.py

Source:grid_sim_linear_program.py Github

copy

Full Screen

1# Copyright 2017 Google Inc.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""Simulate cost-optimal electrical grid construction under different policies.15Code contains GridElements: Power Sources, Demands and Storage. Grid16Elements are placed in different grid regions. Grid regions are17separated from each other so only sources with grid_region_id == x can18power Demands with grid_region_id == x19The costs of constructing GridElements are based upon:20 nameplate_unit_cost: The cost to build one unit (e.g. Megawatt) of power.21 variable_unit_cost: The cost to provide one unit of power over time.22 (e.g. Megawatt-Hour)23The code simulates the grid over multiple time-slices. e.g. Hourly24over a one year period which would map to 24 * 365 = 8760 time-slices.25The code is based upon a linear-program which contains:26 - An objective which is to minimize costs.27 - Constraints which must be met before the solution can converge.28 - conserve_power_constraint: Ensure that sum(power[t]) >=29 demand[t] for all t in each grid-region30This code will work with any set of consistent units. For the31purposes of documentation, the units chosen are:32 Power: Megawatts33 Time: Hours34 (Derived) Energy = Power * Time => Megawatt-Hours35 Cost: Dollars ($)36 CO2 Emissions: Tonnes37 (Derived) CO2 Emitted per Energy => Tonnes / Megawatt-Hours38 Carbon Tax: $ / Tonnes39"""40import logging41import numpy as np42from ortools.linear_solver import pywraplp43class GridSimError(RuntimeError):44 pass45class DemandNotSatisfiedError(GridSimError):46 pass47class RpsExceedsDemandError(GridSimError):48 pass49class RpsCreditExceedsSourcesError(GridSimError):50 pass51class StorageExceedsDemandError(GridSimError):52 pass53class RpsPercentNotMetError(GridSimError):54 pass55class Constraint(object):56 """Holds an LP Constraint object with extra debugging information.57 Attributes:58 constraint: underlying pywraplp.Constraint object59 name: name of constraint60 formula: hashtable that maps names of variables to coefficients61 pywraplp.Constraint doesn't surface a list of variables/coefficients, so62 we have to keep track ourselves.63 """64 def __init__(self, lp, lower_bound, upper_bound, name=None, debug=False):65 """Initializes Constraint.66 Args:67 lp: LinearProgramContainer that wraps the LP solver which68 creates the constraint.69 lower_bound: (float) Lower bound on product between coeffs and variables.70 upper_bound: (float) Upper bound on product between coeffs and variables.71 name: Optional human readable string.72 debug: Boolean which if set, logs constraint info.73 """74 self.constraint = lp.solver.Constraint(lower_bound, upper_bound)75 self.name = name76 self.formula = {}77 self.debug = debug78 if self.debug:79 logging.debug('CONSTRAINT: %f <= %s <= %f',80 lower_bound, name, upper_bound)81 def set_coefficient(self, variable, coefficient):82 """Adds variable * coefficient to LP Coefficient.83 Wraps pywrap.SetCoefficient(variable, coefficient) method and84 saves variable, coefficient to formula dict.85 After calling this method, Objective += variable * coefficient86 Args:87 variable: (Lp Variable) The Variable multiplicand.88 coefficient: (float) The coefficient multiplicand.89 """90 self.constraint.SetCoefficient(variable, coefficient)91 self.formula[variable.name()] = coefficient92 if self.debug:93 logging.debug('%s += %s * %f', self.name, variable.name(), coefficient)94class Objective(object):95 """Holds an LP Objective object with extra debugging information.96 Attributes:97 objective: Underlying pywraplp.Objective object.98 """99 def __init__(self, lp, minimize=True):100 """Initializes Objective.101 Args:102 lp: LinearProgramContainer that wraps the LP solver which103 creates the Objective.104 minimize: boolean, True if objective should be minimized105 otherwise objective is maximizied.106 """107 self.objective = lp.solver.Objective()108 self.formula = {}109 if minimize:110 self.objective.SetMinimization()111 else:112 self.objective.SetMaximization()113 def set_coefficient(self, variable, coefficient):114 """Adds variable * coefficient to LP Objective.115 Wraps pywrap.SetCoefficient(variable, coefficient) method and116 saves variable, coefficient to formula dict.117 After calling this method, Objective += variable * coefficient118 Args:119 variable: (Lp Variable) The Variable multiplicand.120 coefficient: (float) The coefficient multiplicand.121 """122 self.objective.SetCoefficient(variable, coefficient)123 self.formula[variable.name()] = coefficient124 def value(self):125 return self.objective.Value()126class GridDemand(object):127 """Simple place-holder object which represents load on the grid."""128 def __init__(self,129 name,130 grid_region_id=0):131 """Initializes GridDemand object.132 Args:133 name: name of the demand object134 grid_region_id: An int specifying the grid region of the demand.135 Only sources with the same grid_region_id can power this demand.136 """137 self.name = name138 self.grid_region_id = grid_region_id139class GridSource(object):140 """Denotes Costs, co2, region, power and energy limitations of a power source.141 Grid Sources may either be dispatchable or non-dispatchable.142 - Dispatchable sources may power at any time, e.g. fossil fuel plants.143 - Non-dispatchable sources are dependent on the environment to144 generate power. e.g. Solar or Wind plants.145 If there is a time-slice power profile indexed by the same name as146 this source in LinearProgramContainer.profiles. The source is147 considered Non-dispatchable. Otherwise, it is considered dispatchable.148 Attributes:149 name: (str) name of the object.150 nameplate_unit_cost: (float) Cost to build a unit of151 dispatchable power. ($ / Megawatt of capacity)152 variable_unit_cost: (float) Cost to supply a unit of dispatchable power153 per time. ($ / Megawatt-Hour)154 grid_region_id: An int specifying the grid region of the source.155 Only demands with the same grid_region_id can sink the power156 from this source.157 max_power: (float) Optional Maximum power which object can supply.158 (Megawatt). Set < 0 if there is no limit.159 max_energy: (float) Optional maximum energy which object can160 supply. (Megawatt-Hours) Set < 0 if there is no limit.161 co2_per_electrical_energy: (float) (Tonnes of CO2 / Megawatt Hour).162 power_coefficient: (float) ratio of how much power is supplied by163 object vs. how much power gets on the grid. 0 <164 power_coefficient < 1. Nominally 1.0.165 is_rps_source: Boolean which denotes if the source is included166 in the Renewable Portfolio Standard.167 solver: Either a _GridSourceDispatchableSolver or168 _GridSourceNonDispatchableSolver. Used to setup LP169 Constraints, Objectives and variables for the source and to170 report results.171 timeslice_variables: An array of LP variables, one per time-slice172 of simulation. Array is mapped so that variable for173 time-slice t is at index t.174 e.g.175 Variable for first time-slice is timeslice_variable[0].176 Variable for last time-slice is timeslice_variable[-1].177 Variable for time-slice at time t is timeslice_variable[t].178 Only gets declared if GridSource is a DispatchableSource.179 nameplate_variable: LP variable representing the nameplate or180 maximum power the GridSource can output at any given181 time.182 """183 def __init__(184 self,185 name,186 nameplate_unit_cost,187 variable_unit_cost,188 grid_region_id=0,189 max_power=-1.0,190 max_energy=-1.0,191 co2_per_electrical_energy=0,192 power_coefficient=1.0,193 is_rps_source=False194 ):195 """Sets characteristics of a GridSource object.196 Args:197 name: (str) name of the object.198 nameplate_unit_cost: (float) Cost to build a unit of199 dispatchable power. ($ / Megawatt of capacity)200 variable_unit_cost: (float) Cost to supply a unit of dispatchable power201 per time. ($ / Megawatt-Hour)202 grid_region_id: An int specifying the grid region of the demand.203 Only demands with the same grid_region_id can sink the power204 from this source.205 max_power: (float) Maximum power which object can supply. (Megawatt)206 max_energy: (float) Maximum energy which object can207 supply. (Megawatt-Hours)208 co2_per_electrical_energy: (float) (Tonnes of CO2 / Megawatt Hour).209 power_coefficient: (float) ratio of how much power is supplied by210 object vs. how much power gets on the grid. 0 <211 power_coefficient < 1. Nominally 1.0.212 is_rps_source: Boolean which denotes if the source is included213 in the Renewable Portfolio Standard.214 """215 self.name = name216 self.nameplate_unit_cost = nameplate_unit_cost217 self.variable_unit_cost = variable_unit_cost218 self.max_energy = max_energy219 self.max_power = max_power220 self.grid_region_id = grid_region_id221 self.co2_per_electrical_energy = co2_per_electrical_energy222 self.power_coefficient = power_coefficient223 self.is_rps_source = is_rps_source224 self.solver = None225 self.timeslice_variables = None226 self.nameplate_variable = None227 def configure_lp_variables_and_constraints(self, lp):228 """Declare lp variables, and set constraints.229 Args:230 lp: The LinearProgramContainer.231 Defers to self.solver which properly configures variables and232 constraints in this object.233 See Also:234 _GridSourceDispatchableSolver, _GridSourceNonDispatchableSolver235 """236 self.solver.configure_lp_variables_and_constraints(lp)237 def post_process(self, lp):238 """Update lp post_processing result variables.239 This is done post lp.solve() so that sanity data checks can be done240 on RPS before returning results.241 Args:242 lp: The LinearProgramContainer where the post processing variables reside.243 """244 if lp.rps_percent > 0.0 and self.is_rps_source:245 lp.rps_total[self.grid_region_id] += self.get_solution_values()246 else:247 lp.non_rps_total[self.grid_region_id] += self.get_solution_values()248 def get_solution_values(self):249 """Gets the linear program solver results.250 Must be called after lp.solve() to ensure solver has properly251 converged and has generated results.252 Returns:253 np.array of solutions for each timeslice variable.254 """255 return self.solver.get_solution_values()256 def get_nameplate_solution_value(self):257 """Gets the linear program solver results for nameplate.258 Must be called after lp.solve() to ensure solver has properly259 converged and has generated results.260 Raises:261 RuntimeError: If called before LinearProgramContainer.solve().262 Returns:263 Float value representing solved nameplate value.264 """265 nameplate_variable = self.nameplate_variable266 if nameplate_variable is None:267 raise RuntimeError('Get_nameplate_solution_value called before solve().')268 return nameplate_variable.solution_value()269class _GridSourceDispatchableSolver(object):270 """Power Source which can provide power at any time.271 Attributes:272 source: GridSource object where self generates LP variables273 """274 def __init__(self, source):275 self.source = source276 def configure_lp_variables_and_constraints(self, lp):277 """Declare lp variables, and set constraints in grid_source.278 Args:279 lp: The LinearProgramContainer.280 Variables Declared include:281 - timeslice variables: represent how much power the source is282 outputting at each time-slice.283 - nameplate variable: represents the maximum power sourced.284 The values of these variables are solved by the linear program to285 optimize costs subject to some constraints.286 The overall objective is to minimize cost. Herein, the overall287 cost is increased by:288 - nameplate cost: nameplate_unit_cost * nameplate variable289 - variable cost: variable_unit_cost * sum(timeslice_variables)290 - carbon cost: lp.carbon_tax * sum(timeslice_variables) *291 co2_per_electrical_energy292 Since variable and carbon costs accrue on a periodic basis, we293 multiply them by lp.cost_of_money to make periodic and294 one-time costs comparable.295 Constraints created / modified here include:296 - Maximum Energy: Ensure sum timeslice-variables < max_energy if297 self.max_energy >= 0.298 This constraint is only for sources where there are limits299 to the total amount of generation which can be built.300 E.g. There are only a limited number of places where one can301 build hydropower.302 - Maximum Power: Ensure no timeslice-variables > max_power if303 self.max_power is >= 0.304 This constraint is only for sources where there are limits305 to the maximum amount of power which can be built.306 E.g. hydropower which can only discharge at a maximum rate.307 - Conserve Power: Ensure that sum(power) > demand for all308 time-slices. Colloquially called "Keeping the Lights on."309 - Ensure nameplate variable > power(t) for all t. We must make310 sure that we've priced out a plant which can supply the311 requested power.312 """313 source = self.source314 # setup LP variables.315 source.timeslice_variables = lp.declare_timeslice_variables(316 source.name,317 source.grid_region_id)318 source.nameplate_variable = lp.declare_nameplate_variable(319 source.name,320 source.grid_region_id)321 solver = lp.solver322 # Configure maximum energy if it is >= 0. Otherwise do not323 # create a constraint.324 max_energy_constraint = (lp.constraint(0.0, source.max_energy)325 if source.max_energy >= 0 else None)326 # Configure maximum nameplate if it is >= 0. Otherwise do not327 # create a constraint.328 max_power = source.max_power329 if max_power >= 0:330 lp.constraint(0.0, max_power).set_coefficient(331 source.nameplate_variable, 1.0332 )333 # Total_cost includes nameplate cost.334 cost_objective = lp.minimize_costs_objective335 cost_objective.set_coefficient(source.nameplate_variable,336 source.nameplate_unit_cost)337 # Add timeslice variables to coefficients.338 for t, var in enumerate(source.timeslice_variables):339 # Total_cost also includes variable and carbon cost.340 variable_coef = ((source.variable_unit_cost +341 source.co2_per_electrical_energy * lp.carbon_tax) *342 lp.cost_of_money)343 cost_objective.set_coefficient(var, variable_coef)344 # Keep the lights on at all times. Power_coefficient is usually345 # 1.0, but is -1.0 for GridStorage.sink and discharge_efficiency346 # for GridStorage.source.347 lp.conserve_power_constraint[source.grid_region_id][t].set_coefficient(348 var, source.power_coefficient)349 # Constrain rps_credit if needed.350 if source.is_rps_source:351 lp.rps_source_constraints[source.grid_region_id][t].set_coefficient(352 var, source.power_coefficient)353 # Ensure total energy is less than source.max_energy.354 if max_energy_constraint is not None:355 max_energy_constraint.set_coefficient(var, 1.0)356 # Ensure power doesn't exceed source.max_power.357 if max_power >= 0:358 lp.constraint(0.0, max_power).set_coefficient(var, 1.0)359 # Nameplate must be bigger than largest power.360 # If nameplate_unit_cost > 0, Cost Optimization will push361 # Nameplate near max(timeslice_variables).362 nameplate_constraint = lp.constraint(0.0, solver.infinity())363 nameplate_constraint.set_coefficient(var, -1.0)364 nameplate_constraint.set_coefficient(source.nameplate_variable, 1.0)365 # Constrain maximum nameplate if max_power is set.366 if source.max_power >= 0:367 lp.constraint(0.0, source.max_power).set_coefficient(368 source.nameplate_variable,369 1.0)370 def get_solution_values(self):371 """Gets the linear program solver results.372 Must be called after lp.solve() to ensure solver has properly373 converged and has generated results.374 Raises:375 RuntimeError: If called before LinearProgramContainer.solve().376 Returns:377 np.array of solutions for each timeslice variable.378 """379 timeslice_variables = self.source.timeslice_variables380 if timeslice_variables is None:381 raise RuntimeError('get_solution_values called before solve.')382 return np.array([v.solution_value()383 for v in timeslice_variables])384class _GridSourceNonDispatchableSolver(object):385 """Power Source which can provide nameplate multiple of its profile.386 Attributes:387 source: GridSource object where self generates LP variables388 profile: pandas Series which represents what fraction of the389 nameplate the source can provide at any given time.390 """391 def __init__(self, source, profile):392 self.source = source393 # check profile isn't all zeros394 if (profile.values == 0.0).all():395 raise ValueError('%s profile may not be all zero.' %source.name)396 self.profile = source.power_coefficient * profile / max(profile)397 def configure_lp_variables_and_constraints(self, lp):398 """Declare lp variables, and set constraints in grid_source.399 Args:400 lp: The LinearProgramContainer.401 Variables Declared include:402 - nameplate variable: represents the maximum power sourced.403 The values of these variables are solved by the linear program to404 optimize costs subject to some constraints.405 The overall objective is to minimize cost. Herein, the overall406 cost is increased by:407 - nameplate cost: nameplate_unit_cost * nameplate variable408 - variable cost: variable_unit_cost * nameplate variable * sum(profile)409 - carbon cost: lp.carbon_tax * nameplate variable * sum(profile)410 Since variable and carbon costs accrue on a yearly basis, we411 multiply them by lp.cost_of_money to make yearly and412 one-time costs comparable.413 Constraints created / modified here include:414 - Maximum Energy: Ensure nameplate * sum(profile) < max_energy if415 self.max_energy >= 0.416 This constraint is only for sources where there are limits417 to the total amount of generation which can be built.418 E.g. There are only a limited number of places where one can419 build hydropower.420 - Maximum Power: Ensure nameplate <= max_power if421 self.max_power >= 0.422 This constraint is only for sources where there are limits423 to the maximum amount of power which can be built.424 E.g. hydropower which can only discharge at a maximum rate.425 - Conserve Power: Ensure that sum(power) > demand for all426 time-slices. Colloquially called "Keeping the Lights on."427 """428 source = self.source429 # setup LP variables.430 source.nameplate_variable = lp.declare_nameplate_variable(431 source.name,432 source.grid_region_id)433 sum_profile = sum(self.profile)434 # Configure maximum energy if it is >= 0. Otherwise do not435 # create a constraint.436 if source.max_energy >= 0:437 lp.constraint(0.0, source.max_energy).set_coefficient(438 source.nameplate_variable, sum_profile)439 # Configure maximum energy if it is >= 0. Otherwise do not440 # create a constraint.441 max_power = source.max_power442 if max_power >= 0:443 lp.constraint(0.0, max_power).set_coefficient(source.nameplate_variable,444 1.0)445 # Total_cost includes nameplate cost.446 cost_objective = lp.minimize_costs_objective447 cost_coefficient = source.nameplate_unit_cost + lp.cost_of_money * (448 source.variable_unit_cost * sum_profile +449 source.co2_per_electrical_energy * sum_profile * lp.carbon_tax)450 cost_objective.set_coefficient(source.nameplate_variable,451 cost_coefficient)452 # Add timeslice variables to coefficients.453 for t, profile_t in enumerate(self.profile):454 # Keep the lights on at all times.455 try:456 constraint = lp.conserve_power_constraint[source.grid_region_id]457 except KeyError:458 raise KeyError('No Demand declared in grid_region %d.' % (459 source.grid_region_id))460 constraint[t].set_coefficient(source.nameplate_variable, profile_t)461 # Constrain rps_credit if needed.462 if source.is_rps_source:463 lp.rps_source_constraints[source.grid_region_id][t].set_coefficient(464 source.nameplate_variable, profile_t)465 def get_solution_values(self):466 """Gets the linear program solver results.467 Must be called after lp.solve() to ensure solver has properly468 converged and has generated results.469 Raises:470 RuntimeError: If called before LinearProgramContainer.solve().471 Returns:472 np.array of solutions for each timeslice variable.473 """474 nameplate_variable = self.source.nameplate_variable475 if nameplate_variable is None:476 raise RuntimeError('get_solution_values called before solve.')477 return nameplate_variable.solution_value() * self.profile.values478class GridStorage(object):479 """Stores energy from the grid and returns it when needed subject to losses.480 Attributes:481 name: A string which is the name of the object.482 storage_nameplate_cost: A float which is the cost per nameplate of483 energy storage. E.g. The cost of batteries.484 charge_nameplate_cost: A float which is the cost per nameplate485 power to charge the storage. E.g. The rectifier cost to convert486 an AC grid to DC storage.487 discharge_nameplate_cost: A float which is the cost per nameplate488 power to recharge the grid. E.g. The cost of a power inverter to489 convert DC storage back to AC490 grid_region_id: An int specifying the grid region of the storage.491 The storage can only store energy generated by sources with the492 same grid_region_id. Only demands with the same grid_region_id493 can sink power from this.494 charge_efficiency: A float ranging from 0.0 - 1.0 which describes495 the energy loss between the grid and the storage element. 0.0496 means complete loss, 1.0 means no loss.497 storage_efficiency: A float ranging from 0.0 - 1.0 which describes498 how much stored energy remains from previous stored energy after499 one time-cycle. 1.0 means no loss. 0.0 means all stored energy500 is lost.501 discharge_efficiency: A float ranging from 0.0 - 1.0 which describes502 the energy loss between storage and grid when recharging the grid.503 0.0 means complete loss, 1.0 means no loss.504 max_charge_power: A float which represents the maximum power that505 can charge storage (calculated before any efficiency losses.).506 A value < 0 means there is no charge power limit.507 max_discharge_power: A float which represents the maximum power508 that can discharge storage (calculated before any efficiency509 losses.). A value < 0 means there is no discharge power limit.510 max_storage: An optional float which represents the maximum energy511 that can be stored. A value < 0 means there is no maximum512 storage limit.513 is_rps: Boolean; if true, keeps track of rps_credit as storage is514 charged / discharged. Amount charging[t] is subtracted from515 rps_credit[t] from rps_credit[t]. Amount discharging[t] is516 added to rps_credit[t]. If false, no rps_credits are adjusted.517 """518 def __init__(519 self,520 name,521 storage_nameplate_cost,522 charge_nameplate_cost=0.0,523 discharge_nameplate_cost=0.0,524 grid_region_id=0,525 charge_efficiency=1.0,526 storage_efficiency=1.0,527 discharge_efficiency=1.0,528 max_charge_power=-1,529 max_discharge_power=-1,530 max_storage=-1,531 is_rps=False532 ):533 self.name = name534 self.storage_nameplate_cost = storage_nameplate_cost535 self.charge_nameplate_cost = charge_nameplate_cost536 self.discharge_nameplate_cost = discharge_nameplate_cost537 self.grid_region_id = grid_region_id538 self.charge_efficiency = charge_efficiency539 self.storage_efficiency = storage_efficiency540 self.discharge_efficiency = discharge_efficiency541 self.max_charge_power = max_charge_power542 self.max_discharge_power = max_discharge_power543 self.max_storage = max_storage544 self.is_rps = is_rps545 # Sink is a power element which sinks from the grid into storage.546 # Source is a power element which sources to the grid from storage.547 # Both are constructed in configure_lp_variables_and_constraints548 self.sink = None549 self.source = None550 def configure_lp_variables_and_constraints(self, lp):551 """Declare lp variables, and set constraints.552 Args:553 lp: LinearProgramContainer, contains lp solver and constraints.554 """555 # Set up LP variables.556 self.energy_variables = lp.declare_timeslice_variables(557 self.name,558 self.grid_region_id559 )560 if self.storage_nameplate_cost:561 self.energy_nameplate = lp.declare_nameplate_variable(562 self.name,563 self.grid_region_id564 )565 # Set up source and configure LP variables.566 self.source = GridSource(567 name=self.name + ' source',568 nameplate_unit_cost=self.discharge_nameplate_cost,569 variable_unit_cost=0.0,570 grid_region_id=self.grid_region_id,571 max_power=self.max_discharge_power,572 co2_per_electrical_energy=0.0,573 power_coefficient=self.discharge_efficiency,574 is_rps_source=self.is_rps575 )576 self.source.solver = _GridSourceDispatchableSolver(self.source)577 self.source.configure_lp_variables_and_constraints(lp)578 # Set up sink and configure LP variables.579 self.sink = GridSource(580 name=self.name + ' sink',581 nameplate_unit_cost=self.discharge_nameplate_cost,582 variable_unit_cost=0.0,583 grid_region_id=self.grid_region_id,584 max_power=self.max_charge_power,585 co2_per_electrical_energy=0.0,586 power_coefficient=-1.0,587 is_rps_source=self.is_rps588 )589 self.sink.solver = _GridSourceDispatchableSolver(self.sink)590 self.sink.configure_lp_variables_and_constraints(lp)591 # Add energy nameplate costs to the objective. Other costs are592 # added by source/sink.configure_lp_variables_and_constraints.593 if self.storage_nameplate_cost:594 nameplate = self.energy_nameplate595 lp.minimize_costs_objective.set_coefficient(nameplate,596 self.storage_nameplate_cost)597 # Constrain Energy Storage to be Energy Last time plus sink minus source.598 # Storage is circular so variables at t=0 depend on variables at t=-1599 # which is equivalent to last value in python indexing scheme.600 variables = self.energy_variables601 for t in lp.time_index_iterable:602 # Ce = charge_efficiency,603 # Se = storage_efficiency.604 # Stored[i] = se * Stored[i-1] + ce * sink[i-1] - source[i-1]605 # 0 = -Stored[i] + se * Stored[i-1] + ce * sink[i-1] - source[i-1]606 c = lp.constraint(0.0, 0.0)607 c.set_coefficient(variables[t], -1.0) # -Stored[i]608 c.set_coefficient(variables[t - 1], self.storage_efficiency)609 # Source and sink are relative to the grid, so opposite here:610 # Sink adds to storage, source subtracts from storage.611 c.set_coefficient(self.source.timeslice_variables[t - 1], -1.0)612 c.set_coefficient(self.sink.timeslice_variables[t - 1],613 self.charge_efficiency)614 # Ensure nameplate is larger than stored_value.615 if self.storage_nameplate_cost:616 nameplate_constraint = lp.constraint(0.0, lp.solver.infinity())617 nameplate_constraint.set_coefficient(nameplate, 1.0)618 nameplate_constraint.set_coefficient(variables[t], -1.0)619 # Constrain maximum storage if max_storage >= 0620 if self.max_storage >= 0.0:621 max_storage_constraint = lp.constraint(0.0, self.max_storage)622 max_storage_constraint.set_coefficient(variables[t], 1.0)623 def post_process(self, lp):624 """Update lp post_processing result variables.625 This is done post lp.solve() so that sanity data checks can be done626 on RPS before returning results.627 Args:628 lp: The LinearProgramContainer where the post processing variables reside.629 """630 sink_vals = self.sink.get_solution_values()631 source_vals = (self.source.get_solution_values() *632 self.discharge_efficiency)633 if self.is_rps:634 lp.rps_total[self.grid_region_id] += source_vals - sink_vals635 else:636 lp.non_rps_total[self.grid_region_id] += source_vals - sink_vals637 def get_nameplate_solution_value(self):638 """Gets the linear program solver results for nameplate.639 Must be called after lp.solve() to ensure solver has properly640 converged and has generated results.641 Raises:642 RuntimeError: If called before LinearProgramContainer.solve().643 Returns:644 Float value representing solved nameplate value.645 """646 if self.storage_nameplate_cost:647 nameplate_variable = self.energy_nameplate648 if nameplate_variable is None:649 raise RuntimeError(650 'Get_nameplate_solution_value called before solve().')651 return nameplate_variable.solution_value()652 else:653 return max(self.get_solution_values())654 def get_solution_values(self):655 """Gets the linear program solver results.656 Must be called after lp.solve() to ensure solver has properly657 converged and has generated results.658 Raises:659 RuntimeError: If called before LinearProgramContainer.solve().660 Returns:661 np.array of solutions for each timeslice variable.662 """663 timeslice_variables = self.energy_variables664 if timeslice_variables is None:665 raise RuntimeError('get_solution_values called before solve.')666 return np.array([v.solution_value()667 for v in timeslice_variables])668class GridRecStorage(object):669 """Stores energy from the grid and returns it when needed subject to losses.670 This is a wrapper around two GridStorage objects, one which stores671 "clean" energy (is_rps) and one which stores "dirty" energy (not672 is_rps). There is a need for both types of storage to keep track of673 renewable energy credits.674 Attributes:675 name: A string which is the name of the object.676 storage_nameplate_cost: A float which is the cost per nameplate of677 energy storage. E.g. The cost of batteries.678 charge_nameplate_cost: A float which is the cost per nameplate679 power to charge the storage. E.g. The rectifier cost to convert680 an AC grid to DC storage.681 discharge_nameplate_cost: A float which is the cost per nameplate682 power to recharge the grid. E.g. The cost of a power inverter to683 convert DC storage back to AC684 grid_region_id: An int specifying the grid region of the storage.685 The storage can only store energy generated by sources with the686 same grid_region_id. Only demands with the same grid_region_id687 can sink power from this.688 charge_efficiency: A float ranging from 0.0 - 1.0 which describes689 the energy loss between the grid and the storage element. 0.0690 means complete loss, 1.0 means no loss.691 storage_efficiency: A float ranging from 0.0 - 1.0 which describes692 how much stored energy remains from previous stored energy after693 one time-cycle. 1.0 means no loss. 0.0 means all stored energy694 is lost.695 discharge_efficiency: A float ranging from 0.0 - 1.0 which describes696 the energy loss between storage and grid when recharging the grid.697 0.0 means complete loss, 1.0 means no loss.698 max_charge_power: A float which represents the maximum power that699 can charge storage (calculated before any efficiency losses.).700 A value < 0 means there is no charge power limit.701 max_discharge_power: A float which represents the maximum power702 that can discharge storage (calculated before any efficiency703 losses.). A value < 0 means there is no discharge power limit.704 max_storage: An optional float which represents the maximum energy705 that can be stored. A value < 0 means there is no maximum706 storage limit.707 rec_storage: GridStorage object which stores "clean" energy.708 no_rec_storage: GridStorage object which stores "dirty" energy.709 """710 def __init__(711 self,712 name,713 storage_nameplate_cost,714 charge_nameplate_cost=0.0,715 discharge_nameplate_cost=0.0,716 grid_region_id=0,717 charge_efficiency=1.0,718 storage_efficiency=1.0,719 discharge_efficiency=1.0,720 max_charge_power=-1,721 max_discharge_power=-1,722 max_storage=-1,723 ):724 self.name = name725 self.storage_nameplate_cost = storage_nameplate_cost726 self.charge_nameplate_cost = charge_nameplate_cost727 self.discharge_nameplate_cost = discharge_nameplate_cost728 self.grid_region_id = grid_region_id729 self.charge_efficiency = charge_efficiency730 self.storage_efficiency = storage_efficiency731 self.discharge_efficiency = discharge_efficiency732 self.max_charge_power = max_charge_power733 self.max_discharge_power = max_discharge_power734 self.max_storage = max_storage735 self.rec_storage = None736 self.no_rec_storage = None737 def configure_lp_variables_and_constraints(self, lp):738 """Declare lp variables, and set constraints."""739 # For rec_storage and no_rec_storage storage, set all costs to 0740 # and with no limits. Calculate costs and limits after741 # declaration.742 self.rec_storage = GridStorage(743 name=self.name+' REC_STORAGE',744 storage_nameplate_cost=0,745 grid_region_id=self.grid_region_id,746 charge_efficiency=self.charge_efficiency,747 discharge_efficiency=self.discharge_efficiency,748 storage_efficiency=self.storage_efficiency,749 is_rps=True)750 self.no_rec_storage = GridStorage(751 name=self.name+' NO_REC_STORAGE',752 storage_nameplate_cost=0,753 grid_region_id=self.grid_region_id,754 charge_efficiency=self.charge_efficiency,755 discharge_efficiency=self.discharge_efficiency,756 storage_efficiency=self.storage_efficiency,757 is_rps=False)758 self.rec_storage.configure_lp_variables_and_constraints(lp)759 self.no_rec_storage.configure_lp_variables_and_constraints(lp)760 # Calculate costs and limits based on the sum of both rec_storage761 # and no_rec_storage.762 # Set up LP variables.763 self.energy_variables = lp.declare_timeslice_variables(764 self.name,765 self.grid_region_id766 )767 self.energy_nameplate = lp.declare_nameplate_variable(768 self.name,769 self.grid_region_id770 )771 self.charge_nameplate = lp.declare_nameplate_variable(772 self.name + ' charge nameplate',773 self.grid_region_id774 )775 self.discharge_nameplate = lp.declare_nameplate_variable(776 self.name + ' discharge nameplate',777 self.grid_region_id778 )779 # Set limits if needed.780 if self.max_storage >= 0:781 lp.constraint(0.0, self.max_storage).set_coefficient(782 self.energy_nameplate, 1.0)783 if self.max_charge_power >= 0:784 lp.constraint(0.0, self.max_charge_power).set_coefficient(785 self.charge_nameplate, 1.0)786 if self.max_discharge_power >= 0:787 lp.constraint(0.0, self.max_discharge_power).set_coefficient(788 self.discharge_nameplate, 1.0)789 # Add energy nameplate costs to the objective.790 lp.minimize_costs_objective.set_coefficient(self.energy_nameplate,791 self.storage_nameplate_cost)792 lp.minimize_costs_objective.set_coefficient(self.charge_nameplate,793 self.charge_nameplate_cost)794 lp.minimize_costs_objective.set_coefficient(self.discharge_nameplate,795 self.discharge_nameplate_cost)796 rec_storage_energy_variables = self.rec_storage.energy_variables797 no_rec_storage_energy_variables = self.no_rec_storage.energy_variables798 for t in lp.time_index_iterable:799 # Ensure nameplate is >= sum(stored_values)[t].800 nameplate_constraint = lp.constraint(0.0, lp.solver.infinity())801 nameplate_constraint.set_coefficient(self.energy_nameplate, 1.0)802 nameplate_constraint.set_coefficient(rec_storage_energy_variables[t],803 -1.0)804 nameplate_constraint.set_coefficient(no_rec_storage_energy_variables[t],805 -1.0)806 rec_storage_charge_variables = (807 self.rec_storage.sink.timeslice_variables)808 no_rec_storage_charge_variables = (809 self.no_rec_storage.sink.timeslice_variables)810 rec_storage_discharge_variables = (811 self.rec_storage.source.timeslice_variables)812 no_rec_storage_discharge_variables = (813 self.no_rec_storage.source.timeslice_variables)814 max_charge_constraint = lp.constraint(0.0, lp.solver.infinity())815 max_charge_constraint.set_coefficient(self.charge_nameplate, 1.0)816 max_charge_constraint.set_coefficient(817 rec_storage_charge_variables[t], -1.0)818 max_charge_constraint.set_coefficient(819 no_rec_storage_charge_variables[t], -1.0)820 max_charge_constraint.set_coefficient(821 rec_storage_discharge_variables[t], 1.0)822 max_charge_constraint.set_coefficient(823 no_rec_storage_discharge_variables[t], 1.0)824 max_discharge_constraint = lp.constraint(0.0, lp.solver.infinity())825 max_discharge_constraint.set_coefficient(self.discharge_nameplate, 1.0)826 max_discharge_constraint.set_coefficient(827 rec_storage_charge_variables[t], 1.0)828 max_discharge_constraint.set_coefficient(829 no_rec_storage_charge_variables[t], 1.0)830 max_discharge_constraint.set_coefficient(831 rec_storage_discharge_variables[t], -1.0)832 max_discharge_constraint.set_coefficient(833 no_rec_storage_discharge_variables[t], -1.0)834 def get_solution_values(self):835 return (self.rec_storage.get_solution_values() +836 self.no_rec_storage.get_solution_values())837 def get_source_solution_values(self):838 return (self.rec_storage.source.get_solution_values() +839 self.no_rec_storage.source.get_solution_values() -840 self.rec_storage.sink.get_solution_values() -841 self.no_rec_storage.sink.get_solution_values())842 def get_sink_solution_values(self):843 return -self.get_source_solution_values()844 def get_nameplate_solution_value(self):845 """Gets the linear program solver results for nameplate.846 Must be called after lp.solve() to ensure solver has properly847 converged and has generated results.848 Raises:849 RuntimeError: If called before LinearProgramContainer.solve().850 Returns:851 Float value representing solved nameplate value.852 """853 if self.storage_nameplate_cost:854 nameplate_variable = self.energy_nameplate855 if nameplate_variable is None:856 raise RuntimeError(857 'Get_nameplate_solution_value called before solve().')858 return nameplate_variable.solution_value()859 else:860 return max(self.get_solution_values())861 def post_process(self, lp):862 self.rec_storage.post_process(lp)863 self.no_rec_storage.post_process(lp)864class _GridTransmission(GridSource):865 """Shuttles power from one time-zone to another."""866 def __init__(867 self,868 name,869 nameplate_unit_cost,870 source_grid_region_id=0,871 sink_grid_region_id=1,872 max_power=-1.0,873 efficiency=1.0):874 """Init function.875 Args:876 name: String name of the object.877 nameplate_unit_cost: (float) Cost to build a unit of878 transmission capacity. ($ / Megawatt of capacity)879 source_grid_region_id: An int specifying which grid_region880 power gets power added.881 sink_grid_region_id: An int specifying which grid_region882 power gets power subtracted.883 max_power: (float) Optional Maximum power which can be transmitted.884 (Megawatt). Set < 0 if there is no limit.885 efficiency: (float) ratio of how much power gets moved one886 grid_region to the other grid_region. Acceptable values are887 0. < efficiency < 1.888 """889 super(_GridTransmission, self).__init__(890 name,891 nameplate_unit_cost=nameplate_unit_cost,892 variable_unit_cost=0,893 grid_region_id=source_grid_region_id,894 max_power=max_power,895 max_energy=-1,896 co2_per_electrical_energy=0,897 power_coefficient=efficiency898 )899 self.sink_grid_region_id = sink_grid_region_id900 self.solver = _GridSourceDispatchableSolver(self)901 def configure_lp_variables_and_constraints(self, lp):902 """Declare lp variables, and set constraints.903 Args:904 lp: LinearProgramContainer, contains lp solver and constraints.905 """906 super(_GridTransmission, self).configure_lp_variables_and_constraints(lp)907 # Handle Constraints.908 for t, var in enumerate(self.timeslice_variables):909 sink_id = self.sink_grid_region_id910 source_id = self.grid_region_id911 # Whatever the super-class is sourcing in source_grid_region_id,912 # sink it from sink_grid_region_id.913 lp.conserve_power_constraint[sink_id][t].set_coefficient(var, -1.0)914 if self.is_rps_source:915 lp.rps_source_constraints[sink_id][t].set_coefficient(var, -1.0)916 def post_process(self, lp):917 """Update lp post_processing result variables.918 This is done so that sanity data checks can be done on RPS before919 returning results.920 Args:921 lp: The LinearProgramContainer where the post processing variables reside.922 """923 # Normal source post_process924 super(_GridTransmission, self).post_process(lp)925 # Sink post_process926 sink_id = self.sink_grid_region_id927 if lp.rps_percent > 0.0 and self.is_rps_source:928 lp.rps_total[sink_id] -= self.get_solution_values()929 else:930 lp.non_rps_total[sink_id] -= self.get_solution_values()931class GridTransmission(object):932 """Transmits power bidirectionally between two grid_regions.933 At interface level, transmitting from region-m to region-n is934 identical to transmitting from region-n to region-m.935 Attributes:936 name: (str) name of the object.937 nameplate_unit_cost: (float) Cost to build a unit of938 transmission capacity. ($ / Megawatt of capacity)939 grid_region_id_a: An int specifying one grid_region transmission940 terminus941 grid_region_id_b: An int specifying a different grid_region942 transmission terminus943 max_power: (float) Optional Maximum power which can be transmitted.944 (Megawatt). Set < 0 if there is no limit.945 efficiency: (float) ratio of how much power gets moved one946 grid_region to the other grid_region. Acceptable values are947 0. < efficiency < 1.948 a_to_b: _GridTransmission object which moves dirty power from949 grid_region_a to grid_region_b950 b_to_a: _GridTransmission object which moves dirty power from951 grid_region_b to grid_region_a952 rec_a_to_b: _GridTransmission object which moves clean power953 from grid_region_a to grid_region_b954 rec_b_to_a: _GridTransmission object which moves clean power955 from grid_region_b to grid_region_a956 """957 def __init__(958 self,959 name,960 nameplate_unit_cost,961 grid_region_id_a,962 grid_region_id_b,963 efficiency=1.0,964 max_power=-1.0,965 ):966 self.name = name967 self.nameplate_unit_cost = nameplate_unit_cost968 self.grid_region_id_a = grid_region_id_a969 self.grid_region_id_b = grid_region_id_b970 self.efficiency = efficiency971 self.max_power = max_power972 self.a_to_b = None973 self.b_to_a = None974 self.rec_a_to_b = None975 self.rec_b_to_a = None976 def configure_lp_variables_and_constraints(self, lp):977 """Declare lp variables, and set constraints.978 Args:979 lp: LinearProgramContainer, contains lp solver and constraints.980 """981 self.a_to_b = _GridTransmission(self.name + ' a_to_b',982 0,983 self.grid_region_id_b,984 self.grid_region_id_a,985 self.max_power,986 self.efficiency)987 self.b_to_a = _GridTransmission(self.name + ' b_to_a',988 0,989 self.grid_region_id_a,990 self.grid_region_id_b,991 self.max_power,992 self.efficiency)993 self.rec_a_to_b = _GridTransmission(self.name + ' rec a_to_b',994 0,995 self.grid_region_id_b,996 self.grid_region_id_a,997 self.max_power,998 self.efficiency,999 is_rps=True)1000 self.rec_b_to_a = _GridTransmission(self.name + ' rec b_to_a',1001 0,1002 self.grid_region_id_a,1003 self.grid_region_id_b,1004 self.max_power,1005 self.efficiency,1006 is_rps=True)1007 self.a_to_b.configure_lp_variables_and_constraints(lp)1008 self.b_to_a.configure_lp_variables_and_constraints(lp)1009 self.rec_a_to_b.configure_lp_variables_and_constraints(lp)1010 self.rec_b_to_a.configure_lp_variables_and_constraints(lp)1011 # Make sure nameplate >= sum(a_to_b) and nameplate >= sum(b_to_a)1012 self.nameplate_variable = lp.declare_nameplate_variable(1013 self.name, '%d_%d' % (self.grid_region_id_a, self.grid_region_id_b))1014 lp.minimize_costs_objective.set_coefficient(self.nameplate_variable,1015 self.nameplate_unit_cost)1016 for t in lp.time_index_iterable:1017 # nameplate >= a_to_b[t] + rec_a_to_b[t] - b_to_a[t] - rec_b_to_a[t]1018 a_to_b_constraint = lp.constraint(0.0, lp.solver.infinity())1019 a_to_b_constraint.set_coefficient(self.nameplate_variable, 1.0)1020 a_to_b_constraint.set_coefficient(1021 self.a_to_b.timeslice_variables[t], -1.0)1022 a_to_b_constraint.set_coefficient(1023 self.rec_a_to_b.timeslice_variables[t], -1.0)1024 a_to_b_constraint.set_coefficient(1025 self.b_to_a.timeslice_variables[t], 1.0)1026 a_to_b_constraint.set_coefficient(1027 self.rec_b_to_a.timeslice_variables[t], 1.0)1028 # nameplate >= b_to_a[t] + rec_b_to_a[t] - a_to_b[t] - rec_a_to_b[t]1029 b_to_a_constraint = lp.constraint(0.0, lp.solver.infinity())1030 b_to_a_constraint.set_coefficient(self.nameplate_variable, 1.0)1031 b_to_a_constraint.set_coefficient(1032 self.b_to_a.timeslice_variables[t], -1.0)1033 b_to_a_constraint.set_coefficient(1034 self.rec_b_to_a.timeslice_variables[t], -1.0)1035 b_to_a_constraint.set_coefficient(1036 self.a_to_b.timeslice_variables[t], 1.0)1037 b_to_a_constraint.set_coefficient(1038 self.rec_a_to_b.timeslice_variables[t], 1.0)1039 def post_process(self, lp):1040 """Update lp post_processing result variables.1041 This is done so that sanity data checks can be done on RPS before1042 returning results.1043 Args:1044 lp: The LinearProgramContainer where the post processing variables reside.1045 """1046 self.a_to_b.post_process(lp)1047 self.b_to_a.post_process(lp)1048 self.rec_a_to_b.post_process(lp)1049 self.rec_b_to_a.post_process(lp)1050 def get_nameplate_solution_value(self):1051 """Gets the linear program solver results for nameplate.1052 Must be called after lp.solve() to ensure solver has properly1053 converged and has generated results.1054 Raises:1055 RuntimeError: If called before LinearProgramContainer.solve().1056 Returns:1057 Float value representing solved nameplate value.1058 """1059 nameplate_variable = self.nameplate_variable1060 if nameplate_variable is None:1061 raise RuntimeError('Get_nameplate_solution_value called before solve().')1062 return nameplate_variable.solution_value()1063class LinearProgramContainer(object):1064 """Instantiates and interfaces to LP Solver.1065 Example Usage:1066 Initialize: lp = LinearProgramContainer()1067 Add objects:1068 lp.add_demands(<GridDemand>)1069 lp.add_sources(<GridSource>)1070 lp.add_transmissions(<GridTransmission>)1071 lp.solve()1072 Attributes:1073 carbon_tax: The amount to tax 1 unit of co2 emissions.1074 cost_of_money: The amount to multiply variable costs by to1075 make yearly costs and fixed costs comparable.1076 profiles: time-series profiles indexed by name which map to1077 GridDemands and GridNonDispatchableSources.1078 number_of_timeslices: int representing one timeslice per profile index.1079 time_index_iterable: A simple int range from 0 - number_of_timeslices.1080 Constraints:1081 conserve_power_constraint: Dict keyed by grid_region_id. Value1082 is a list of LP Constraints which ensures that power > demand1083 at all times in all grid_regions.1084 minimize_costs_objective: The LP Objective which is to minimize costs.1085 rps_source_constraints: Dict keyed by grid_region_id. Value is a1086 list of LP Constraints which ensures that1087 rps_credit[grid_region, t] <= sum(rps_sources[grid_region, t])1088 rps_demand_constraints: Dict keyed by grid_region_id. Value is1089 a list of LP Constraints which ensures that1090 rps_credit[grid_region, t] <= demand[grid_region, t]1091 RPS Variables:1092 rps_credit_variables: Dict object keyed by grid_region_id. Value is a1093 list of rps_credit[grid_region, t] variables for calculating rps.1094 Post Processing Variables. Computed after LP converges:1095 rps_total: Dict object keyed by grid_region_id. Value is sum1096 (GridSource_power[grid_region, t]) of all rps sources.1097 non_rps_total: Dict object keyed by grid_region_id. Value is sum1098 (GridSource_power[grid_region, t]) of all non_rps sources.1099 adjusted_demand: Dict object keyed by grid_region_id. Value is1100 Demand[grid_region, t]1101 rps_credit_values: Dict object keyed by grid_region_id. Value is1102 rps_credit.value[grid_region, t]1103 Grid Elements:1104 demands: A list of GridDemand(s).1105 sources: A list of GridSource(s).1106 storage: A list of GridStorage(s).1107 transmission: A list of GridTransmission(s).1108 solver: The wrapped pywraplp.Solver.1109 solver_precision: A float representing estimated precision of the solver.1110 """1111 def __init__(self, profiles):1112 """Initializes LP Container.1113 Args:1114 profiles: Time-series pandas dataframe profiles indexed by name1115 which map to GridDemands and GridNonDispatchableSources.1116 Raises:1117 ValueError: If any value in profiles is < 0 or Nan / None.1118 """1119 self.carbon_tax = 0.01120 self.cost_of_money = 1.01121 self.rps_percent = 0.01122 self.profiles = profiles1123 # Constraints1124 self.conserve_power_constraint = {}1125 self.minimize_costs_objective = None1126 # RPS Constraints1127 self.rps_source_constraints = {}1128 self.rps_demand_constraints = {}1129 # RPS Variables1130 self.rps_credit_variables = {}1131 # Post Processing Variables1132 self.rps_total = {}1133 self.non_rps_total = {}1134 self.adjusted_demand = {}1135 self.total_demand = 01136 self.rps_demand = 01137 self.rps_credit_values = {}1138 self.demands = []1139 self.sources = []1140 self.storage = []1141 self.transmission = []1142 self.solver = None1143 self.solver_precision = 1e-31144 # Validate profiles1145 if profiles is None:1146 raise ValueError('No profiles specified.')1147 if profiles.empty:1148 raise ValueError('No Data in Profiles.')1149 if profiles.isnull().values.any():1150 raise ValueError('Profiles may not be Null or None')1151 profiles_lt_0 = profiles.values < 01152 if profiles_lt_0.any():1153 raise ValueError('Profiles must not be < 0.')1154 self.number_of_timeslices = len(profiles)1155 self.time_index_iterable = range(self.number_of_timeslices)1156 def add_demands(self, *demands):1157 """Add all GridDemands in Args to self.demands."""1158 for d in demands:1159 self.demands.append(d)1160 def add_dispatchable_sources(self, *sources):1161 """Verify source has no profile associated with it and add to self.sources.1162 Args:1163 *sources: arbitrary number of GridSources.1164 Raises:1165 KeyError: if Source has a profile associated with it which would1166 indicate the source was non-dispatchable instead of1167 dispatchable.1168 """1169 for source in sources:1170 if source.name in self.profiles:1171 raise KeyError(1172 'Dispatchable Source %s has a profile associated with it' %(1173 source.name1174 )1175 )1176 source.solver = _GridSourceDispatchableSolver(source)1177 self.sources.append(source)1178 def add_nondispatchable_sources(self, *sources):1179 """Verify source has a profile associated with it and add to self.sources.1180 Args:1181 *sources: arbitrary number of GridSources.1182 Raises:1183 KeyError: if Source has no profile associated with it which would1184 indicate the source was dispatchable instead of1185 non-dispatchable.1186 """1187 for source in sources:1188 if source.name not in self.profiles:1189 known_sources = ','.join(sorted(self.profiles.columns))1190 known_source_string = 'Known sources are (%s).' % known_sources1191 raise KeyError(1192 'Nondispatchable Source %s has no profile. %s' % (1193 source.name,1194 known_source_string1195 )1196 )1197 source.solver = _GridSourceNonDispatchableSolver(1198 source,1199 self.profiles[source.name])1200 self.sources.append(source)1201 def add_storage(self, *storage):1202 """Add storage to lp."""1203 self.storage.extend(storage)1204 def add_transmissions(self, *transmission):1205 """Add transmission to lp."""1206 self.transmission.extend(transmission)1207 def constraint(self, lower, upper, name=None, debug=False):1208 """Build a new Constraint which with valid range between lower and upper."""1209 return Constraint(self, lower, upper, name, debug)1210 def _initialize_solver(self):1211 """Initializes solver, declares objective and set constraints.1212 Solver is pywraplp.solver.1213 Objective is to minimize costs subject to constraints.1214 One constraint declared here is to ensure that1215 power[grid_region][t] > demand[grid_region][t] for all t and1216 grid_regions.1217 Also configures GridElements.1218 """1219 self.solver = pywraplp.Solver('SolveEnergy',1220 pywraplp.Solver.CLP_LINEAR_PROGRAMMING)1221 self.minimize_costs_objective = Objective(self, minimize=True)1222 # Initialize GridDemands and GridSources1223 demand_sum = 0.01224 for d in self.demands:1225 try:1226 profiles = self.profiles[d.name]1227 self.adjusted_demand[d.grid_region_id] = np.array(profiles.values)1228 except KeyError:1229 profile_names = str(self.profiles.keys())1230 error_string = 'GridDemand %s. No profile found! Known profiles:(%s)' %(1231 d.name,1232 profile_names1233 )1234 raise KeyError(error_string)1235 self.conserve_power_constraint[d.grid_region_id] = [1236 self.constraint(p,1237 self.solver.infinity(),1238 'Conserve Power gid:%d t:%d' %(1239 d.grid_region_id, t))1240 for t, p in enumerate(profiles)1241 ]1242 demand_sum += sum(profiles)1243 # Handle RPS which is tricky. It requires special credit1244 # variables[grid_region][time] and 3 constraints.1245 #1246 # Constraint #1:1247 # The overall goal is to have RPS exceed rps_percent of total1248 # demand. Given that:1249 # total_rps_credit := sum(rps_credit[g][t])1250 # total_demand := sum(demand[g][t])1251 #1252 # The constraint named total_rps_credit_gt_rps_percent_constraint1253 # is:1254 # total_rps_credit >= (self.rps_percent / 100) * total_demand1255 #1256 # Constraint #2:1257 # rps_credit[g][t] cannot exceed sum of rps_sources - sum of1258 # rps_sinks at each g,t. An example of rps_sink is the 'REC_STORAGE'1259 # part of GridRecStorage which stores rps energy off the grid only1260 # to put it back on the grid later as a rps_source. This is1261 # reflected in the constraint named1262 # rps_source_constraints[g][t]:1263 # rps_credit[g][t] <= sum(rps_sources[g][t]) - sum(rps_sinks[g][t])1264 #1265 # Constraint #31266 # rps_credit[g][t] cannot exceed what can be used at each g,t. if1267 # rps_sources generate a Gigawatt at g,t = 0,0 and only 1MW can be1268 # used at g,t then we don't want to credit the unused 999 MW.1269 #1270 # The constraint named rps_demand_constraints is:1271 # rps_credit[g][t] <= demand[g][t]1272 #1273 self.total_demand = demand_sum1274 self.rps_demand = demand_sum * self.rps_percent / 100.1275 solver = self.solver1276 total_rps_credit_gt_rps_percent_constraint = self.constraint(1277 self.rps_demand,1278 solver.infinity()1279 )1280 for d in self.demands:1281 profiles = self.profiles[d.name]1282 if self.rps_percent > 0.0:1283 rps_credit_variables = self.declare_timeslice_variables(1284 '__rps_credit__',1285 d.grid_region_id1286 )1287 else:1288 rps_credit_variables = [solver.NumVar(0.0, 0.0,1289 '__bogus rps_credit__ %d %d' %(1290 d.grid_region_id, t))1291 for t in self.time_index_iterable]1292 rps_demand_constraints = []1293 rps_source_constraints = [self.constraint(0.0, solver.infinity())1294 for t in self.time_index_iterable]1295 self.rps_source_constraints[d.grid_region_id] = rps_source_constraints1296 self.rps_credit_variables[d.grid_region_id] = rps_credit_variables1297 for t in self.time_index_iterable:1298 # Sum(rps_credit[grid_region, t]) >= rps_percent * total demand.1299 total_rps_credit_gt_rps_percent_constraint.set_coefficient(1300 rps_credit_variables[t], 1.0)1301 # Rps_credit[grid_region, t] <= demand[grid_region, t].1302 rps_credit_less_than_demand = self.constraint(-solver.infinity(),1303 profiles[t])1304 rps_credit_less_than_demand.set_coefficient(rps_credit_variables[t],1305 1.0)1306 rps_demand_constraints.append(rps_credit_less_than_demand)1307 # Rps_credit[grid_region, t] <= (sum(rps_sources[grid_region, t])1308 # Constraint also gets adjusted by _GridSource(Non)DispatchableSolver.1309 # configure_lp_variables_and_constraints1310 rps_source_constraints[t].set_coefficient(rps_credit_variables[t],1311 -1.0)1312 self.rps_demand_constraints[d.grid_region_id] = rps_demand_constraints1313 # Configure sources and storage.1314 for s in self.sources + self.storage + self.transmission:1315 s.configure_lp_variables_and_constraints(self)1316 def solve(self):1317 """Initializes and runs linear program.1318 This is the main routine to call after __init__.1319 Returns:1320 True if linear program gave an optimal result. False otherwise.1321 """1322 self._initialize_solver()1323 status = self.solver.Solve()1324 converged = status == self.solver.OPTIMAL1325 if converged:1326 self._post_process()1327 return converged1328 def _post_process(self):1329 """Generates data used for calculating consumed rps/non-rps values.1330 Also double-checks results to make sure they match constraints.1331 Raises:1332 RuntimeError: If double-checked results do not match constraints.1333 """1334 # Initialize post_processing totals.1335 for d in self.demands:1336 # Total amount of rps_sources[g][t] power.1337 self.rps_total[d.grid_region_id] = np.zeros(self.number_of_timeslices)1338 # Total amount of non-rps_sources[g][t] power.1339 self.non_rps_total[d.grid_region_id] = np.zeros(self.number_of_timeslices)1340 for s in self.sources + self.storage + self.transmission:1341 s.post_process(self)1342 # Sanity error check results against constraints. If any of these1343 # get raised, it indicates a bug in the code.1344 solver_precision = self.solver_precision1345 sum_rps_credits = 0.01346 for g_id in [d.grid_region_id for d in self.demands]:1347 power_deficit = (self.adjusted_demand[g_id] -1348 (self.rps_total[g_id] + self.non_rps_total[g_id]))1349 lights_kept_on = (power_deficit < solver_precision).all()1350 rps_credits = np.array(1351 [rcv.solution_value() for rcv in self.rps_credit_variables[g_id]])1352 sum_rps_credits += sum(rps_credits)1353 self.rps_credit_values[g_id] = rps_credits1354 rps_credit_gt_demand = (1355 rps_credits > self.adjusted_demand[g_id] + solver_precision).all()1356 rps_credit_gt_rps_sources = (1357 rps_credits > self.rps_total[g_id] + solver_precision).all()1358 storage_exceeds_demand = (1359 self.adjusted_demand[g_id] < -solver_precision).all()1360 if not lights_kept_on:1361 raise DemandNotSatisfiedError(1362 'Demand not satisfied by %f for region %d' % (max(power_deficit),1363 g_id))1364 if rps_credit_gt_demand:1365 raise RpsExceedsDemandError(1366 'RPS Credits Exceed Demand for region %d' %g_id)1367 if rps_credit_gt_rps_sources:1368 raise RpsCreditExceedsSourcesError(1369 'RPS Credits Exceed RPS Sources for region %d' %g_id)1370 if storage_exceeds_demand:1371 raise StorageExceedsDemandError(1372 'Storage Exceeds Demand for region %d' %g_id)1373 # Scale solver_precision by number of timeslices to get precision1374 # for a summed comparison.1375 sum_solver_precision = solver_precision * self.number_of_timeslices1376 if sum_solver_precision + sum_rps_credits < self.rps_demand:1377 raise RpsPercentNotMetError(1378 'Sum RPS credits (%f) < demand * (%f rps_percent) (%f)' %(1379 sum_rps_credits,1380 float(self.rps_percent),1381 self.rps_demand1382 )1383 )1384 def declare_timeslice_variables(self, name, grid_region_id):1385 """Declares timeslice variables for a grid_region.1386 Args:1387 name: String to be included in the generated variable name.1388 grid_region_id: Int which identifies which grid these variables affect.1389 Do Not call this function with the same (name, grid_region_id)1390 pair more than once. There may not be identically named variables1391 in the same grid_region.1392 Returns:1393 Array of lp variables, each which range from 0 to infinity.1394 Array is mapped so that variable for time-slice x is at index x.1395 e.g. variable for first time-slice is variable[0]. variable for1396 last time-slice is variable[-1]1397 """1398 solver = self.solver1399 variables = []1400 for t in self.time_index_iterable:1401 var_name = '__'.join([name,1402 'grid_region_id',1403 str(grid_region_id),1404 'at_t',1405 str(t)])1406 variables.append(solver.NumVar(0.0,1407 solver.infinity(),1408 var_name))1409 return variables1410 def declare_nameplate_variable(self, name, grid_region_id):1411 """Declares a nameplate variable for a grid_region.1412 Args:1413 name: String to be included in the generated variable name.1414 grid_region_id: Stringifyable object which identifies which grid1415 these variables affect.1416 Do Not call this function with the same (name, grid_region_id)1417 pair more than once. There may not be identically named variables1418 in the same grid_region.1419 Returns:1420 A lp variable which values range from 0 to infinity.1421 """1422 nameplate_name = '__'.join([name,1423 'grid_region_id', str(grid_region_id),1424 'peak'])1425 solver = self.solver1426 return solver.NumVar(0.0,1427 solver.infinity(),1428 nameplate_name)1429def extrapolate_cost(cost, discount_rate, time_span_1, time_span_2):1430 """Extrapolate cost from one time span to another.1431 Args:1432 cost: cost incurred during time_span_1 (in units of currency)1433 discount_rate: rate that money decays, per year (as decimal, e.g., .06)1434 time_span_1: time span when cost incurred (in units of years)1435 time_span_2: time span to extrapolate cost (in units of years)1436 Returns:1437 Cost extrapolated to time_span_2, units of currency.1438 Model parameters are costs over time spans. For example, the demand1439 may be a time series that lasts 1 year. The variable cost to fulfill1440 that demand would then be for 1 year of operation. However, the1441 GridModel is supposed to compute the total cost over a longer time1442 span (e.g., 30 years).1443 If there were no time value of money, the extrapolated cost would be1444 the ratio of time_span_2 to time_span_1 (e.g., 30 in the1445 example). However, payments in the future are less costly than1446 payments in the present. We extrapolate the cost by first finding1447 the equivalent continuous stream of payments over time_span_1 that1448 is equivalent to the cost, then assume that stream of payments1449 occurs over time_span_2, instead.1450 """1451 growth_rate = 1.0 + discount_rate1452 value_decay_1 = pow(growth_rate, -time_span_2)1453 value_decay_2 = pow(growth_rate, -time_span_1)1454 try:1455 return cost * (1.0 - value_decay_1) / (1.0-value_decay_2)1456 except ZeroDivisionError:...

Full Screen

Full Screen

main.py

Source:main.py Github

copy

Full Screen

...56class LoadProfile(object):57 def __init__(self, load_type, schedule):58 self.load_type = load_type59 self.schedule = self.__make_steps(schedule)60 def is_rps(self):61 return self.load_type == 'rps'62 def is_instances(self):63 return self.load_type == 'instances'64 @staticmethod65 def __make_steps(schedule):66 steps = []67 for step in " ".join(schedule.split("\n")).split(')'):68 if step.strip():69 steps.append(step.strip() + ')')70 return steps71class StepperWrapper(object):72 # TODO: review and rewrite this class73 '''74 Wrapper for cached stepper functionality75 '''76 OPTION_LOAD = 'load_profile'77 OPTION_LOAD_TYPE = 'load_type'78 OPTION_SCHEDULE = 'schedule'79 OPTION_STEPS = 'steps'80 OPTION_TEST_DURATION = 'test_duration'81 OPTION_AMMO_COUNT = 'ammo_count'82 OPTION_LOOP = 'loop'83 OPTION_LOOP_COUNT = 'loop_count'84 OPTION_AMMOFILE = "ammofile"85 OPTION_LOADSCHEME = 'loadscheme'86 OPTION_INSTANCES_LIMIT = 'instances'87 def __init__(self, core, cfg):88 self.log = logging.getLogger(__name__)89 self.core = core90 self.cfg = cfg91 self.cache_dir = '.'92 # per-shoot params93 self.instances = 100094 self.http_ver = '1.0'95 self.ammo_file = None96 self.loop_limit = -197 self.ammo_limit = -198 self.uris = []99 self.headers = []100 self.autocases = 0101 self.enum_ammo = False102 self.force_stepping = None103 self.chosen_cases = []104 # out params105 self.stpd = None106 self.steps = []107 self.ammo_count = 1108 self.duration = 0109 self.loop_count = 0110 self.loadscheme = ""111 self.file_cache = 8192112 def get_option(self, option, param2=None):113 ''' get_option wrapper'''114 result = self.cfg[option]115 self.log.debug(116 "Option %s = %s", option, result)117 return result118 @staticmethod119 def get_available_options():120 opts = [121 StepperWrapper.OPTION_AMMOFILE, StepperWrapper.OPTION_LOOP,122 StepperWrapper.OPTION_SCHEDULE, StepperWrapper.OPTION_INSTANCES_LIMIT123 ]124 opts += [125 "instances_schedule", "uris", "headers", "header_http", "autocases",126 "enum_ammo", "ammo_type", "ammo_limit"127 ]128 opts += [129 "use_caching", "cache_dir", "force_stepping", "file_cache",130 "chosen_cases"131 ]132 return opts133 def read_config(self):134 ''' stepper part of reading options '''135 self.log.info("Configuring StepperWrapper...")136 self.ammo_file = self.get_option(self.OPTION_AMMOFILE)137 self.ammo_type = self.get_option('ammo_type')138 if self.ammo_file:139 self.ammo_file = os.path.expanduser(self.ammo_file)140 self.loop_limit = self.get_option(self.OPTION_LOOP)141 self.ammo_limit = self.get_option("ammo_limit")142 self.load_profile = LoadProfile(**self.get_option('load_profile'))143 self.instances = int(144 self.get_option(self.OPTION_INSTANCES_LIMIT, '1000'))145 self.uris = self.get_option("uris", [])146 while '' in self.uris:147 self.uris.remove('')148 self.headers = self.get_option("headers")149 self.http_ver = self.get_option("header_http")150 self.autocases = self.get_option("autocases")151 self.enum_ammo = self.get_option("enum_ammo")152 self.use_caching = self.get_option("use_caching")153 self.file_cache = self.get_option('file_cache')154 cache_dir = self.get_option("cache_dir") or self.core.artifacts_base_dir155 self.cache_dir = os.path.expanduser(cache_dir)156 self.force_stepping = self.get_option("force_stepping")157 if self.get_option(self.OPTION_LOAD)[self.OPTION_LOAD_TYPE] == 'stpd_file':158 self.stpd = self.get_option(self.OPTION_LOAD)[self.OPTION_SCHEDULE]159 self.chosen_cases = self.get_option("chosen_cases").split()160 if self.chosen_cases:161 self.log.info("chosen_cases LIMITS: %s", self.chosen_cases)162 def prepare_stepper(self):163 ''' Generate test data if necessary '''164 def publish_info(stepper_info):165 info.status.publish('loadscheme', stepper_info.loadscheme)166 info.status.publish('loop_count', stepper_info.loop_count)167 info.status.publish('steps', stepper_info.steps)168 info.status.publish('duration', stepper_info.duration)169 info.status.ammo_count = stepper_info.ammo_count170 info.status.publish('instances', stepper_info.instances)171 self.core.publish('stepper', 'loadscheme', stepper_info.loadscheme)172 self.core.publish('stepper', 'loop_count', stepper_info.loop_count)173 self.core.publish('stepper', 'steps', stepper_info.steps)174 self.core.publish('stepper', 'duration', stepper_info.duration)175 self.core.publish('stepper', 'ammo_count', stepper_info.ammo_count)176 self.core.publish('stepper', 'instances', stepper_info.instances)177 return stepper_info178 if not self.stpd:179 self.stpd = self.__get_stpd_filename()180 if self.use_caching and not self.force_stepping and os.path.exists(181 self.stpd) and os.path.exists(self.__si_filename()):182 self.log.info("Using cached stpd-file: %s", self.stpd)183 stepper_info = self.__read_cached_options()184 if self.instances and self.load_profile.is_rps():185 self.log.info(186 "rps_schedule is set. Overriding cached instances param from config: %s",187 self.instances)188 stepper_info = stepper_info._replace(189 instances=self.instances)190 publish_info(stepper_info)191 else:192 if (193 self.force_stepping and os.path.exists(self.__si_filename())):194 os.remove(self.__si_filename())195 self.__make_stpd_file()196 stepper_info = info.status.get_info()197 self.__write_cached_options(stepper_info)198 else:199 self.log.info("Using specified stpd-file: %s", self.stpd)200 stepper_info = publish_info(self.__read_cached_options())201 self.ammo_count = stepper_info.ammo_count202 self.duration = stepper_info.duration203 self.loop_count = stepper_info.loop_count204 self.loadscheme = stepper_info.loadscheme205 self.steps = stepper_info.steps206 if stepper_info.instances:207 self.instances = stepper_info.instances208 def __si_filename(self):209 '''Return name for stepper_info json file'''210 return "%s_si.json" % self.stpd211 def __get_stpd_filename(self):212 ''' Choose the name for stepped data file '''213 if self.use_caching:214 sep = "|"215 hasher = hashlib.md5()216 hashed_str = "cache version 6" + sep + \217 ';'.join(self.load_profile.schedule) + sep + str(self.loop_limit)218 hashed_str += sep + str(self.ammo_limit) + sep + ';'.join(219 self.load_profile.schedule) + sep + str(self.autocases)220 hashed_str += sep + ";".join(self.uris) + sep + ";".join(221 self.headers) + sep + self.http_ver + sep + ";".join(222 self.chosen_cases)223 hashed_str += sep + str(self.enum_ammo) + sep + str(self.ammo_type)224 if self.load_profile.is_instances():225 hashed_str += sep + str(self.instances)226 if self.ammo_file:227 opener = resource.get_opener(self.ammo_file)228 hashed_str += sep + opener.hash229 else:230 if not self.uris:231 raise RuntimeError("Neither ammofile nor uris specified")232 hashed_str += sep + \233 ';'.join(self.uris) + sep + ';'.join(self.headers)234 self.log.debug("stpd-hash source: %s", hashed_str)235 hasher.update(hashed_str.encode('utf8'))236 if not os.path.exists(self.cache_dir):237 os.makedirs(self.cache_dir)238 stpd = self.cache_dir + '/' + \239 os.path.basename(self.ammo_file) + \240 "_" + hasher.hexdigest() + ".stpd"241 else:242 stpd = os.path.realpath("ammo.stpd")243 self.log.debug("Generated cache file name: %s", stpd)244 return stpd245 def __read_cached_options(self):246 '''247 Read stepper info from json248 '''249 self.log.debug("Reading cached stepper info: %s", self.__si_filename())250 with open(self.__si_filename(), 'r') as si_file:251 si = info.StepperInfo(**json.load(si_file))252 return si253 def __write_cached_options(self, si):254 '''255 Write stepper info to json256 '''257 self.log.debug("Saving stepper info: %s", self.__si_filename())258 with open(self.__si_filename(), 'w') as si_file:259 json.dump(si._asdict(), si_file, indent=4)260 def __make_stpd_file(self):261 ''' stpd generation using Stepper class '''262 self.log.info("Making stpd-file: %s", self.stpd)263 stepper = Stepper(264 self.core,265 rps_schedule=self.load_profile.schedule if self.load_profile.is_rps() else None,266 http_ver=self.http_ver,267 ammo_file=self.ammo_file,268 instances_schedule=self.load_profile.schedule if self.load_profile.is_instances() else None,269 instances=self.instances,270 loop_limit=self.loop_limit,271 ammo_limit=self.ammo_limit,272 uris=self.uris,273 headers=[header.strip('[]') for header in self.headers],274 autocases=self.autocases,275 enum_ammo=self.enum_ammo,276 ammo_type=self.ammo_type,277 chosen_cases=self.chosen_cases,278 use_cache=self.use_caching)279 with open(self.stpd, 'wb', self.file_cache) as os:...

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run yandex-tank automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful