Module nemo.generators

Simulated electricity generators for the NEMO framework.

Expand source code
# Copyright (C) 2011, 2012, 2013, 2014 Ben Elliston
# Copyright (C) 2014, 2015, 2016 The University of New South Wales
# Copyright (C) 2016, 2017 IT Power (Australia)
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.

"""Simulated electricity generators for the NEMO framework."""

import locale

import urllib.request
import urllib.error
import urllib.parse
import numpy as np
from matplotlib.patches import Patch
import pint

from nemo import polygons


# Needed for currency formatting.
locale.setlocale(locale.LC_ALL, '')

# Default to abbreviated units when formatting
ureg = pint.UnitRegistry()
ureg.default_format = '.2f~P'


class Generator():

    """Base generator class."""

    def __init__(self, polygon, capacity, label):
        """Base Generator constructor.

        Arguments: installed polygon, installed capacity, descriptive label.
        """
        self.setters = [(self.set_capacity, 0, 40)]
        self.storage_p = False
        self.label = label
        self.capacity = capacity
        self.polygon = polygon

        # Sanity check polygon argument.
        assert not isinstance(polygon, polygons.regions.Region)
        assert 0 < polygon <= polygons.numpolygons, polygon

        # Is the generator a rotating machine?
        self.non_synchronous_p = False

        # Time series of dispatched power and spills
        self.series_power = {}
        self.series_spilled = {}

        # By default, generators have infinite ramping capability
        self.power = 0
        self.ramp_up_mw_per_h = np.inf
        self.ramp_down_mw_per_h = np.inf

    def region(self):
        """Return the region the generator is in."""
        return polygons.region(self.polygon)

    def capcost(self, costs):
        """Return the annual capital cost."""
        return costs.capcost_per_kw[type(self)] * self.capacity * 1000

    def opcost(self, costs):
        """Return the annual operating and maintenance cost."""
        return self.fixed_om_costs(costs) + \
            sum(self.series_power.values()) * self.opcost_per_mwh(costs)

    def fixed_om_costs(self, costs):
        """Return the fixed O&M costs."""
        return costs.fixed_om_costs[type(self)] * self.capacity * 1000

    def opcost_per_mwh(self, costs):
        """Return the variable O&M costs."""
        return costs.opcost_per_mwh[type(self)]

    def reset(self):
        """Reset the generator.

        >>> g = Generator(1, 0, 'label')
        >>> g.power = 10
        >>> g.reset()
        >>> g.power
        0
        """
        self.series_power.clear()
        self.series_spilled.clear()
        self.power = 0

    def capfactor(self):
        """Capacity factor of this generator (in %).

        >>> g = Generator(1, 0, 'label')
        >>> g.capfactor()  # doctest: +ELLIPSIS
        nan
        """
        supplied = sum(self.series_power.values())
        hours = len(self.series_power)
        try:
            capfactor = supplied / (self.capacity * hours) * 100
            return capfactor
        except ZeroDivisionError:
            return float('nan')

    def lcoe(self, costs, years):
        """Calculate the LCOE in $/MWh."""
        total_cost = self.capcost(costs) / costs.annuityf * years \
            + self.opcost(costs)
        supplied = sum(self.series_power.values())
        if supplied > 0:
            cost_per_mwh = total_cost / supplied
            return cost_per_mwh
        return np.inf

    def summary(self, context):
        """Return a summary of the generator activity."""
        costs = context.costs
        supplied = sum(self.series_power.values()) * ureg.MWh
        s = 'supplied {}'.format(supplied.to_compact())
        if self.capacity > 0:
            cf = self.capfactor()
            if cf > 0:
                s += ', CF %.1f%%' % cf
        if sum(self.series_spilled.values()) > 0:
            spilled = sum(self.series_spilled.values()) * ureg.MWh
            s += ', surplus {}'.format(spilled.to_compact())
        if self.capcost(costs) > 0:
            s += ', capcost $%s' % locale.format('%d', self.capcost(costs), grouping=True)
        if self.opcost(costs) > 0:
            s += ', opcost $%s' % locale.format('%d', self.opcost(costs), grouping=True)
        lcoe = self.lcoe(costs, context.years)
        if np.isfinite(lcoe) and lcoe > 0:
            s += ', LCOE $%d' % int(lcoe)
        return s

    def set_capacity(self, cap):
        """Change the capacity of the generator to 'cap' GW."""
        self.capacity = cap * 1000

    def __str__(self):
        """A short string representation of the generator."""
        return '%s (%s:%s), %s' \
            % (self.label, self.region(), self.polygon, str(self.capacity * ureg.MW))

    def __repr__(self):
        """A representation of the generator."""
        return self.__str__()


class Wind(Generator):

    """Wind power."""

    patch = Patch(facecolor='green')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, filename, column, delimiter=None, build_limit=None, label='wind'):
        Generator.__init__(self, polygon, capacity, label)
        if build_limit is not None:
            # Override default capacity limit with build_limit
            _, _, limit = self.setters[0]
            self.setters = [(self.set_capacity, 0, min(build_limit, limit))]
        self.non_synchronous_p = True
        if Wind.csvfilename != filename:
            # Optimisation:
            # Only if the filename changes do we invoke genfromtxt.
            urlobj = urllib.request.urlopen(filename)
            Wind.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=delimiter)
            Wind.csvdata = np.maximum(0, Wind.csvdata)
            Wind.csvfilename = filename
        self.generation = Wind.csvdata[::, column]

    def step(self, hr, demand):
        """Step method for wind generators."""
        generation = self.generation[hr] * self.capacity
        power = min(generation, demand)
        spilled = generation - power
        self.series_power[hr] = power
        self.series_spilled[hr] = spilled
        return power, spilled


class PV(Generator):

    """Solar photovoltaic (PV) model."""

    patch = Patch(facecolor='yellow')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, filename, column, build_limit=None, label='PV'):
        Generator.__init__(self, polygon, capacity, label)
        self.non_synchronous_p = True
        if build_limit is not None:
            # Override default capacity limit with build_limit
            _, _, limit = self.setters[0]
            self.setters = [(self.set_capacity, 0, min(build_limit, limit))]
        if PV.csvfilename != filename:
            urlobj = urllib.request.urlopen(filename)
            PV.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=',')
            PV.csvdata = np.maximum(0, PV.csvdata)
            PV.csvfilename = filename
        self.generation = PV.csvdata[::, column]

    def step(self, hr, demand):
        """Step method for PV generators."""
        generation = self.generation[hr] * self.capacity
        power = min(generation, demand)
        spilled = generation - power
        self.series_power[hr] = power
        self.series_spilled[hr] = spilled
        return power, spilled


class PV1Axis(PV):
    """Single-axis tracking PV."""

    patch = Patch(facecolor='lightyellow')

    def __init__(self, polygon, capacity, filename, column, build_limit=None, label='PV 1-axis'):
        PV.__init__(self, polygon, capacity, filename, column, build_limit, label)


class Behind_Meter_PV(PV):
    """Behind the meter PV.

    This stub class allows differentiated PV costs in costs.py."""

    def __init__(self, polygon, capacity, filename, column, build_limit=None, label='Behind-meter PV'):
        PV.__init__(self, polygon, capacity, filename, column, build_limit, label)


class CST(Generator):

    """Concentrating solar thermal (CST) model."""

    patch = Patch(facecolor='yellow')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, sm, shours, filename, column, build_limit=None, label='CST'):
        Generator.__init__(self, polygon, capacity, label)
        if build_limit is not None:
            # Override default capacity limit with build_limit
            _, _, limit = self.setters[0]
            self.setters = [(self.set_capacity, 0, min(build_limit, limit))]
        if CST.csvfilename != filename:
            urlobj = urllib.request.urlopen(filename)
            CST.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=',')
            CST.csvfilename = filename
        self.generation = CST.csvdata[::, column]
        # initialise these for good measure
        self.maxstorage = None
        self.stored = None
        self.set_storage(shours)
        self.set_multiple(sm)

    def set_capacity(self, cap):
        Generator.set_capacity(self, cap)
        self.maxstorage = self.capacity * self.shours

    def set_multiple(self, sm):
        """Change the solar multiple of a CST plant."""
        self.sm = sm

    def set_storage(self, shours):
        """Change the storage capacity of a CST plant."""
        self.shours = shours
        self.maxstorage = self.capacity * shours
        self.stored = 0.5 * self.maxstorage

    def step(self, hr, demand):
        """Step method for CST generators."""
        generation = self.generation[hr] * self.capacity * self.sm
        remainder = min(self.capacity, demand)
        if generation > remainder:
            to_storage = generation - remainder
            generation -= to_storage
            self.stored += to_storage
            self.stored = min(self.stored, self.maxstorage)
        else:
            from_storage = min(remainder - generation, self.stored)
            generation += from_storage
            self.stored -= from_storage
            assert self.stored >= 0
        assert self.stored <= self.maxstorage
        assert self.stored >= 0
        # assert generation <= self.capacity
        self.series_power[hr] = generation
        self.series_spilled[hr] = 0

        if generation > demand:  # pragma: no cover
            # This can happen due to rounding errors.
            generation = demand
        return generation, 0

    def reset(self):
        Generator.reset(self)
        self.stored = 0.5 * self.maxstorage

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', solar mult %.2f' % self.sm + ', %dh storage' % self.shours


