def _one_per_poly(region): """Return three lists of wind, PV and CST generators, one per polygon. >>> from nemo import regions >>> wind, pv, cst = _one_per_poly(regions.tas) >>> len(wind), len(pv), len(cst) (4, 4, 4) """ pv = [] wind = [] cst = [] for poly in region.polygons: wind.append(generators.Wind(poly, 0, configfile.get('generation', 'wind-trace'), poly - 1, delimiter=',', build_limit=polygons.wind_limit[poly], label='poly %d wind' % poly)) pv.append(generators.PV1Axis(poly, 0, configfile.get('generation', 'pv1axis-trace'), poly - 1, build_limit=polygons.pv_limit[poly], label='poly %d PV' % poly)) cst.append(generators.CentralReceiver(poly, 0, 2, 6, configfile.get('generation', 'cst-trace'), poly - 1, build_limit=polygons.cst_limit[poly], label='poly %d CST' % poly)) return wind, pv, cst
def theworks(context): """All technologies.""" re100(context) # pylint: disable=redefined-outer-name egs = generators.Geothermal_EGS(polygons.wildcard, 0, configfile.get('generation', 'egs-geothermal-trace'), 38) hsa = generators.Geothermal_HSA(polygons.wildcard, 0, configfile.get('generation', 'hsa-geothermal-trace'), 38) pt = generators.ParabolicTrough(polygons.wildcard, 0, 2, 6, configfile.get('generation', 'cst-trace'), 12) coal = generators.Black_Coal(polygons.wildcard, 0) coal_ccs = generators.Coal_CCS(polygons.wildcard, 0) ccgt = generators.CCGT(polygons.wildcard, 0) ccgt_ccs = generators.CCGT_CCS(polygons.wildcard, 0) ocgt = generators.OCGT(polygons.wildcard, 0) batt = generators.Battery(polygons.wildcard, 0, 0) diesel = generators.Diesel(polygons.wildcard, 0) dem = generators.DemandResponse(polygons.wildcard, 0, 300) biomass = generators.Biomass(polygons.wildcard, 0) greenpower = generators.GreenPower(polygons.wildcard, 0) btm_pv = generators.Behind_Meter_PV(polygons.wildcard, 0, configfile.get('generation', 'rooftop-pv-trace'), 0) g = context.generators context.generators = [hsa, egs, pt, coal, coal_ccs, ccgt, ccgt_ccs] + g[:-4] + \ [btm_pv, ocgt, diesel, batt, dem, biomass, greenpower]
def plot(context, spills=False, filename=None, showlegend=True, xlim=None): """Produce a pretty plot of supply and demand.""" # aggregate demand demand = context.demand.sum(axis=1) plt.clf() plt.ylabel('Power (MW)') try: title = configfile.get('plot', 'title') except (configparser.NoSectionError, configparser.NoOptionError): title = 'Supply/demand balance' try: title += '\n' + configfile.get('plot', 'subtitle') except (configfile.configparser.NoSectionError, configfile.configparser.NoOptionError): pass plt.suptitle(title) if showlegend: _legend(context) # Plot demand first. plt.plot(demand.index, demand, color='black', linewidth=3 if spills else 2) accum = pd.Series(data=0, index=demand.index) prev = accum.copy() for g in _generator_list(context): idx = context.generators.index(g) accum += context.generation[idx] # Ensure accumulated generation does not exceed demand in any timestep. # (Due to rounding, accum can be close to demand.) assert all(np.logical_or(accum < demand, np.isclose(accum, demand))) plt.plot(accum.index, accum, color='black', linewidth=0.5) plt.fill_between(accum.index, prev, accum, facecolor=g.patch.get_fc(), hatch=g.patch.get_hatch()) prev = accum.copy() # Unmet demand is shaded red. plt.fill_between(accum.index, accum, demand, facecolor='red') if spills: prev = demand.copy() for g in list(g for g in context.generators if g.region() in context.regions): idx = context.generators.index(g) accum += context.spill[idx] plt.plot(accum.index, accum, color='black', linewidth=0.5) plt.fill_between(prev.index, prev, accum, facecolor=g.patch.get_fc(), alpha=0.3) prev = accum.copy() plt.gca().set_xlim(xlim) # set_xlim accepts None plt.gca().xaxis_date() plt.gcf().autofmt_xdate() _, ymax = plt.gca().get_ylim() plt.plot(context.unserved.index, [ymax] * len(context.unserved), "yv", markersize=10, color='red', markeredgecolor='black') if not filename: plt.show() # pragma: no cover else: plt.savefig(filename)
def re100(context): """100% renewable electricity. >>> class C: pass >>> c = C() >>> re100(c) >>> len(c.generators) 184 """ from nemo.generators import CentralReceiver, Wind, PV1Axis, Hydro, PumpedHydro, Biofuel result = [] # The following list is in merit order. for g in [PV1Axis, Wind, PumpedHydro, Hydro, CentralReceiver, Biofuel]: if g == PumpedHydro: result += [h for h in _hydro() if isinstance(h, PumpedHydro)] elif g == Hydro: result += [ h for h in _hydro() if isinstance(h, Hydro) and not isinstance(h, PumpedHydro) ] elif g == Biofuel: for poly in range(1, 44): result.append(g(poly, 0, label='polygon %d GT' % poly)) elif g == PV1Axis: for poly in range(1, 44): result.append( g(poly, 0, configfile.get('generation', 'pv1axis-trace'), poly - 1, build_limit=polygons.pv_limit[poly], label='polygon %d PV' % poly)) elif g == CentralReceiver: for poly in range(1, 44): result.append( g(poly, 0, 2, 6, configfile.get('generation', 'cst-trace'), poly - 1, build_limit=polygons.cst_limit[poly], label='polygon %d CST' % poly)) elif g == Wind: for poly in range(1, 44): result.append( g(poly, 0, configfile.get('generation', 'wind-trace'), poly - 1, delimiter=',', build_limit=polygons.wind_limit[poly], label='polygon %d wind' % poly)) else: # pragma: no cover raise ValueError context.generators = result
def __init__(self): """Initialise a default context.""" self.verbose = False self.track_exchanges = False self.regions = regions.All self.startdate = startdate # Number of timesteps is determined by the number of demand rows. self.hours = len(hourly_regional_demand) # Estimate the number of years from the number of simulation hours. if self.hours == 8760 or self.hours == 8784: self.years = 1 else: self.years = self.hours / (365.25 * 24) self.relstd = 0.002 # 0.002% unserved energy self.generators = [ generators.CCGT(polygons.wildcard, 20000), generators.OCGT(polygons.wildcard, 20000) ] self.demand = hourly_demand.copy() self.timesteps = len(self.demand) self.spill = pd.DataFrame() self.generation = pd.DataFrame() self.unserved = pd.DataFrame() # System non-synchronous penetration limit self.nsp_limit = float(configfile.get('limits', 'nonsync-penetration')) self.exchanges = np.zeros( (self.hours, polygons.numpolygons, polygons.numpolygons)) self.costs = costs.NullCosts()
def re100_geothermal_hsa(context): """100% renewables plus HSA geothermal. >>> class C: pass >>> c = C() >>> re100_geothermal_hsa(c) >>> isinstance(c.generators[0], generators.Geothermal_HSA) True """ re100(context) g = context.generators poly = 38 geo = generators.Geothermal_HSA( poly, 0, configfile.get('generation', 'hsa-geothermal-trace'), poly, 'HSA geothermal') context.generators = [geo] + g
def re100_geothermal_egs(context): """100% renewables plus EGS geothermal. >>> class C: pass >>> c = C() >>> re100_geothermal_egs(c) >>> isinstance(c.generators[0], generators.Geothermal) True """ re100(context) g = context.generators poly = 14 geo = generators.Geothermal_EGS( poly, 0, configfile.get('generation', 'egs-geothermal-trace'), poly, 'EGS geothermal') context.generators = [geo] + g
"""A National Electricity Market (NEM) simulation.""" import urllib.request import urllib.error import urllib.parse import numpy as np import pandas as pd from nemo import configfile from nemo import regions from nemo import polygons # Demand is in 30 minute intervals. NOTE: the number of rows in the # demand file now dictates the number of timesteps in the simulation. urlobj = urllib.request.urlopen(configfile.get('demand', 'demand-trace')) demand = pd.read_csv(urlobj, comment='#', sep=',', parse_dates=[['Date', 'Time']], index_col='Date_Time') # Check for date, time and n demand columns (for n regions). assert len(demand.columns) == regions.numregions # The number of rows must be even. assert len(demand) % 2 == 0, "odd number of rows in half-hourly demand data" # Check demand data starts at midnight startdate = demand.index[0] assert (startdate.hour, startdate.minute, startdate.second) == (0, 30, 0), \ 'demand data must start at midnight'
# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. """A National Electricity Market (NEM) simulation.""" import urllib2 import numpy as np import pandas as pd from nemo import configfile from nemo import regions from nemo import polygons # Demand is in 30 minute intervals. NOTE: the number of rows in the # demand file now dictates the number of timesteps in the simulation. urlobj = urllib2.urlopen(configfile.get('demand', 'demand-trace')) demand = pd.read_csv(urlobj, comment='#', sep=',', parse_dates=[['Date', 'Time']], index_col='Date_Time') # Check for date, time and n demand columns (for n regions). assert len(demand.columns) == regions.numregions # The number of rows must be even. assert len(demand) % 2 == 0, "odd number of rows in half-hourly demand data" # Check demand data starts at midnight startdate = demand.index[0] assert (startdate.hour, startdate.minute, startdate.second) == (0, 30, 0), \ 'demand data must start at midnight'
from nemo import costs from nemo import configfile as cf from nemo import transmission # Conversion factor between MWh and TWh. twh = pow(10., 6) # Ignore possible runtime warnings from SCOOP warnings.simplefilter('ignore', RuntimeWarning) parser = argparse.ArgumentParser( description='Bug reports to: [email protected]') parser.add_argument("-c", "--carbon-price", type=int, default=cf.get('costs', 'co2-price-per-t'), help='carbon price ($/t) [default: %s]' % cf.get('costs', 'co2-price-per-t')) parser.add_argument("-d", "--demand-modifier", type=str, default=[], action="append", help='demand modifier') parser.add_argument("-g", "--generations", type=int, default=cf.get('optimiser', 'generations'), help='generations [default: %s]' % cf.get('optimiser', 'generations')) parser.add_argument("-o",