def testSimpleStorage(self): """Free Storage should backup Solar.""" solar = GridSource(SOLAR, 2.0e6, 0) wind = GridSource(WIND, 5.0e6, 0) ng = GridSource(NG, 1.0e10, 0) storage = GridStorage(STORAGE, 0) lp = self.lp lp.add_nondispatchable_sources(solar, wind) lp.add_dispatchable_sources(ng) lp.add_storage(storage) self.assertTrue(lp.solve()) npt.assert_almost_equal(solar.get_solution_values(), np.array([4.0, 0, 0, 2.0])) npt.assert_almost_equal(wind.get_solution_values(), np.zeros(4)) npt.assert_almost_equal(ng.get_solution_values(), np.zeros(4)) npt.assert_almost_equal(storage.get_solution_values(), np.array([0, 1, 1, 1])) npt.assert_almost_equal(storage.source.get_solution_values(), np.array([0, 0, 0, 1])) npt.assert_almost_equal(storage.sink.get_solution_values(), np.array([1, 0, 0, 0]))
def testCheaperDispatchableOnly(self): """Two Dispatchable Sources. Cheaper one should fill demand.""" lp = self.lp ng1 = self.ng ng2 = GridSource(NG2, 2e6, 2e6) lp.add_dispatchable_sources(ng1, ng2) self.assertTrue(lp.solve()) npt.assert_almost_equal(ng1.get_solution_values(), self.demand_profile) npt.assert_almost_equal(ng2.get_solution_values(), np.zeros(4)) self.assertAlmostEqual(ng1.get_nameplate_solution_value(), max(self.demand_profile)) self.assertAlmostEqual(ng2.get_nameplate_solution_value(), 0.0)
def testCircularRecStorageFirstHourSource(self): """Verify that storage from first hour affects last hour.""" wind = GridSource(WIND, 2.0e6, 0) storage = GridRecStorage(STORAGE, 0) lp = self.lp lp.add_nondispatchable_sources(wind) lp.add_storage(storage) self.assertTrue(lp.solve()) npt.assert_almost_equal(wind.get_solution_values(), np.array([2.0, 0.0])) npt.assert_almost_equal(storage.get_solution_values(), np.array([0.0, 1.0])) npt.assert_almost_equal(storage.get_source_solution_values(), np.array([-1.0, 1.0]))
def testCostConstraintCreation(self): """Verify cost constraint gets created upon initialization.""" lp = self.lp ng = GridSource(NG, 1e6, 1e6) lp.add_dispatchable_sources(ng) self.assertIsNone(lp.minimize_costs_objective) lp._initialize_solver() self.assertIsNotNone(lp.minimize_costs_objective)
def testCircularRecStorageLastHourSource(self): """Verify that storage from last hour affects first hour.""" solar = GridSource(SOLAR, 2.0e6, 0) storage = GridRecStorage(STORAGE, 0) lp = self.lp lp.add_nondispatchable_sources(solar) lp.add_storage(storage) self.assertTrue(lp.solve()) npt.assert_almost_equal(solar.get_solution_values(), np.array([0.0, 2.0])) npt.assert_almost_equal(storage.get_solution_values(), np.array([1.0, 0.0])) npt.assert_almost_equal(storage.get_source_solution_values(), np.array([1.0, -1.0]))
def testPostProcessing(self): lp = self.lp wind = GridSource(WIND, 2.0e6, 0, is_rps_source=True) ng = GridSource(NG, 1.0e6, 1.0e6) storage = GridStorage(STORAGE, 1, 1, 1) lp.add_nondispatchable_sources(wind) lp.add_dispatchable_sources(ng) lp.add_storage(storage) lp.rps_percent = 20 self.assertTrue(lp.solve()) wind_solution = MockPostProcessingGridSourceSolver(wind) storage_sink_solution = MockPostProcessingGridSourceSolver(storage.sink) wind_solution.solution_values -= 0.1 with self.assertRaises(DemandNotSatisfiedError): lp._post_process() # After reset it shouldn't error out. wind_solution.reset() try: lp._post_process() except RuntimeError: self.fail() storage_sink_solution.solution_values += 20 with self.assertRaises(DemandNotSatisfiedError): lp._post_process() # After reset it shouldn't error out. storage_sink_solution.reset() try: lp._post_process() except RuntimeError: self.fail() lp.rps_demand *= 100 with self.assertRaises(RpsPercentNotMetError): lp._post_process()
def testCombinedSolarWindNgRps(self): """Sweep RPS.""" solar = GridSource(SOLAR, 2.0e6, 0, is_rps_source=True) wind = GridSource(WIND, 4.0e6, 0, is_rps_source=True) ng = GridSource(NG, 0, 1.0e6) lp = self.lp lp.add_dispatchable_sources(ng) lp.add_nondispatchable_sources(solar, wind) # As rps is swept, it will fill RPS requirement with cheaper solar # first then wind. NG is cheapest but not in RPS so it will fill # in the blanks. # Wind is really expensive so check and make sure the LP doesn't # cheat by requesting more cheaper solar than it can actually use # in order to satisfy rps. Instead, after it's maxed out on # solar, it has to use the expensive wind. for rps in np.arange(0, 1.2, 0.1): lp.rps_percent = rps * 100.0 if lp.solve(): self.assertTrue(rps <= 1.0) solar_nameplate = rps * 2 if rps <= 0.5 else 1.0 wind_nameplate = (rps - 0.5) * 2 if rps > 0.5 else 0.0 solar_golden_profile = self.profiles[SOLAR] * solar_nameplate wind_golden_profile = self.profiles[WIND] * wind_nameplate ng_golden_profile = (self.profiles[DEMAND] - solar_golden_profile - wind_golden_profile) npt.assert_almost_equal(solar.get_solution_values(), solar_golden_profile) npt.assert_almost_equal(wind.get_solution_values(), wind_golden_profile) npt.assert_almost_equal(ng.get_solution_values(), ng_golden_profile) else: # Ensure lp doesn't solve if rps > 1.0 self.assertTrue(rps > 1.0)
def setUp(self): self.demand_profile = np.array([3.0, 0.0, 0.0, 3.0]) rng = pd.date_range('1/1/2011', periods=len(self.demand_profile), freq='H') df = pd.DataFrame.from_dict( {DEMAND: self.demand_profile, SOLAR: np.array([2.0, 0.0, 0.0, 1.0]), WIND: np.array([1.0, 0.0, 0.0, 0.0]), TIME: rng} ) self.profiles = df.set_index(TIME, verify_integrity=True) self.lp = LinearProgramContainer(self.profiles) self.lp.add_demands(GridDemand(DEMAND)) self.solar = GridSource(SOLAR, 1e6, 1e6) self.ng = GridSource(NG, 1e6, 1e6)
def testVariableCreation(self): """Check GridSource creates nameplate / timeslice variables.""" lp = self.lp ng = GridSource(NG, 1e6, 1e6) self.assertIsNone(ng.timeslice_variables) self.assertIsNone(ng.nameplate_variable) lp.add_dispatchable_sources(ng) lp._initialize_solver() self.assertIsNotNone(ng.nameplate_variable) self.assertEqual(len(ng.timeslice_variables), lp.number_of_timeslices)
def testFreeStorageMeansCheapestSource(self): """Verify that free storage selects the cheapest energy supply.""" solar = GridSource(SOLAR, 2.0e6, 0) wind = GridSource(WIND, 2.2e6, 0) storage = GridStorage(STORAGE, 0) lp = self.lp lp.add_nondispatchable_sources(solar, wind) lp.add_storage(storage) self.assertTrue(lp.solve()) npt.assert_almost_equal(wind.get_solution_values(), np.zeros(2)) npt.assert_almost_equal(solar.get_solution_values(), np.array([0.0, 2.0])) npt.assert_almost_equal(storage.get_solution_values(), np.array([1.0, 0.0])) npt.assert_almost_equal(storage.source.get_solution_values(), np.array([1.0, 0.0])) npt.assert_almost_equal(storage.sink.get_solution_values(), np.array([0.0, 1.0]))
def testSolutionValuesCalledBeforeSolve(self): lp = LinearProgramContainer(self.dummy_profile) ng = GridSource(NG, 1e6, 1e6) lp.add_dispatchable_sources(ng) with self.assertRaises(RuntimeError): ng.get_solution_values() with self.assertRaises(RuntimeError): ng.get_nameplate_solution_value()
def testRpsStorage(self): """Storage credits rps if timeslice exceeds demand.""" lp = self.lp wind = GridSource(WIND, 2.0e6, 0, is_rps_source=True) ng = GridSource(NG, 0, 1.0e6) storage = GridRecStorage(STORAGE, 1, 1, 1) lp.add_nondispatchable_sources(wind) lp.add_dispatchable_sources(ng) lp.add_storage(storage) demand_at_0 = self.profiles[DEMAND][0] demand_at_1 = self.profiles[DEMAND][1] total = sum(self.profiles[DEMAND]) for rps in range(0, 120, 10): lp.rps_percent = rps rps_total = total * rps / 100.0 # Wind is only on for one time-slice. wind_at_0 = rps_total wind_nameplate = wind_at_0 golden_wind = wind_nameplate * self.profiles[WIND] # Either we charge at 0, or ng fills remaining. if wind_at_0 >= demand_at_0: storage_at_0 = wind_at_0 - demand_at_0 ng_at_0 = 0 else: storage_at_0 = 0 ng_at_0 = demand_at_0 - wind_at_0 # Discharge everything at 1. storage_discharge_power = storage_at_0 ng_at_1 = demand_at_1 - storage_discharge_power if lp.solve(): self.assertTrue(rps <= 100) npt.assert_almost_equal(wind.get_solution_values(), golden_wind) npt.assert_almost_equal(ng.get_solution_values(), np.array([ng_at_0, ng_at_1])) # Storage at t=1 shows what charged at t=0. npt.assert_almost_equal(storage.get_solution_values(), np.array([0.0, storage_at_0])) else: # Verify no convergence because we asked for a ridiculous rps number. self.assertTrue(rps > 100)
def testMaxPowerCheaperDispatchable(self): """Two Dispatchable Sources. Cheaper one fills up to max_power.""" lp = self.lp max_power = 1 ng1 = GridSource(NG, 1e6, 1e6, max_power=max_power) ng2 = GridSource(NG2, 2e6, 2e6) lp.add_dispatchable_sources(ng1, ng2) self.assertTrue(lp.solve()) max_power_profile = np.array([max_power, 0, 0, max_power]) remaining = self.demand_profile - max_power_profile npt.assert_almost_equal(ng1.get_solution_values(), max_power_profile) npt.assert_almost_equal(ng2.get_solution_values(), remaining) self.assertAlmostEqual(ng1.get_nameplate_solution_value(), max(max_power_profile)) self.assertAlmostEqual(ng2.get_nameplate_solution_value(), max(remaining))
def testMaxEnergyCheaperDispatchable(self): """Two Dispatchable Sources. Cheaper one fills up to max_energy.""" lp = self.lp max_energy = 1.0 ng1 = GridSource(NG, 1e6, 1e6, max_energy=max_energy) ng2 = GridSource(NG2, 2e6, 2e6) lp.add_dispatchable_sources(ng1, ng2) self.assertTrue(lp.solve()) demand_profiles = self.demand_profile demand_energy = sum(demand_profiles) max_energy_profile = (max_energy / demand_energy) * demand_profiles remaining = demand_profiles - max_energy_profile npt.assert_almost_equal(ng1.get_solution_values(), max_energy_profile) npt.assert_almost_equal(ng2.get_solution_values(), remaining) self.assertAlmostEqual(ng1.get_nameplate_solution_value(), max(max_energy_profile)) self.assertAlmostEqual(ng2.get_nameplate_solution_value(), max(remaining))
def setUp(self): self.demand_profile = np.array( [0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0]) impulse_profile = np.zeros(len(self.demand_profile)) impulse_profile[0] = 1.0 self.impulse = impulse_profile rng = pd.date_range('1/1/2011', periods=len(self.demand_profile), freq='H') df = pd.DataFrame.from_dict( {DEMAND: self.demand_profile, SOLAR: impulse_profile, TIME: rng} ) self.profiles = df.set_index(TIME, verify_integrity=True) self.lp = LinearProgramContainer(self.profiles) self.lp.add_demands(GridDemand(DEMAND)) self.solar = GridSource(SOLAR, 1e6, 1e6)
def testCircularRecAccounting(self): """Verify that RECs get stored and accounted for properly.""" lp = self.lp solar = GridSource(SOLAR, 2e6, 2e6, is_rps_source=True) wind = GridSource(WIND, 1e6, 1e6) storage = GridRecStorage(STORAGE, 1, 1, 1, discharge_efficiency=0.5) lp.add_nondispatchable_sources(solar, wind) lp.add_storage(storage) for rps in np.arange(0, 110, 10): lp.rps_percent = rps self.assertTrue(lp.solve()) npt.assert_almost_equal(solar.get_solution_values(), self.profiles[SOLAR].values * rps / storage.discharge_efficiency) npt.assert_almost_equal(wind.get_solution_values(), self.profiles[WIND] * (100 - rps) / storage.discharge_efficiency) rps_credit = np.array([v.solution_value() for v in lp.rps_credit_variables[0]]) self.assertAlmostEqual(sum(rps_credit), rps) rec_storage = storage.rec_storage no_rec_storage = storage.no_rec_storage npt.assert_almost_equal(rps_credit, rec_storage.source.get_solution_values() * storage.discharge_efficiency) npt.assert_almost_equal(self.profiles[DEMAND] - rps_credit, no_rec_storage.source.get_solution_values() * storage.discharge_efficiency)
def testStorageNameplateCost(self): """Keep increasing storage costs until ng finally wins out.""" wind = GridSource(WIND, 1.0e6, 0) storage = GridRecStorage(STORAGE, 0) ng = GridSource(NG, 4.6e6, 0) lp = self.lp lp.add_nondispatchable_sources(wind) lp.add_dispatchable_sources(ng) lp.add_storage(storage) self.assertTrue(lp.solve()) npt.assert_almost_equal(wind.get_solution_values(), np.array([2.0, 0.0])) npt.assert_almost_equal(ng.get_solution_values(), np.zeros(2)) # Change costs. Total wind + storage cost is now (2 + 1)E6. # Still less than ng costs. storage.charge_nameplate_cost = 1.0e6 self.assertTrue(lp.solve()) npt.assert_almost_equal(wind.get_solution_values(), np.array([2.0, 0.0])) npt.assert_almost_equal(ng.get_solution_values(), np.zeros(2)) # Change Costs. Total wind + storage cost is now (2 + 1 + 1)E6. # Still less than ng costs. storage.storage_nameplate_cost = 1.0e6 self.assertTrue(lp.solve()) npt.assert_almost_equal(wind.get_solution_values(), np.array([2.0, 0.0])) npt.assert_almost_equal(ng.get_solution_values(), np.zeros(2)) # Change costs. Total wind + storage cost is now # (2 + 1 + 1 + 1)E6. Now more than ng costs. storage.discharge_nameplate_cost = 1.0e6 self.assertTrue(lp.solve()) npt.assert_almost_equal(wind.get_solution_values(), np.zeros(2)) npt.assert_almost_equal(ng.get_solution_values(), np.array([1.0, 1.0]))
def testAddNonDispatchableSourceWithoutProfile(self): """Ensure NonDispatchable Source without profile gets error.""" with self.assertRaises(KeyError): lp = LinearProgramContainer(self.dummy_profile) lp.add_nondispatchable_sources(GridSource(NG, 1e6, 1e6))
def testAddDispatchableSourceWithProfile(self): """Ensure Dispatchable Source with profile gets error.""" with self.assertRaises(KeyError): lp = LinearProgramContainer(pd.DataFrame({NG: np.ones(4)})) lp.add_dispatchable_sources(GridSource(NG, 1e6, 1e6))