class ParabolicTrough(CST):

    """Parabolic trough CST generator.

    This stub class allows differentiated CST costs in costs.py.
    """


class CentralReceiver(CST):

    """Central receiver CST generator.

    This stub class allows differentiated CST costs in costs.py.
    """


class Fuelled(Generator):

    """The class of generators that consume fuel."""

    def __init__(self, polygon, capacity, label):
        Generator.__init__(self, polygon, capacity, label)
        self.runhours = 0

    def reset(self):
        Generator.reset(self)
        self.runhours = 0

    def step(self, hr, demand):
        """Step method for fuelled generators."""
        power = min(self.capacity, demand)
        if power > 0:
            self.runhours += 1
        self.series_power[hr] = power
        return power, 0

    def summary(self, context):
        return Generator.summary(self, context) + ', ran %s hours' \
            % locale.format('%d', self.runhours, grouping=True)


class Hydro(Fuelled):

    """Hydro power stations."""

    patch = Patch(facecolor='lightskyblue')

    def __init__(self, polygon, capacity, label='hydro'):
        Fuelled.__init__(self, polygon, capacity, label)
        # capacity is in MW, but build limit is in GW
        self.setters = [(self.set_capacity, 0, capacity / 1000.)]


class PumpedHydro(Hydro):

    """Pumped storage hydro (PSH) model."""

    patch = Patch(facecolor='powderblue')

    def __init__(self, polygon, capacity, maxstorage, rte=0.8, label='pumped-hydro'):
        Hydro.__init__(self, polygon, capacity, label)
        self.maxstorage = maxstorage
        # Half the water starts in the lower reservoir.
        self.stored = self.maxstorage * .5
        self.rte = rte
        self.storage_p = True
        self.last_run = None

    def store(self, hr, power):
        """Pump water uphill for one hour.

        >>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

        Cannot pump and generate at the same time.
        >>> psh.step(hr=0, demand=100)
        (100, 0)
        >>> psh.store(hr=0, power=250)
        0

        Test filling the store.
        >>> for hour in range(1, 4): psh.store(hr=hour, power=250)
        250
        250
        100.0
        """
        if self.last_run == hr:
            # Can't pump and generate in the same hour.
            return 0
        power = min(power, self.capacity)
        energy = power * self.rte
        if self.stored + energy > self.maxstorage:
            power = (self.maxstorage - self.stored) / self.rte
            self.stored = self.maxstorage
        else:
            self.stored += energy
        if power > 0:
            self.last_run = hr
        return power

    def step(self, hr, demand):
        """
        >>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

        Cannot pump and generate at the same time.
        >>> psh.store(hr=0, power=250)
        250
        >>> psh.step(hr=0, demand=100)
        (0, 0)
        """
        power = min(self.stored, min(self.capacity, demand))
        if self.last_run == hr:
            # Can't pump and generate in the same hour.
            return 0, 0
        self.series_power[hr] = power
        self.stored -= power
        if power > 0:
            self.runhours += 1
            self.last_run = hr
        return power, 0

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', ran %s hours' % locale.format('%d', self.runhours, grouping=True) + \
            ', %s storage' % str(self.maxstorage * ureg.MWh)

    def reset(self):
        Fuelled.reset(self)
        self.stored = self.maxstorage * .5
        self.last_run = None


class Biofuel(Fuelled):

    """Model of open cycle gas turbines burning biofuel."""

    patch = Patch(facecolor='wheat')

    def __init__(self, polygon, capacity, label='biofuel'):
        Fuelled.__init__(self, polygon, capacity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.bioenergy_price_per_gj * (3.6 / .31)  # 31% heat rate
        return vom + fuel_cost


class Biomass(Fuelled):

    """Model of steam turbine burning solid biomass."""

    patch = Patch(facecolor='greenyellow')

    def __init__(self, polygon, capacity, label='biomass', heatrate=0.3):
        Fuelled.__init__(self, polygon, capacity, label)
        self.heatrate = heatrate

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.bioenergy_price_per_gj * (3.6 / self.heatrate)
        return vom + fuel_cost


class Fossil(Fuelled):

    """Base class for GHG emitting power stations."""

    patch = Patch(facecolor='brown')

    def __init__(self, polygon, capacity, intensity, label='fossil'):
        # Greenhouse gas intensity in tonnes per MWh
        Fuelled.__init__(self, polygon, capacity, label)
        self.intensity = intensity

    def summary(self, context):
        return Fuelled.summary(self, context) + ', %.1f Mt CO2' \
            % (sum(self.series_power.values()) * self.intensity / 1000000.)


class Black_Coal(Fossil):

    """Black coal power stations with no CCS."""

    patch = Patch(facecolor='black')

    def __init__(self, polygon, capacity, intensity=0.773, label='coal'):
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.coal_price_per_gj * 8.57
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost


class OCGT(Fossil):

    """Open cycle gas turbine (OCGT) model."""

    patch = Patch(facecolor='purple')

    def __init__(self, polygon, capacity, intensity=0.7, label='OCGT'):
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.gas_price_per_gj * 11.61
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost


class CCGT(Fossil):

    """Combined cycle gas turbine (CCGT) model."""

    patch = Patch(facecolor='purple')

    def __init__(self, polygon, capacity, intensity=0.4, label='CCGT'):
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.gas_price_per_gj * 6.92
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost


class CCS(Fossil):

    """Base class of carbon capture and storage (CCS)."""

    def __init__(self, polygon, capacity, intensity, capture, label='CCS'):
        Fossil.__init__(self, polygon, capacity, intensity, label)
        # capture fraction ranges from 0 to 1
        self.capture = capture

    def summary(self, context):
        return Fossil.summary(self, context) + ', %.1f Mt captured' \
            % (sum(self.series_power.values()) * self.intensity / 1000000. * self.capture)


class Coal_CCS(CCS):

    """Coal with CCS."""

    def __init__(self, polygon, capacity, intensity=0.8, capture=0.85, label='Coal-CCS'):
        CCS.__init__(self, polygon, capacity, intensity, capture, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        # thermal efficiency 31.4% (AETA 2012)
        fuel_cost = costs.coal_price_per_gj * (3.6 / 0.314)
        # t CO2/MWh
        emissions_rate = 0.103
        total_opcost = vom + fuel_cost + \
            (emissions_rate * costs.carbon) + \
            (self.intensity * self.capture * costs.ccs_storage_per_t)
        return total_opcost


class CCGT_CCS(CCS):

    """CCGT with CCS."""

    def __init__(self, polygon, capacity, intensity=0.4, capture=0.85, label='CCGT-CCS'):
        CCS.__init__(self, polygon, capacity, intensity, capture, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        # thermal efficiency 43.1% (AETA 2012)
        fuel_cost = costs.gas_price_per_gj * (3.6 / 0.431)
        total_opcost = vom + fuel_cost + \
            (self.intensity * (1 - self.capture) * costs.carbon) + \
            (self.intensity * self.capture * costs.ccs_storage_per_t)
        return total_opcost


class Diesel(Fossil):

    """Diesel genset model."""

    patch = Patch(facecolor='dimgrey')

    def __init__(self, polygon, capacity, intensity=1.0, kwh_per_litre=3.3, label='diesel'):
        Fossil.__init__(self, polygon, capacity, intensity, label)
        self.kwh_per_litre = kwh_per_litre

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        litres_per_mwh = (1 / self.kwh_per_litre) * 1000
        fuel_cost = costs.diesel_price_per_litre * litres_per_mwh
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost


class Battery(Generator):

    """Battery storage (of any type)."""

    patch = Patch(facecolor='grey')

    def __init__(self, polygon, capacity, maxstorage, discharge_hours=None, rte=0.95, label='battery'):
        Generator.__init__(self, polygon, capacity, label)
        self.non_synchronous_p = True
        self.setters += [(self.set_storage, 0, 10000)]
        self.maxstorage = maxstorage
        self.stored = 0
        self.discharge_hours = discharge_hours if discharge_hours is not None else range(24)
        self.rte = rte
        self.storage_p = True
        self.last_run = None
        self.runhours = 0
        self.chargehours = 0

    def set_storage(self, maxstorage):
        """Vary the storage capacity (recorded in MWh)."""
        self.maxstorage = maxstorage * 1000
        self.stored = 0

    def store(self, hr, power):
        """Store power.

        >>> b = Battery(polygons.wildcard, 400, 1000, rte=1.0)
        >>> b.store(hr=0, power=400)
        400
        >>> b.store(hr=1, power=700)
        400
        >>> b.store(hr=2, power=400)
        200.0
        """
        if self.last_run == hr:
            # Can't charge and discharge in the same hour.
            return 0
        power = min(power, self.capacity)
        energy = power * self.rte
        if self.stored + energy > self.maxstorage:
            power = (self.maxstorage - self.stored) / self.rte
            self.stored = self.maxstorage
        else:
            self.chargehours += 1
            self.stored += energy
        if power > 0:
            self.last_run = hr
        return power

    def step(self, hr, demand):
        """
        >>> dischg = range(18, 24)
        >>> b = Battery(polygons.wildcard, 400, 1000, dischg, rte=1.0)
        >>> b.stored = 400

        Cannot discharge outside of discharge hours.
        >>> b.step(hr=0, demand=200)
        (0, 0)

        Normal operation.
        >>> b.store(hr=0, power=400)
        400
        >>> b.step(hr=18, demand=200)
        (200, 0)

        Cannot generate and then store at the same time.
        >>> b.store(hr=18, power=200)
        0

        Cannot store and then generate at the same time.
        >>> b.store(hr=19, power=200)
        200
        >>> b.step(hr=19, demand=200)
        (0, 0)
        """
        if hr % 24 not in self.discharge_hours:
            return 0, 0

        if hr == self.last_run:
            # Can't charge and discharge in the same hour.
            return 0, 0

        power = min(self.stored, min(self.capacity, demand))
        self.series_power[hr] = power
        self.stored -= power
        if power > 0:
            self.runhours += 1
            self.last_run = hr
        return power, 0

    def reset(self):
        Generator.reset(self)
        self.runhours = 0
        self.chargehours = 0
        self.stored = 0
        self.last_run = None

    def capcost(self, costs):
        # capital cost of batteries has power and energy components
        # $400/kW and $400/kWh respectively
        power = 400 * self.capacity * 1000
        energy = 400 * self.maxstorage * 1000
        return power + energy

    def fixed_om_costs(self, costs):
        # fixed O&M of $28/kW/yr
        fom = 28 * self.capacity * 1000
        return fom

    def opcost_per_mwh(self, costs):
        # per-kWh costs for batteries are included in capital costs
        return 0

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', ran %s hours' % locale.format('%d', self.runhours, grouping=True) + \
            ', charged %s hours' % locale.format('%d', self.chargehours, grouping=True) + \
            ', %s storage' % str(self.maxstorage * ureg.MWh)


class Geothermal(Generator):

    """Geothermal power plant."""

    patch = Patch(facecolor='brown')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, filename, column, label):
        Generator.__init__(self, polygon, capacity, label)
        if Geothermal.csvfilename != filename:
            urlobj = urllib.request.urlopen(filename)
            Geothermal.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=',')
            Geothermal.csvdata = np.maximum(0, Geothermal.csvdata)
            Geothermal.csvfilename = filename
        self.generation = Geothermal.csvdata[::, column]

    def step(self, hr, demand):
        """Step method for geothermal generators."""
        generation = self.generation[hr] * self.capacity
        power = min(generation, demand)
        self.series_power[hr] = power
        self.series_spilled[hr] = 0
        return power, 0


class Geothermal_HSA(Geothermal):

    """Hot sedimentary aquifer (HSA) geothermal model."""

    def __init__(self, polygon, capacity, filename, column, label='HSA geothermal'):
        Geothermal.__init__(self, polygon, capacity, filename, column, label)


class Geothermal_EGS(Geothermal):

    """Enhanced geothermal systems (EGS) geothermal model."""

    def __init__(self, polygon, capacity, filename, column, label='EGS geothermal'):
        Geothermal.__init__(self, polygon, capacity, filename, column, label)


class DemandResponse(Generator):

    """Load shedding generator."""

    patch = Patch(facecolor='white')

    def __init__(self, polygon, capacity, cost_per_mwh, label='demand-response'):
        Generator.__init__(self, polygon, capacity, label)
        self.setters = []
        self.runhours = 0
        self.maxresponse = 0
        self.cost_per_mwh = cost_per_mwh

    def step(self, hr, demand):
        """
        >>> dr = DemandResponse(polygons.wildcard, 500, 1500)
        >>> dr.step(hr=0, demand=200)
        (200, 0)
        >>> dr.runhours
        1
        """
        power = min(self.capacity, demand)
        self.maxresponse = max(self.maxresponse, power)
        self.series_power[hr] = power
        self.series_spilled[hr] = 0
        if power > 0:
            self.runhours += 1
        return power, 0

    def reset(self):
        Generator.reset(self)
        self.runhours = 0
        self.maxresponse = 0

    def opcost_per_mwh(self, costs):
        return self.cost_per_mwh

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', max response %d MW' % self.maxresponse + \
            ', ran %s hours' % locale.format('%d', self.runhours, grouping=True)


class GreenPower(Generator):
    """GreenPower"""

    patch = Patch(facecolor='darkgreen')

    def __init__(self, polygon, capacity, label='GreenPower'):
        """
        >>> g = GreenPower(1, 100)
        >>> g.step(0, 200)
        (100, 0)
        """
        Generator.__init__(self, polygon, capacity, label)

    def step(self, hr, demand):
        """Step method for GreenPower."""
        power = min(self.capacity, demand)
        self.series_power[hr] = power
        return power, 0


class HydrogenStorage():
    """A simple hydrogen storage vessel."""

    def __init__(self, maxstorage, label='Hydrogen storage'):
        # initialise these for good measure
        self.maxstorage = None
        self.storage = None
        self.set_storage(maxstorage)
        self.label = label

    def set_storage(self, maxstorage):
        """Change the storage capacity.
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.set_storage(1200)
        >>> h.maxstorage
        1200
        >>> h.storage
        600.0
        """
        self.maxstorage = maxstorage
        self.storage = self.maxstorage / 2

    def full_p(self):
        """Is the storage full?
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.full_p()
        False
        >>> h.storage = 1000
        >>> h.full_p()
        True
        """
        return self.storage == self.maxstorage

    def charge(self, amt):
        """Charge the storage by amt.
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.charge(100)
        100
        >>> h.charge(600)
        400.0
        >>> h.full_p()
        True
        """
        assert amt >= 0
        delta = min(self.maxstorage - self.storage, amt)
        self.storage = min(self.maxstorage, self.storage + amt)
        return delta

    def discharge(self, amt):
        """Discharge the storage by amt.
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.discharge(100)
        100
        >>> h.discharge(600)
        400.0
        """
        assert amt >= 0
        delta = min(self.storage, amt)
        self.storage = max(0, self.storage - amt)
        return delta


class Electrolyser(Generator):
    """A hydrogen electrolyser."""

    patch = Patch()

    def __init__(self, tank, polygon, capacity, efficiency=0.8, label='Electrolyser'):
        """
        >>> e = Electrolyser(None, 1, 100, 'test')      # doctest: +ELLIPSIS
        Traceback (most recent call last):
          ...
        AssertionError
        >>> h = HydrogenStorage(400, 'test')
        >>> e = Electrolyser(h, 1, 100, efficiency=1.0, label='test')
        >>> print(e)
        test (QLD1:1), 100.00 MW
        >>> e.step(0, 100)
        (0, 0)
        >>> e.store(0, 100) # store 100 MWh of hydrogen
        100.0
        >>> e.store(0, 100) # store another 100 MWh of hydrogen
        100.0
        >>> e.store(0, 100) # tank is full, none stored
        0.0
        """
        assert isinstance(tank, HydrogenStorage)
        Generator.__init__(self, polygon, capacity, label)
        self.storage_p = True
        self.efficiency = efficiency
        self.tank = tank
        self.setters += [(self.tank.set_storage, 0, 10000)]

    def step(self, hr, demand):
        # pylint: disable=no-self-use
        # pylint: disable=unused-argument
        """Return 0 as this is not a generator."""
        return 0, 0

    def store(self, hr, power):
        """Store power."""
        # pylint: disable=unused-argument
        power = min(power, self.capacity)
        stored = self.tank.charge(power * self.efficiency)
        return stored / self.efficiency


class HydrogenGT(Fuelled):
    """A combustion turbine fuelled by hydrogen."""
    patch = Patch(facecolor='violet')

    def __init__(self, tank, polygon, capacity, efficiency=0.36, label='HydrogenGT'):
        """
        >>> h = HydrogenStorage(1000, 'test')
        >>> e = HydrogenGT(h, 1, 100, efficiency=0.5, label='test')
        >>> print(e)
        test (QLD1:1), 100.00 MW
        >>> e.step(0, 100) # discharge 100 MWh-e of hydrogen
        (100.0, 0)
        >>> e.step(0, 100) # discharge another 100 MWh-e of hydrogen
        (100.0, 0)
        >>> h.storage == (1000 / 2.) - (200 / e.efficiency)
        True
        """
        assert isinstance(tank, HydrogenStorage)
        Fuelled.__init__(self, polygon, capacity, label)
        self.tank = tank
        self.efficiency = efficiency

    def step(self, hr, demand):
        # calculate hydrogen requirement
        h = min(self.capacity, demand) / self.efficiency
        # discharge that amount of hydrogen
        power = self.tank.discharge(h) * self.efficiency
        self.series_power[hr] = power
        if power > 0:
            self.runhours += 1
        return power, 0

Classes

class Battery (polygon, capacity, maxstorage, discharge_hours=None, rte=0.95, label='battery')

Battery storage (of any type).

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Battery(Generator):

    """Battery storage (of any type)."""

    patch = Patch(facecolor='grey')

    def __init__(self, polygon, capacity, maxstorage, discharge_hours=None, rte=0.95, label='battery'):
        Generator.__init__(self, polygon, capacity, label)
        self.non_synchronous_p = True
        self.setters += [(self.set_storage, 0, 10000)]
        self.maxstorage = maxstorage
        self.stored = 0
        self.discharge_hours = discharge_hours if discharge_hours is not None else range(24)
        self.rte = rte
        self.storage_p = True
        self.last_run = None
        self.runhours = 0
        self.chargehours = 0

    def set_storage(self, maxstorage):
        """Vary the storage capacity (recorded in MWh)."""
        self.maxstorage = maxstorage * 1000
        self.stored = 0

    def store(self, hr, power):
        """Store power.

        >>> b = Battery(polygons.wildcard, 400, 1000, rte=1.0)
        >>> b.store(hr=0, power=400)
        400
        >>> b.store(hr=1, power=700)
        400
        >>> b.store(hr=2, power=400)
        200.0
        """
        if self.last_run == hr:
            # Can't charge and discharge in the same hour.
            return 0
        power = min(power, self.capacity)
        energy = power * self.rte
        if self.stored + energy > self.maxstorage:
            power = (self.maxstorage - self.stored) / self.rte
            self.stored = self.maxstorage
        else:
            self.chargehours += 1
            self.stored += energy
        if power > 0:
            self.last_run = hr
        return power

    def step(self, hr, demand):
        """
        >>> dischg = range(18, 24)
        >>> b = Battery(polygons.wildcard, 400, 1000, dischg, rte=1.0)
        >>> b.stored = 400

        Cannot discharge outside of discharge hours.
        >>> b.step(hr=0, demand=200)
        (0, 0)

        Normal operation.
        >>> b.store(hr=0, power=400)
        400
        >>> b.step(hr=18, demand=200)
        (200, 0)

        Cannot generate and then store at the same time.
        >>> b.store(hr=18, power=200)
        0

        Cannot store and then generate at the same time.
        >>> b.store(hr=19, power=200)
        200
        >>> b.step(hr=19, demand=200)
        (0, 0)
        """
        if hr % 24 not in self.discharge_hours:
            return 0, 0

        if hr == self.last_run:
            # Can't charge and discharge in the same hour.
            return 0, 0

        power = min(self.stored, min(self.capacity, demand))
        self.series_power[hr] = power
        self.stored -= power
        if power > 0:
            self.runhours += 1
            self.last_run = hr
        return power, 0

    def reset(self):
        Generator.reset(self)
        self.runhours = 0
        self.chargehours = 0
        self.stored = 0
        self.last_run = None

    def capcost(self, costs):
        # capital cost of batteries has power and energy components
        # $400/kW and $400/kWh respectively
        power = 400 * self.capacity * 1000
        energy = 400 * self.maxstorage * 1000
        return power + energy

    def fixed_om_costs(self, costs):
        # fixed O&M of $28/kW/yr
        fom = 28 * self.capacity * 1000
        return fom

    def opcost_per_mwh(self, costs):
        # per-kWh costs for batteries are included in capital costs
        return 0

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', ran %s hours' % locale.format('%d', self.runhours, grouping=True) + \
            ', charged %s hours' % locale.format('%d', self.chargehours, grouping=True) + \
            ', %s storage' % str(self.maxstorage * ureg.MWh)

Ancestors

Class variables

var patch

Methods

def set_storage(self, maxstorage)

Vary the storage capacity (recorded in MWh).

Expand source code
def set_storage(self, maxstorage):
    """Vary the storage capacity (recorded in MWh)."""
    self.maxstorage = maxstorage * 1000
    self.stored = 0
def step(self, hr, demand)
>>> dischg = range(18, 24)
>>> b = Battery(polygons.wildcard, 400, 1000, dischg, rte=1.0)
>>> b.stored = 400

Cannot discharge outside of discharge hours.

>>> b.step(hr=0, demand=200)
(0, 0)

Normal operation.

>>> b.store(hr=0, power=400)
400
>>> b.step(hr=18, demand=200)
(200, 0)

Cannot generate and then store at the same time.

>>> b.store(hr=18, power=200)
0

Cannot store and then generate at the same time.

>>> b.store(hr=19, power=200)
200
>>> b.step(hr=19, demand=200)
(0, 0)
Expand source code
def step(self, hr, demand):
    """
    >>> dischg = range(18, 24)
    >>> b = Battery(polygons.wildcard, 400, 1000, dischg, rte=1.0)
    >>> b.stored = 400

    Cannot discharge outside of discharge hours.
    >>> b.step(hr=0, demand=200)
    (0, 0)

    Normal operation.
    >>> b.store(hr=0, power=400)
    400
    >>> b.step(hr=18, demand=200)
    (200, 0)

    Cannot generate and then store at the same time.
    >>> b.store(hr=18, power=200)
    0

    Cannot store and then generate at the same time.
    >>> b.store(hr=19, power=200)
    200
    >>> b.step(hr=19, demand=200)
    (0, 0)
    """
    if hr % 24 not in self.discharge_hours:
        return 0, 0

    if hr == self.last_run:
        # Can't charge and discharge in the same hour.
        return 0, 0

    power = min(self.stored, min(self.capacity, demand))
    self.series_power[hr] = power
    self.stored -= power
    if power > 0:
        self.runhours += 1
        self.last_run = hr
    return power, 0
def store(self, hr, power)

Store power.

>>> b = Battery(polygons.wildcard, 400, 1000, rte=1.0)
>>> b.store(hr=0, power=400)
400
>>> b.store(hr=1, power=700)
400
>>> b.store(hr=2, power=400)
200.0
Expand source code
def store(self, hr, power):
    """Store power.

    >>> b = Battery(polygons.wildcard, 400, 1000, rte=1.0)
    >>> b.store(hr=0, power=400)
    400
    >>> b.store(hr=1, power=700)
    400
    >>> b.store(hr=2, power=400)
    200.0
    """
    if self.last_run == hr:
        # Can't charge and discharge in the same hour.
        return 0
    power = min(power, self.capacity)
    energy = power * self.rte
    if self.stored + energy > self.maxstorage:
        power = (self.maxstorage - self.stored) / self.rte
        self.stored = self.maxstorage
    else:
        self.chargehours += 1
        self.stored += energy
    if power > 0:
        self.last_run = hr
    return power

Inherited members

class Behind_Meter_PV (polygon, capacity, filename, column, build_limit=None, label='Behind-meter PV')

Behind the meter PV.

This stub class allows differentiated PV costs in costs.py.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Behind_Meter_PV(PV):
    """Behind the meter PV.

    This stub class allows differentiated PV costs in costs.py."""

    def __init__(self, polygon, capacity, filename, column, build_limit=None, label='Behind-meter PV'):
        PV.__init__(self, polygon, capacity, filename, column, build_limit, label)

Ancestors

Inherited members

class Biofuel (polygon, capacity, label='biofuel')

Model of open cycle gas turbines burning biofuel.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Biofuel(Fuelled):

    """Model of open cycle gas turbines burning biofuel."""

    patch = Patch(facecolor='wheat')

    def __init__(self, polygon, capacity, label='biofuel'):
        Fuelled.__init__(self, polygon, capacity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.bioenergy_price_per_gj * (3.6 / .31)  # 31% heat rate
        return vom + fuel_cost

Ancestors

Class variables

var patch

Inherited members

class Biomass (polygon, capacity, label='biomass', heatrate=0.3)

Model of steam turbine burning solid biomass.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Biomass(Fuelled):

    """Model of steam turbine burning solid biomass."""

    patch = Patch(facecolor='greenyellow')

    def __init__(self, polygon, capacity, label='biomass', heatrate=0.3):
        Fuelled.__init__(self, polygon, capacity, label)
        self.heatrate = heatrate

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.bioenergy_price_per_gj * (3.6 / self.heatrate)
        return vom + fuel_cost

Ancestors

Class variables

var patch

Inherited members

class Black_Coal (polygon, capacity, intensity=0.773, label='coal')

Black coal power stations with no CCS.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Black_Coal(Fossil):

    """Black coal power stations with no CCS."""

    patch = Patch(facecolor='black')

    def __init__(self, polygon, capacity, intensity=0.773, label='coal'):
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.coal_price_per_gj * 8.57
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost

Ancestors

Class variables

var patch

Inherited members

class CCGT (polygon, capacity, intensity=0.4, label='CCGT')

Combined cycle gas turbine (CCGT) model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class CCGT(Fossil):

    """Combined cycle gas turbine (CCGT) model."""

    patch = Patch(facecolor='purple')

    def __init__(self, polygon, capacity, intensity=0.4, label='CCGT'):
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.gas_price_per_gj * 6.92
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost

Ancestors

Class variables

var patch

Inherited members

class CCGT_CCS (polygon, capacity, intensity=0.4, capture=0.85, label='CCGT-CCS')

CCGT with CCS.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class CCGT_CCS(CCS):

    """CCGT with CCS."""

    def __init__(self, polygon, capacity, intensity=0.4, capture=0.85, label='CCGT-CCS'):
        CCS.__init__(self, polygon, capacity, intensity, capture, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        # thermal efficiency 43.1% (AETA 2012)
        fuel_cost = costs.gas_price_per_gj * (3.6 / 0.431)
        total_opcost = vom + fuel_cost + \
            (self.intensity * (1 - self.capture) * costs.carbon) + \
            (self.intensity * self.capture * costs.ccs_storage_per_t)
        return total_opcost

Ancestors

Inherited members

class CCS (polygon, capacity, intensity, capture, label='CCS')

Base class of carbon capture and storage (CCS).

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class CCS(Fossil):

    """Base class of carbon capture and storage (CCS)."""

    def __init__(self, polygon, capacity, intensity, capture, label='CCS'):
        Fossil.__init__(self, polygon, capacity, intensity, label)
        # capture fraction ranges from 0 to 1
        self.capture = capture

    def summary(self, context):
        return Fossil.summary(self, context) + ', %.1f Mt captured' \
            % (sum(self.series_power.values()) * self.intensity / 1000000. * self.capture)

Ancestors

Subclasses

Inherited members

class CST (polygon, capacity, sm, shours, filename, column, build_limit=None, label='CST')

Concentrating solar thermal (CST) model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class CST(Generator):

    """Concentrating solar thermal (CST) model."""

    patch = Patch(facecolor='yellow')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, sm, shours, filename, column, build_limit=None, label='CST'):
        Generator.__init__(self, polygon, capacity, label)
        if build_limit is not None:
            # Override default capacity limit with build_limit
            _, _, limit = self.setters[0]
            self.setters = [(self.set_capacity, 0, min(build_limit, limit))]
        if CST.csvfilename != filename:
            urlobj = urllib.request.urlopen(filename)
            CST.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=',')
            CST.csvfilename = filename
        self.generation = CST.csvdata[::, column]
        # initialise these for good measure
        self.maxstorage = None
        self.stored = None
        self.set_storage(shours)
        self.set_multiple(sm)

    def set_capacity(self, cap):
        Generator.set_capacity(self, cap)
        self.maxstorage = self.capacity * self.shours

    def set_multiple(self, sm):
        """Change the solar multiple of a CST plant."""
        self.sm = sm

    def set_storage(self, shours):
        """Change the storage capacity of a CST plant."""
        self.shours = shours
        self.maxstorage = self.capacity * shours
        self.stored = 0.5 * self.maxstorage

    def step(self, hr, demand):
        """Step method for CST generators."""
        generation = self.generation[hr] * self.capacity * self.sm
        remainder = min(self.capacity, demand)
        if generation > remainder:
            to_storage = generation - remainder
            generation -= to_storage
            self.stored += to_storage
            self.stored = min(self.stored, self.maxstorage)
        else:
            from_storage = min(remainder - generation, self.stored)
            generation += from_storage
            self.stored -= from_storage
            assert self.stored >= 0
        assert self.stored <= self.maxstorage
        assert self.stored >= 0
        # assert generation <= self.capacity
        self.series_power[hr] = generation
        self.series_spilled[hr] = 0

        if generation > demand:  # pragma: no cover
            # This can happen due to rounding errors.
            generation = demand
        return generation, 0

    def reset(self):
        Generator.reset(self)
        self.stored = 0.5 * self.maxstorage

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', solar mult %.2f' % self.sm + ', %dh storage' % self.shours

Ancestors

Subclasses

Class variables

var csvdata
var csvfilename
var patch

Methods

def set_multiple(self, sm)

Change the solar multiple of a CST plant.

Expand source code
def set_multiple(self, sm):
    """Change the solar multiple of a CST plant."""
    self.sm = sm
def set_storage(self, shours)

Change the storage capacity of a CST plant.

Expand source code
def set_storage(self, shours):
    """Change the storage capacity of a CST plant."""
    self.shours = shours
    self.maxstorage = self.capacity * shours
    self.stored = 0.5 * self.maxstorage
def step(self, hr, demand)

Step method for CST generators.

Expand source code
def step(self, hr, demand):
    """Step method for CST generators."""
    generation = self.generation[hr] * self.capacity * self.sm
    remainder = min(self.capacity, demand)
    if generation > remainder:
        to_storage = generation - remainder
        generation -= to_storage
        self.stored += to_storage
        self.stored = min(self.stored, self.maxstorage)
    else:
        from_storage = min(remainder - generation, self.stored)
        generation += from_storage
        self.stored -= from_storage
        assert self.stored >= 0
    assert self.stored <= self.maxstorage
    assert self.stored >= 0
    # assert generation <= self.capacity
    self.series_power[hr] = generation
    self.series_spilled[hr] = 0

    if generation > demand:  # pragma: no cover
        # This can happen due to rounding errors.
        generation = demand
    return generation, 0

Inherited members

class CentralReceiver (polygon, capacity, sm, shours, filename, column, build_limit=None, label='CST')

Central receiver CST generator.

This stub class allows differentiated CST costs in costs.py.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class CentralReceiver(CST):

    """Central receiver CST generator.

    This stub class allows differentiated CST costs in costs.py.
    """

Ancestors

Inherited members

class Coal_CCS (polygon, capacity, intensity=0.8, capture=0.85, label='Coal-CCS')

Coal with CCS.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Coal_CCS(CCS):

    """Coal with CCS."""

    def __init__(self, polygon, capacity, intensity=0.8, capture=0.85, label='Coal-CCS'):
        CCS.__init__(self, polygon, capacity, intensity, capture, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        # thermal efficiency 31.4% (AETA 2012)
        fuel_cost = costs.coal_price_per_gj * (3.6 / 0.314)
        # t CO2/MWh
        emissions_rate = 0.103
        total_opcost = vom + fuel_cost + \
            (emissions_rate * costs.carbon) + \
            (self.intensity * self.capture * costs.ccs_storage_per_t)
        return total_opcost

Ancestors

Inherited members

class DemandResponse (polygon, capacity, cost_per_mwh, label='demand-response')

Load shedding generator.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class DemandResponse(Generator):

    """Load shedding generator."""

    patch = Patch(facecolor='white')

    def __init__(self, polygon, capacity, cost_per_mwh, label='demand-response'):
        Generator.__init__(self, polygon, capacity, label)
        self.setters = []
        self.runhours = 0
        self.maxresponse = 0
        self.cost_per_mwh = cost_per_mwh

    def step(self, hr, demand):
        """
        >>> dr = DemandResponse(polygons.wildcard, 500, 1500)
        >>> dr.step(hr=0, demand=200)
        (200, 0)
        >>> dr.runhours
        1
        """
        power = min(self.capacity, demand)
        self.maxresponse = max(self.maxresponse, power)
        self.series_power[hr] = power
        self.series_spilled[hr] = 0
        if power > 0:
            self.runhours += 1
        return power, 0

    def reset(self):
        Generator.reset(self)
        self.runhours = 0
        self.maxresponse = 0

    def opcost_per_mwh(self, costs):
        return self.cost_per_mwh

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', max response %d MW' % self.maxresponse + \
            ', ran %s hours' % locale.format('%d', self.runhours, grouping=True)

Ancestors

Class variables

var patch

Methods

def step(self, hr, demand)
>>> dr = DemandResponse(polygons.wildcard, 500, 1500)
>>> dr.step(hr=0, demand=200)
(200, 0)
>>> dr.runhours
1
Expand source code
def step(self, hr, demand):
    """
    >>> dr = DemandResponse(polygons.wildcard, 500, 1500)
    >>> dr.step(hr=0, demand=200)
    (200, 0)
    >>> dr.runhours
    1
    """
    power = min(self.capacity, demand)
    self.maxresponse = max(self.maxresponse, power)
    self.series_power[hr] = power
    self.series_spilled[hr] = 0
    if power > 0:
        self.runhours += 1
    return power, 0

Inherited members

class Diesel (polygon, capacity, intensity=1.0, kwh_per_litre=3.3, label='diesel')

Diesel genset model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Diesel(Fossil):

    """Diesel genset model."""

    patch = Patch(facecolor='dimgrey')

    def __init__(self, polygon, capacity, intensity=1.0, kwh_per_litre=3.3, label='diesel'):
        Fossil.__init__(self, polygon, capacity, intensity, label)
        self.kwh_per_litre = kwh_per_litre

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        litres_per_mwh = (1 / self.kwh_per_litre) * 1000
        fuel_cost = costs.diesel_price_per_litre * litres_per_mwh
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost

Ancestors

Class variables

var patch

Inherited members

class Electrolyser (tank, polygon, capacity, efficiency=0.8, label='Electrolyser')

A hydrogen electrolyser.

>>> e = Electrolyser(None, 1, 100, 'test')      # doctest: +ELLIPSIS
Traceback (most recent call last):
  ...
AssertionError
>>> h = HydrogenStorage(400, 'test')
>>> e = Electrolyser(h, 1, 100, efficiency=1.0, label='test')
>>> print(e)
test (QLD1:1), 100.00 MW
>>> e.step(0, 100)
(0, 0)
>>> e.store(0, 100) # store 100 MWh of hydrogen
100.0
>>> e.store(0, 100) # store another 100 MWh of hydrogen
100.0
>>> e.store(0, 100) # tank is full, none stored
0.0
Expand source code
class Electrolyser(Generator):
    """A hydrogen electrolyser."""

    patch = Patch()

    def __init__(self, tank, polygon, capacity, efficiency=0.8, label='Electrolyser'):
        """
        >>> e = Electrolyser(None, 1, 100, 'test')      # doctest: +ELLIPSIS
        Traceback (most recent call last):
          ...
        AssertionError
        >>> h = HydrogenStorage(400, 'test')
        >>> e = Electrolyser(h, 1, 100, efficiency=1.0, label='test')
        >>> print(e)
        test (QLD1:1), 100.00 MW
        >>> e.step(0, 100)
        (0, 0)
        >>> e.store(0, 100) # store 100 MWh of hydrogen
        100.0
        >>> e.store(0, 100) # store another 100 MWh of hydrogen
        100.0
        >>> e.store(0, 100) # tank is full, none stored
        0.0
        """
        assert isinstance(tank, HydrogenStorage)
        Generator.__init__(self, polygon, capacity, label)
        self.storage_p = True
        self.efficiency = efficiency
        self.tank = tank
        self.setters += [(self.tank.set_storage, 0, 10000)]

    def step(self, hr, demand):
        # pylint: disable=no-self-use
        # pylint: disable=unused-argument
        """Return 0 as this is not a generator."""
        return 0, 0

    def store(self, hr, power):
        """Store power."""
        # pylint: disable=unused-argument
        power = min(power, self.capacity)
        stored = self.tank.charge(power * self.efficiency)
        return stored / self.efficiency

Ancestors

Class variables

var patch

Methods

def step(self, hr, demand)

Return 0 as this is not a generator.

Expand source code
def step(self, hr, demand):
    # pylint: disable=no-self-use
    # pylint: disable=unused-argument
    """Return 0 as this is not a generator."""
    return 0, 0
def store(self, hr, power)

Store power.

Expand source code
def store(self, hr, power):
    """Store power."""
    # pylint: disable=unused-argument
    power = min(power, self.capacity)
    stored = self.tank.charge(power * self.efficiency)
    return stored / self.efficiency

Inherited members

class Fossil (polygon, capacity, intensity, label='fossil')

Base class for GHG emitting power stations.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Fossil(Fuelled):

    """Base class for GHG emitting power stations."""

    patch = Patch(facecolor='brown')

    def __init__(self, polygon, capacity, intensity, label='fossil'):
        # Greenhouse gas intensity in tonnes per MWh
        Fuelled.__init__(self, polygon, capacity, label)
        self.intensity = intensity

    def summary(self, context):
        return Fuelled.summary(self, context) + ', %.1f Mt CO2' \
            % (sum(self.series_power.values()) * self.intensity / 1000000.)

Ancestors

Subclasses

Class variables

var patch

Inherited members

class Fuelled (polygon, capacity, label)

The class of generators that consume fuel.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Fuelled(Generator):

    """The class of generators that consume fuel."""

    def __init__(self, polygon, capacity, label):
        Generator.__init__(self, polygon, capacity, label)
        self.runhours = 0

    def reset(self):
        Generator.reset(self)
        self.runhours = 0

    def step(self, hr, demand):
        """Step method for fuelled generators."""
        power = min(self.capacity, demand)
        if power > 0:
            self.runhours += 1
        self.series_power[hr] = power
        return power, 0

    def summary(self, context):
        return Generator.summary(self, context) + ', ran %s hours' \
            % locale.format('%d', self.runhours, grouping=True)

Ancestors

Subclasses

Methods

def step(self, hr, demand)

Step method for fuelled generators.

Expand source code
def step(self, hr, demand):
    """Step method for fuelled generators."""
    power = min(self.capacity, demand)
    if power > 0:
        self.runhours += 1
    self.series_power[hr] = power
    return power, 0

Inherited members

class Generator (polygon, capacity, label)

Base generator class.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Generator():

    """Base generator class."""

    def __init__(self, polygon, capacity, label):
        """Base Generator constructor.

        Arguments: installed polygon, installed capacity, descriptive label.
        """
        self.setters = [(self.set_capacity, 0, 40)]
        self.storage_p = False
        self.label = label
        self.capacity = capacity
        self.polygon = polygon

        # Sanity check polygon argument.
        assert not isinstance(polygon, polygons.regions.Region)
        assert 0 < polygon <= polygons.numpolygons, polygon

        # Is the generator a rotating machine?
        self.non_synchronous_p = False

        # Time series of dispatched power and spills
        self.series_power = {}
        self.series_spilled = {}

        # By default, generators have infinite ramping capability
        self.power = 0
        self.ramp_up_mw_per_h = np.inf
        self.ramp_down_mw_per_h = np.inf

    def region(self):
        """Return the region the generator is in."""
        return polygons.region(self.polygon)

    def capcost(self, costs):
        """Return the annual capital cost."""
        return costs.capcost_per_kw[type(self)] * self.capacity * 1000

    def opcost(self, costs):
        """Return the annual operating and maintenance cost."""
        return self.fixed_om_costs(costs) + \
            sum(self.series_power.values()) * self.opcost_per_mwh(costs)

    def fixed_om_costs(self, costs):
        """Return the fixed O&M costs."""
        return costs.fixed_om_costs[type(self)] * self.capacity * 1000

    def opcost_per_mwh(self, costs):
        """Return the variable O&M costs."""
        return costs.opcost_per_mwh[type(self)]

    def reset(self):
        """Reset the generator.

        >>> g = Generator(1, 0, 'label')
        >>> g.power = 10
        >>> g.reset()
        >>> g.power
        0
        """
        self.series_power.clear()
        self.series_spilled.clear()
        self.power = 0

    def capfactor(self):
        """Capacity factor of this generator (in %).

        >>> g = Generator(1, 0, 'label')
        >>> g.capfactor()  # doctest: +ELLIPSIS
        nan
        """
        supplied = sum(self.series_power.values())
        hours = len(self.series_power)
        try:
            capfactor = supplied / (self.capacity * hours) * 100
            return capfactor
        except ZeroDivisionError:
            return float('nan')

    def lcoe(self, costs, years):
        """Calculate the LCOE in $/MWh."""
        total_cost = self.capcost(costs) / costs.annuityf * years \
            + self.opcost(costs)
        supplied = sum(self.series_power.values())
        if supplied > 0:
            cost_per_mwh = total_cost / supplied
            return cost_per_mwh
        return np.inf

    def summary(self, context):
        """Return a summary of the generator activity."""
        costs = context.costs
        supplied = sum(self.series_power.values()) * ureg.MWh
        s = 'supplied {}'.format(supplied.to_compact())
        if self.capacity > 0:
            cf = self.capfactor()
            if cf > 0:
                s += ', CF %.1f%%' % cf
        if sum(self.series_spilled.values()) > 0:
            spilled = sum(self.series_spilled.values()) * ureg.MWh
            s += ', surplus {}'.format(spilled.to_compact())
        if self.capcost(costs) > 0:
            s += ', capcost $%s' % locale.format('%d', self.capcost(costs), grouping=True)
        if self.opcost(costs) > 0:
            s += ', opcost $%s' % locale.format('%d', self.opcost(costs), grouping=True)
        lcoe = self.lcoe(costs, context.years)
        if np.isfinite(lcoe) and lcoe > 0:
            s += ', LCOE $%d' % int(lcoe)
        return s

    def set_capacity(self, cap):
        """Change the capacity of the generator to 'cap' GW."""
        self.capacity = cap * 1000

    def __str__(self):
        """A short string representation of the generator."""
        return '%s (%s:%s), %s' \
            % (self.label, self.region(), self.polygon, str(self.capacity * ureg.MW))

    def __repr__(self):
        """A representation of the generator."""
        return self.__str__()

Subclasses

Methods

def capcost(self, costs)

Return the annual capital cost.

Expand source code
def capcost(self, costs):
    """Return the annual capital cost."""
    return costs.capcost_per_kw[type(self)] * self.capacity * 1000
def capfactor(self)

Capacity factor of this generator (in %).

>>> g = Generator(1, 0, 'label')
>>> g.capfactor()  # doctest: +ELLIPSIS
nan
Expand source code
def capfactor(self):
    """Capacity factor of this generator (in %).

    >>> g = Generator(1, 0, 'label')
    >>> g.capfactor()  # doctest: +ELLIPSIS
    nan
    """
    supplied = sum(self.series_power.values())
    hours = len(self.series_power)
    try:
        capfactor = supplied / (self.capacity * hours) * 100
        return capfactor
    except ZeroDivisionError:
        return float('nan')
def fixed_om_costs(self, costs)

Return the fixed O&M costs.

Expand source code
def fixed_om_costs(self, costs):
    """Return the fixed O&M costs."""
    return costs.fixed_om_costs[type(self)] * self.capacity * 1000
def lcoe(self, costs, years)

Calculate the LCOE in $/MWh.

Expand source code
def lcoe(self, costs, years):
    """Calculate the LCOE in $/MWh."""
    total_cost = self.capcost(costs) / costs.annuityf * years \
        + self.opcost(costs)
    supplied = sum(self.series_power.values())
    if supplied > 0:
        cost_per_mwh = total_cost / supplied
        return cost_per_mwh
    return np.inf
def opcost(self, costs)

Return the annual operating and maintenance cost.

Expand source code
def opcost(self, costs):
    """Return the annual operating and maintenance cost."""
    return self.fixed_om_costs(costs) + \
        sum(self.series_power.values()) * self.opcost_per_mwh(costs)
def opcost_per_mwh(self, costs)

Return the variable O&M costs.

Expand source code
def opcost_per_mwh(self, costs):
    """Return the variable O&M costs."""
    return costs.opcost_per_mwh[type(self)]
def region(self)

Return the region the generator is in.

Expand source code
def region(self):
    """Return the region the generator is in."""
    return polygons.region(self.polygon)
def reset(self)

Reset the generator.

>>> g = Generator(1, 0, 'label')
>>> g.power = 10
>>> g.reset()
>>> g.power
0
Expand source code
def reset(self):
    """Reset the generator.

    >>> g = Generator(1, 0, 'label')
    >>> g.power = 10
    >>> g.reset()
    >>> g.power
    0
    """
    self.series_power.clear()
    self.series_spilled.clear()
    self.power = 0
def set_capacity(self, cap)

Change the capacity of the generator to 'cap' GW.

Expand source code
def set_capacity(self, cap):
    """Change the capacity of the generator to 'cap' GW."""
    self.capacity = cap * 1000
def summary(self, context)

Return a summary of the generator activity.

Expand source code
def summary(self, context):
    """Return a summary of the generator activity."""
    costs = context.costs
    supplied = sum(self.series_power.values()) * ureg.MWh
    s = 'supplied {}'.format(supplied.to_compact())
    if self.capacity > 0:
        cf = self.capfactor()
        if cf > 0:
            s += ', CF %.1f%%' % cf
    if sum(self.series_spilled.values()) > 0:
        spilled = sum(self.series_spilled.values()) * ureg.MWh
        s += ', surplus {}'.format(spilled.to_compact())
    if self.capcost(costs) > 0:
        s += ', capcost $%s' % locale.format('%d', self.capcost(costs), grouping=True)
    if self.opcost(costs) > 0:
        s += ', opcost $%s' % locale.format('%d', self.opcost(costs), grouping=True)
    lcoe = self.lcoe(costs, context.years)
    if np.isfinite(lcoe) and lcoe > 0:
        s += ', LCOE $%d' % int(lcoe)
    return s
class Geothermal (polygon, capacity, filename, column, label)

Geothermal power plant.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Geothermal(Generator):

    """Geothermal power plant."""

    patch = Patch(facecolor='brown')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, filename, column, label):
        Generator.__init__(self, polygon, capacity, label)
        if Geothermal.csvfilename != filename:
            urlobj = urllib.request.urlopen(filename)
            Geothermal.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=',')
            Geothermal.csvdata = np.maximum(0, Geothermal.csvdata)
            Geothermal.csvfilename = filename
        self.generation = Geothermal.csvdata[::, column]

    def step(self, hr, demand):
        """Step method for geothermal generators."""
        generation = self.generation[hr] * self.capacity
        power = min(generation, demand)
        self.series_power[hr] = power
        self.series_spilled[hr] = 0
        return power, 0

Ancestors

Subclasses

Class variables

var csvdata
var csvfilename
var patch

Methods

def step(self, hr, demand)

Step method for geothermal generators.

Expand source code
def step(self, hr, demand):
    """Step method for geothermal generators."""
    generation = self.generation[hr] * self.capacity
    power = min(generation, demand)
    self.series_power[hr] = power
    self.series_spilled[hr] = 0
    return power, 0

Inherited members

class Geothermal_EGS (polygon, capacity, filename, column, label='EGS geothermal')

Enhanced geothermal systems (EGS) geothermal model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Geothermal_EGS(Geothermal):

    """Enhanced geothermal systems (EGS) geothermal model."""

    def __init__(self, polygon, capacity, filename, column, label='EGS geothermal'):
        Geothermal.__init__(self, polygon, capacity, filename, column, label)

Ancestors

Inherited members

class Geothermal_HSA (polygon, capacity, filename, column, label='HSA geothermal')

Hot sedimentary aquifer (HSA) geothermal model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Geothermal_HSA(Geothermal):

    """Hot sedimentary aquifer (HSA) geothermal model."""

    def __init__(self, polygon, capacity, filename, column, label='HSA geothermal'):
        Geothermal.__init__(self, polygon, capacity, filename, column, label)

Ancestors

Inherited members

class GreenPower (polygon, capacity, label='GreenPower')

GreenPower

>>> g = GreenPower(1, 100)
>>> g.step(0, 200)
(100, 0)
Expand source code
class GreenPower(Generator):
    """GreenPower"""

    patch = Patch(facecolor='darkgreen')

    def __init__(self, polygon, capacity, label='GreenPower'):
        """
        >>> g = GreenPower(1, 100)
        >>> g.step(0, 200)
        (100, 0)
        """
        Generator.__init__(self, polygon, capacity, label)

    def step(self, hr, demand):
        """Step method for GreenPower."""
        power = min(self.capacity, demand)
        self.series_power[hr] = power
        return power, 0

Ancestors

Class variables

var patch

Methods

def step(self, hr, demand)

Step method for GreenPower.

Expand source code
def step(self, hr, demand):
    """Step method for GreenPower."""
    power = min(self.capacity, demand)
    self.series_power[hr] = power
    return power, 0

Inherited members

class Hydro (polygon, capacity, label='hydro')

Hydro power stations.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Hydro(Fuelled):

    """Hydro power stations."""

    patch = Patch(facecolor='lightskyblue')

    def __init__(self, polygon, capacity, label='hydro'):
        Fuelled.__init__(self, polygon, capacity, label)
        # capacity is in MW, but build limit is in GW
        self.setters = [(self.set_capacity, 0, capacity / 1000.)]

Ancestors

Subclasses

Class variables

var patch

Inherited members

class HydrogenGT (tank, polygon, capacity, efficiency=0.36, label='HydrogenGT')

A combustion turbine fuelled by hydrogen.

>>> h = HydrogenStorage(1000, 'test')
>>> e = HydrogenGT(h, 1, 100, efficiency=0.5, label='test')
>>> print(e)
test (QLD1:1), 100.00 MW
>>> e.step(0, 100) # discharge 100 MWh-e of hydrogen
(100.0, 0)
>>> e.step(0, 100) # discharge another 100 MWh-e of hydrogen
(100.0, 0)
>>> h.storage == (1000 / 2.) - (200 / e.efficiency)
True
Expand source code
class HydrogenGT(Fuelled):
    """A combustion turbine fuelled by hydrogen."""
    patch = Patch(facecolor='violet')

    def __init__(self, tank, polygon, capacity, efficiency=0.36, label='HydrogenGT'):
        """
        >>> h = HydrogenStorage(1000, 'test')
        >>> e = HydrogenGT(h, 1, 100, efficiency=0.5, label='test')
        >>> print(e)
        test (QLD1:1), 100.00 MW
        >>> e.step(0, 100) # discharge 100 MWh-e of hydrogen
        (100.0, 0)
        >>> e.step(0, 100) # discharge another 100 MWh-e of hydrogen
        (100.0, 0)
        >>> h.storage == (1000 / 2.) - (200 / e.efficiency)
        True
        """
        assert isinstance(tank, HydrogenStorage)
        Fuelled.__init__(self, polygon, capacity, label)
        self.tank = tank
        self.efficiency = efficiency

    def step(self, hr, demand):
        # calculate hydrogen requirement
        h = min(self.capacity, demand) / self.efficiency
        # discharge that amount of hydrogen
        power = self.tank.discharge(h) * self.efficiency
        self.series_power[hr] = power
        if power > 0:
            self.runhours += 1
        return power, 0

Ancestors

Class variables

var patch

Inherited members

class HydrogenStorage (maxstorage, label='Hydrogen storage')

A simple hydrogen storage vessel.

Expand source code
class HydrogenStorage():
    """A simple hydrogen storage vessel."""

    def __init__(self, maxstorage, label='Hydrogen storage'):
        # initialise these for good measure
        self.maxstorage = None
        self.storage = None
        self.set_storage(maxstorage)
        self.label = label

    def set_storage(self, maxstorage):
        """Change the storage capacity.
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.set_storage(1200)
        >>> h.maxstorage
        1200
        >>> h.storage
        600.0
        """
        self.maxstorage = maxstorage
        self.storage = self.maxstorage / 2

    def full_p(self):
        """Is the storage full?
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.full_p()
        False
        >>> h.storage = 1000
        >>> h.full_p()
        True
        """
        return self.storage == self.maxstorage

    def charge(self, amt):
        """Charge the storage by amt.
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.charge(100)
        100
        >>> h.charge(600)
        400.0
        >>> h.full_p()
        True
        """
        assert amt >= 0
        delta = min(self.maxstorage - self.storage, amt)
        self.storage = min(self.maxstorage, self.storage + amt)
        return delta

    def discharge(self, amt):
        """Discharge the storage by amt.
        >>> h = HydrogenStorage(1000, 'test')
        >>> h.discharge(100)
        100
        >>> h.discharge(600)
        400.0
        """
        assert amt >= 0
        delta = min(self.storage, amt)
        self.storage = max(0, self.storage - amt)
        return delta

Methods

def charge(self, amt)

Charge the storage by amt.

>>> h = HydrogenStorage(1000, 'test')
>>> h.charge(100)
100
>>> h.charge(600)
400.0
>>> h.full_p()
True
Expand source code
def charge(self, amt):
    """Charge the storage by amt.
    >>> h = HydrogenStorage(1000, 'test')
    >>> h.charge(100)
    100
    >>> h.charge(600)
    400.0
    >>> h.full_p()
    True
    """
    assert amt >= 0
    delta = min(self.maxstorage - self.storage, amt)
    self.storage = min(self.maxstorage, self.storage + amt)
    return delta
def discharge(self, amt)

Discharge the storage by amt.

>>> h = HydrogenStorage(1000, 'test')
>>> h.discharge(100)
100
>>> h.discharge(600)
400.0
Expand source code
def discharge(self, amt):
    """Discharge the storage by amt.
    >>> h = HydrogenStorage(1000, 'test')
    >>> h.discharge(100)
    100
    >>> h.discharge(600)
    400.0
    """
    assert amt >= 0
    delta = min(self.storage, amt)
    self.storage = max(0, self.storage - amt)
    return delta
def full_p(self)

Is the storage full?

>>> h = HydrogenStorage(1000, 'test')
>>> h.full_p()
False
>>> h.storage = 1000
>>> h.full_p()
True
Expand source code
def full_p(self):
    """Is the storage full?
    >>> h = HydrogenStorage(1000, 'test')
    >>> h.full_p()
    False
    >>> h.storage = 1000
    >>> h.full_p()
    True
    """
    return self.storage == self.maxstorage
def set_storage(self, maxstorage)

Change the storage capacity.

>>> h = HydrogenStorage(1000, 'test')
>>> h.set_storage(1200)
>>> h.maxstorage
1200
>>> h.storage
600.0
Expand source code
def set_storage(self, maxstorage):
    """Change the storage capacity.
    >>> h = HydrogenStorage(1000, 'test')
    >>> h.set_storage(1200)
    >>> h.maxstorage
    1200
    >>> h.storage
    600.0
    """
    self.maxstorage = maxstorage
    self.storage = self.maxstorage / 2
class OCGT (polygon, capacity, intensity=0.7, label='OCGT')

Open cycle gas turbine (OCGT) model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class OCGT(Fossil):

    """Open cycle gas turbine (OCGT) model."""

    patch = Patch(facecolor='purple')

    def __init__(self, polygon, capacity, intensity=0.7, label='OCGT'):
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.gas_price_per_gj * 11.61
        total_opcost = vom + fuel_cost + self.intensity * costs.carbon
        return total_opcost

Ancestors

Class variables

var patch

Inherited members

class PV (polygon, capacity, filename, column, build_limit=None, label='PV')

Solar photovoltaic (PV) model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class PV(Generator):

    """Solar photovoltaic (PV) model."""

    patch = Patch(facecolor='yellow')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, filename, column, build_limit=None, label='PV'):
        Generator.__init__(self, polygon, capacity, label)
        self.non_synchronous_p = True
        if build_limit is not None:
            # Override default capacity limit with build_limit
            _, _, limit = self.setters[0]
            self.setters = [(self.set_capacity, 0, min(build_limit, limit))]
        if PV.csvfilename != filename:
            urlobj = urllib.request.urlopen(filename)
            PV.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=',')
            PV.csvdata = np.maximum(0, PV.csvdata)
            PV.csvfilename = filename
        self.generation = PV.csvdata[::, column]

    def step(self, hr, demand):
        """Step method for PV generators."""
        generation = self.generation[hr] * self.capacity
        power = min(generation, demand)
        spilled = generation - power
        self.series_power[hr] = power
        self.series_spilled[hr] = spilled
        return power, spilled

Ancestors

Subclasses

Class variables

var csvdata
var csvfilename
var patch

Methods

def step(self, hr, demand)

Step method for PV generators.

Expand source code
def step(self, hr, demand):
    """Step method for PV generators."""
    generation = self.generation[hr] * self.capacity
    power = min(generation, demand)
    spilled = generation - power
    self.series_power[hr] = power
    self.series_spilled[hr] = spilled
    return power, spilled

Inherited members

class PV1Axis (polygon, capacity, filename, column, build_limit=None, label='PV 1-axis')

Single-axis tracking PV.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class PV1Axis(PV):
    """Single-axis tracking PV."""

    patch = Patch(facecolor='lightyellow')

    def __init__(self, polygon, capacity, filename, column, build_limit=None, label='PV 1-axis'):
        PV.__init__(self, polygon, capacity, filename, column, build_limit, label)

Ancestors

Class variables

var patch

Inherited members

class ParabolicTrough (polygon, capacity, sm, shours, filename, column, build_limit=None, label='CST')

Parabolic trough CST generator.

This stub class allows differentiated CST costs in costs.py.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class ParabolicTrough(CST):

    """Parabolic trough CST generator.

    This stub class allows differentiated CST costs in costs.py.
    """

Ancestors

Inherited members

class PumpedHydro (polygon, capacity, maxstorage, rte=0.8, label='pumped-hydro')

Pumped storage hydro (PSH) model.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class PumpedHydro(Hydro):

    """Pumped storage hydro (PSH) model."""

    patch = Patch(facecolor='powderblue')

    def __init__(self, polygon, capacity, maxstorage, rte=0.8, label='pumped-hydro'):
        Hydro.__init__(self, polygon, capacity, label)
        self.maxstorage = maxstorage
        # Half the water starts in the lower reservoir.
        self.stored = self.maxstorage * .5
        self.rte = rte
        self.storage_p = True
        self.last_run = None

    def store(self, hr, power):
        """Pump water uphill for one hour.

        >>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

        Cannot pump and generate at the same time.
        >>> psh.step(hr=0, demand=100)
        (100, 0)
        >>> psh.store(hr=0, power=250)
        0

        Test filling the store.
        >>> for hour in range(1, 4): psh.store(hr=hour, power=250)
        250
        250
        100.0
        """
        if self.last_run == hr:
            # Can't pump and generate in the same hour.
            return 0
        power = min(power, self.capacity)
        energy = power * self.rte
        if self.stored + energy > self.maxstorage:
            power = (self.maxstorage - self.stored) / self.rte
            self.stored = self.maxstorage
        else:
            self.stored += energy
        if power > 0:
            self.last_run = hr
        return power

    def step(self, hr, demand):
        """
        >>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

        Cannot pump and generate at the same time.
        >>> psh.store(hr=0, power=250)
        250
        >>> psh.step(hr=0, demand=100)
        (0, 0)
        """
        power = min(self.stored, min(self.capacity, demand))
        if self.last_run == hr:
            # Can't pump and generate in the same hour.
            return 0, 0
        self.series_power[hr] = power
        self.stored -= power
        if power > 0:
            self.runhours += 1
            self.last_run = hr
        return power, 0

    def summary(self, context):
        return Generator.summary(self, context) + \
            ', ran %s hours' % locale.format('%d', self.runhours, grouping=True) + \
            ', %s storage' % str(self.maxstorage * ureg.MWh)

    def reset(self):
        Fuelled.reset(self)
        self.stored = self.maxstorage * .5
        self.last_run = None

Ancestors

Class variables

var patch

Methods

def step(self, hr, demand)
>>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

Cannot pump and generate at the same time.

>>> psh.store(hr=0, power=250)
250
>>> psh.step(hr=0, demand=100)
(0, 0)
Expand source code
def step(self, hr, demand):
    """
    >>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

    Cannot pump and generate at the same time.
    >>> psh.store(hr=0, power=250)
    250
    >>> psh.step(hr=0, demand=100)
    (0, 0)
    """
    power = min(self.stored, min(self.capacity, demand))
    if self.last_run == hr:
        # Can't pump and generate in the same hour.
        return 0, 0
    self.series_power[hr] = power
    self.stored -= power
    if power > 0:
        self.runhours += 1
        self.last_run = hr
    return power, 0
def store(self, hr, power)

Pump water uphill for one hour.

>>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

Cannot pump and generate at the same time.

>>> psh.step(hr=0, demand=100)
(100, 0)
>>> psh.store(hr=0, power=250)
0

Test filling the store.

>>> for hour in range(1, 4): psh.store(hr=hour, power=250)
250
250
100.0
Expand source code
def store(self, hr, power):
    """Pump water uphill for one hour.

    >>> psh = PumpedHydro(polygons.wildcard, 250, 1000, rte=1.0)

    Cannot pump and generate at the same time.
    >>> psh.step(hr=0, demand=100)
    (100, 0)
    >>> psh.store(hr=0, power=250)
    0

    Test filling the store.
    >>> for hour in range(1, 4): psh.store(hr=hour, power=250)
    250
    250
    100.0
    """
    if self.last_run == hr:
        # Can't pump and generate in the same hour.
        return 0
    power = min(power, self.capacity)
    energy = power * self.rte
    if self.stored + energy > self.maxstorage:
        power = (self.maxstorage - self.stored) / self.rte
        self.stored = self.maxstorage
    else:
        self.stored += energy
    if power > 0:
        self.last_run = hr
    return power

Inherited members

class Wind (polygon, capacity, filename, column, delimiter=None, build_limit=None, label='wind')

Wind power.

Base Generator constructor.

Arguments: installed polygon, installed capacity, descriptive label.

Expand source code
class Wind(Generator):

    """Wind power."""

    patch = Patch(facecolor='green')
    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, filename, column, delimiter=None, build_limit=None, label='wind'):
        Generator.__init__(self, polygon, capacity, label)
        if build_limit is not None:
            # Override default capacity limit with build_limit
            _, _, limit = self.setters[0]
            self.setters = [(self.set_capacity, 0, min(build_limit, limit))]
        self.non_synchronous_p = True
        if Wind.csvfilename != filename:
            # Optimisation:
            # Only if the filename changes do we invoke genfromtxt.
            urlobj = urllib.request.urlopen(filename)
            Wind.csvdata = np.genfromtxt(urlobj, comments='#', delimiter=delimiter)
            Wind.csvdata = np.maximum(0, Wind.csvdata)
            Wind.csvfilename = filename
        self.generation = Wind.csvdata[::, column]

    def step(self, hr, demand):
        """Step method for wind generators."""
        generation = self.generation[hr] * self.capacity
        power = min(generation, demand)
        spilled = generation - power
        self.series_power[hr] = power
        self.series_spilled[hr] = spilled
        return power, spilled

Ancestors

Class variables

var csvdata
var csvfilename
var patch

Methods

def step(self, hr, demand)

Step method for wind generators.

Expand source code
def step(self, hr, demand):
    """Step method for wind generators."""
    generation = self.generation[hr] * self.capacity
    power = min(generation, demand)
    spilled = generation - power
    self.series_power[hr] = power
    self.series_spilled[hr] = spilled
    return power, spilled

Inherited members