Module nemo.generators

Simulated electricity generators for the NEMO framework.

Classes

class Battery (polygon, capacity, shours, battery, label=None, discharge_hours=None)
Expand source code
class Battery(Generator):
    """Battery storage (of any type)."""

    # Lifespan of the battery in years
    lifetime = 15

    # storage durations (in hours)
    durations = (1, 2, 4, 8, 12, 24)

    patch = Patch(facecolor='#00a2fa')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, shours, battery,
                 label=None, discharge_hours=None):
        """Construct a battery generator.

        battery must be an instance of storage.BatteryStorage.
        shours is the number of hours of storage at full load.
        discharge_hours is a list of hours when discharging can occur.
        """
        if not isinstance(battery, storage.BatteryStorage):
            raise TypeError
        Generator.__init__(self, polygon, capacity, label)
        self.battery = battery
        self.runhours = 0
        self.shours = shours
        if shours not in self.durations:
            raise ValueError(shours)
        if capacity * shours != battery.maxstorage:
            raise ValueError
        self.discharge_hours = discharge_hours \
            if discharge_hours is not None else range(18, 24)

    def set_capacity(self, cap):
        """Change the capacity of the generator to cap GW."""
        Generator.set_capacity(self, cap)
        # now alter the storage to match the new capacity
        newmax = self.capacity * self.shours
        self.battery.set_storage(newmax)

    def step(self, hour, demand):
        """Specialised step method for batteries."""
        if self.battery.empty_p() or \
           hour % 24 not in self.discharge_hours:
            self.series_power[hour] = 0
            self.series_spilled[hour] = 0
            return 0, 0

        power = min(self.battery.storage, self.capacity, demand)
        self.battery.discharge(power)
        self.series_power[hour] = power
        self.series_spilled[hour] = 0
        if power > 0:
            self.runhours += 1
        return power, 0

    def reset(self):
        """Reset the generator."""
        Generator.reset(self)
        self.battery.reset()
        self.runhours = 0

    def soc(self):
        """Return the battery SOC (state of charge)."""
        return self.battery.soc()

    def capcost(self, costs):
        """Return the capital cost."""
        kwh = self.battery.maxstorage * 1000
        if self.shours not in self.durations:
            raise ValueError(self.shours)
        cost_per_kwh = costs.totcost_per_kwh[type(self)][self.shours]
        return kwh * cost_per_kwh

    def fixed_om_costs(self, costs):
        """Return the fixed O&M costs."""
        return 0

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

        Per-kWh costs for batteries are included in the capital cost.
        """
        return 0

    def summary(self, context):
        """Return a summary of the generator activity."""
        return Generator.summary(self, context) + \
            f', {self.shours}h storage' + \
            f', ran {thousands(self.runhours)} hours'

Battery storage (of any type).

Construct a battery generator.

battery must be an instance of storage.BatteryStorage. shours is the number of hours of storage at full load. discharge_hours is a list of hours when discharging can occur.

Ancestors

Class variables

var durations
var lifetime
var patch

Colour for plotting

Methods

def opcost_per_mwh(self, costs)
Expand source code
def opcost_per_mwh(self, costs):
    """Return the variable O&M costs.

    Per-kWh costs for batteries are included in the capital cost.
    """
    return 0

Return the variable O&M costs.

Per-kWh costs for batteries are included in the capital cost.

def soc(self)
Expand source code
def soc(self):
    """Return the battery SOC (state of charge)."""
    return self.battery.soc()

Return the battery SOC (state of charge).

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Specialised step method for batteries."""
    if self.battery.empty_p() or \
       hour % 24 not in self.discharge_hours:
        self.series_power[hour] = 0
        self.series_spilled[hour] = 0
        return 0, 0

    power = min(self.battery.storage, self.capacity, demand)
    self.battery.discharge(power)
    self.series_power[hour] = power
    self.series_spilled[hour] = 0
    if power > 0:
        self.runhours += 1
    return power, 0

Specialised step method for batteries.

Inherited members

class BatteryLoad (polygon, capacity, battery, label=None, discharge_hours=None, rte=0.95)
Expand source code
class BatteryLoad(Storage, Generator):
    """Battery storage (load side)."""

    patch = Patch(facecolor='#b2daef')
    """Colour for plotting"""
    synchronous_p = False
    """Is this a synchronous generator?"""

    def __init__(self, polygon, capacity, battery, label=None,
                 discharge_hours=None, rte=0.95):
        """Construct a battery load (battery charging).

        battery must be an instance of storage.BatteryStorage.
        discharge_hours is a list of hours when discharging can occur
          (or, rather, when charging cannot occur).
        """
        Storage.__init__(self)
        Generator.__init__(self, polygon, capacity, label)
        if not isinstance(battery, storage.BatteryStorage):
            raise TypeError
        self.battery = battery
        self.rte = rte
        self.discharge_hours = discharge_hours \
            if discharge_hours is not None else range(18, 24)

    def step(self, hour, demand):
        """Return 0 as this is not a generator."""
        return 0, 0

    def store(self, hour, power):
        """Store power."""
        if power <= 0:
            msg = f'{power} is <= 0'
            raise ValueError(msg)

        if self.battery.full_p() or \
           hour % 24 in self.discharge_hours:
            return 0

        power = min(self.charge_capacity(self, hour), power,
                    self.capacity)
        stored = self.battery.charge(power * self.rte)
        if power > 0:
            self.record(hour, stored / self.rte)
        return stored / self.rte

    def reset(self):
        """Reset the generator."""
        Generator.reset(self)
        Storage.reset(self)
        self.battery.reset()

    def series(self):
        """Return the combined series."""
        dict1 = Generator.series(self)
        dict2 = Storage.series(self)
        dict1.update(dict2)
        return dict1

    def soc(self):
        """Return the battery SOC (state of charge)."""
        return self.battery.soc()

    # Battery costs are all calculated on the discharge side.
    def capcost(self, costs):
        """Return the capital cost."""
        return 0

    def fixed_om_costs(self, costs):
        """Return the fixed O&M costs."""
        return 0

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

    def summary(self, context):
        """Return a summary of the generator activity."""
        mwh = self.battery.maxstorage * ureg.MWh
        return Generator.summary(self, context) + \
            f', charged {thousands(len(self.series_charge))} hours' + \
            f', {mwh.to_compact()} storage'

Battery storage (load side).

Construct a battery load (battery charging).

battery must be an instance of storage.BatteryStorage. discharge_hours is a list of hours when discharging can occur (or, rather, when charging cannot occur).

Ancestors

Class variables

var patch

Colour for plotting

Methods

def reset(self)
Expand source code
def reset(self):
    """Reset the generator."""
    Generator.reset(self)
    Storage.reset(self)
    self.battery.reset()

Reset the generator.

def series(self)
Expand source code
def series(self):
    """Return the combined series."""
    dict1 = Generator.series(self)
    dict2 = Storage.series(self)
    dict1.update(dict2)
    return dict1

Return the combined series.

def soc(self)
Expand source code
def soc(self):
    """Return the battery SOC (state of charge)."""
    return self.battery.soc()

Return the battery SOC (state of charge).

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Return 0 as this is not a generator."""
    return 0, 0

Return 0 as this is not a generator.

def store(self, hour, power)
Expand source code
def store(self, hour, power):
    """Store power."""
    if power <= 0:
        msg = f'{power} is <= 0'
        raise ValueError(msg)

    if self.battery.full_p() or \
       hour % 24 in self.discharge_hours:
        return 0

    power = min(self.charge_capacity(self, hour), power,
                self.capacity)
    stored = self.battery.charge(power * self.rte)
    if power > 0:
        self.record(hour, stored / self.rte)
    return stored / self.rte

Store power.

Inherited members

class Behind_Meter_PV (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class Behind_Meter_PV(PV):
    """Behind the meter PV."""

    patch = Patch(facecolor='#ffe03d')

Behind the meter PV.

Construct a generator with a specified trace file.

Ancestors

Class variables

var patch

Inherited members

class Biofuel (polygon, capacity, label=None)
Expand source code
class Biofuel(Fuelled):
    """Model of open cycle gas turbines burning biofuel."""

    patch = Patch(facecolor='wheat')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, label=None):
        """Construct a biofuel generator."""
        Fuelled.__init__(self, polygon, capacity, label)

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

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

    def opcost_per_mwh(self, costs):
        """Return the variable O&M costs."""
        vom = costs.opcost_per_mwh[OCGT]
        fuel_cost = costs.bioenergy_price_per_gj * (3.6 / .31)  # 31% heat rate
        return vom + fuel_cost

Model of open cycle gas turbines burning biofuel.

Construct a biofuel generator.

Ancestors

Class variables

var patch

Colour for plotting

Methods

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

Return the capital cost (of an OCGT).

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

Return the fixed O&M costs (of an OCGT).

Inherited members

class Biomass (polygon, capacity, label=None, heatrate=0.3)
Expand source code
class Biomass(Fuelled):
    """Model of steam turbine burning solid biomass."""

    patch = Patch(facecolor='#1d7a7a')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, label=None, heatrate=0.3):
        """Construct a biomass generator."""
        Fuelled.__init__(self, polygon, capacity, label)
        self.heatrate = heatrate

    def opcost_per_mwh(self, costs):
        """Return the variable O&M costs."""
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.bioenergy_price_per_gj * (3.6 / self.heatrate)
        return vom + fuel_cost

Model of steam turbine burning solid biomass.

Construct a biomass generator.

Ancestors

Class variables

var patch

Colour for plotting

Inherited members

class Black_Coal (polygon, capacity, intensity=0.773, label=None)
Expand source code
class Black_Coal(Fossil):
    """Black coal power stations with no CCS."""

    patch = Patch(facecolor='#121212')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, intensity=0.773, label=None):
        """Construct a black coal generator."""
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        """Return the variable O&M costs."""
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.coal_price_per_gj * 8.57
        carbon_cost = self.intensity * costs.carbon
        return vom + fuel_cost + carbon_cost

Black coal power stations with no CCS.

Construct a black coal generator.

Ancestors

Inherited members

class Block (polygon, capacity, label=None)
Expand source code
class Block(Generator):
    """A simple block generator."""

    patch = Patch(facecolor='darkgreen')
    """Colour for plotting"""

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

A simple block generator.

Construct a base Generator.

Arguments: installed polygon, installed capacity, descriptive label.

Ancestors

Class variables

var patch

Colour for plotting

Methods

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

Step method for GreenPower.

Inherited members

class CCGT (polygon, capacity, intensity=0.4, label=None)
Expand source code
class CCGT(Fossil):
    """Combined cycle gas turbine (CCGT) model."""

    patch = Patch(facecolor='#fdb462')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, intensity=0.4, label=None):
        """Construct a CCGT generator."""
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        """Return the variable O&M costs."""
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.gas_price_per_gj * 6.92
        carbon_cost = self.intensity * costs.carbon
        return vom + fuel_cost + carbon_cost

Combined cycle gas turbine (CCGT) model.

Construct a CCGT generator.

Ancestors

Inherited members

class CCGT_CCS (polygon, capacity, intensity=0.4, capture=0.85, label=None)
Expand source code
class CCGT_CCS(CCS):
    """CCGT with CCS."""

    def __init__(self, polygon, capacity, intensity=0.4, capture=0.85,
                 label=None):
        """Construct a CCGT (with CCS) generator.

        Emissions capture rate is given in the range 0 to 1.
        """
        CCS.__init__(self, polygon, capacity, intensity, capture, label)

    def opcost_per_mwh(self, costs):
        """Return the variable O&M 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)
        carbon_cost = \
            self.intensity * (1 - self.capture) * costs.carbon + \
            self.intensity * self.capture * costs.ccs_storage_per_t
        return vom + fuel_cost + carbon_cost

CCGT with CCS.

Construct a CCGT (with CCS) generator.

Emissions capture rate is given in the range 0 to 1.

Ancestors

Inherited members

class CCS (polygon, capacity, intensity, capture, label=None)
Expand source code
class CCS(Fossil):
    """Base class of carbon capture and storage (CCS)."""

    def __init__(self, polygon, capacity, intensity, capture, label=None):
        """Construct a CCS generator.

        Emissions capture rate is given in the range 0 to 1.
        """
        Fossil.__init__(self, polygon, capacity, intensity, label)
        if not 0 <= capture <= 1:
            raise ValueError(capture)
        self.capture = capture

    def summary(self, context):
        """Return a summary of the generator activity."""
        generation = sum(self.series_power.values()) * ureg.MWh
        emissions = generation * self.intensity * (ureg.t / ureg.MWh)
        captured = emissions * self.capture
        return Fossil.summary(self, context) + \
            f', {captured.to("Mt")} captured'

Base class of carbon capture and storage (CCS).

Construct a CCS generator.

Emissions capture rate is given in the range 0 to 1.

Ancestors

Subclasses

Inherited members

class CST (polygon, capacity, solarmult, shours, filename, column, label=None, build_limit=None)
Expand source code
class CST(CSVTraceGenerator):
    """Concentrating solar thermal (CST) model."""

    patch = Patch(facecolor='orange')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, solarmult, shours, filename,
                 column, label=None, build_limit=None):
        """Construct a CST generator.

        Arguments include capacity (in MW), sm (solar multiple) and
        shours (hours of storage).
        """
        CSVTraceGenerator.__init__(self, polygon, capacity, filename, column,
                                   label)
        self.maxstorage = None
        self.stored = None
        self.set_storage(shours)
        self.set_multiple(solarmult)

    def set_capacity(self, cap):
        """Change the capacity of the generator to cap GW."""
        Generator.set_capacity(self, cap)
        self.maxstorage = self.capacity * self.shours

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

    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, hour, demand):
        """Step method for CST generators."""
        generation = self.generation[hour] * self.capacity * self.solarmult
        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
            if self.stored < 0:
                raise AssertionError
        if self.stored > self.maxstorage:
            raise AssertionError
        if self.stored < 0:
            raise AssertionError
        self.series_power[hour] = generation
        self.series_spilled[hour] = 0

        # This can happen due to rounding errors.
        generation = min(generation, demand)
        return generation, 0

    def reset(self):
        """Reset the generator."""
        Generator.reset(self)
        self.stored = 0.5 * self.maxstorage

    def summary(self, context):
        """Return a summary of the generator activity."""
        return Generator.summary(self, context) + \
            f', solar mult {self.solarmult:.2f}' + \
            f', {self.shours}h storage'

Concentrating solar thermal (CST) model.

Construct a CST generator.

Arguments include capacity (in MW), sm (solar multiple) and shours (hours of storage).

Ancestors

Subclasses

Class variables

var patch

Colour for plotting

Methods

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

Change the solar multiple of a CST plant.

def set_storage(self, shours)
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

Change the storage capacity of a CST plant.

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Step method for CST generators."""
    generation = self.generation[hour] * self.capacity * self.solarmult
    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
        if self.stored < 0:
            raise AssertionError
    if self.stored > self.maxstorage:
        raise AssertionError
    if self.stored < 0:
        raise AssertionError
    self.series_power[hour] = generation
    self.series_spilled[hour] = 0

    # This can happen due to rounding errors.
    generation = min(generation, demand)
    return generation, 0

Step method for CST generators.

Inherited members

class CSVTraceGenerator (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class CSVTraceGenerator(TraceGenerator):
    """A generator that gets its hourly dispatch from a CSV trace file."""

    csvfilename = None
    csvdata = None

    def __init__(self, polygon, capacity, filename, column, label=None,
                 build_limit=None):
        """Construct a generator with a specified trace file."""
        TraceGenerator.__init__(self, polygon, capacity, label, build_limit)
        cls = self.__class__
        if cls.csvfilename != filename:
            # Optimisation:
            # Only if the filename changes do we invoke genfromtxt.
            if not filename.startswith('http'):
                # Local file path
                traceinput = filename
            else:
                try:
                    resp = requests.request('GET', filename, timeout=5)
                except requests.exceptions.Timeout as exc:
                    msg = f'timeout fetching {filename}'
                    raise TimeoutError(msg) from exc
                if not resp.ok:
                    msg = f'HTTP {resp.status_code}: {filename}'
                    raise ConnectionError(msg)
                traceinput = resp.text.splitlines()
            cls.csvdata = np.genfromtxt(traceinput, encoding='UTF-8',
                                        delimiter=',')
            cls.csvdata = np.maximum(0, cls.csvdata)
            # check no elements are NaNs
            msg = f'Trace file {filename} contains NaNs; inspect file'
            if np.any(np.isnan(cls.csvdata)):
                raise AssertionError(msg)
            cls.csvfilename = filename
        # pylint limitation: https://github.com/pylint-dev/pylint/issues/9250
        # pylint: disable=unsubscriptable-object
        self.generation = cls.csvdata[::, column]

A generator that gets its hourly dispatch from a CSV trace file.

Construct a generator with a specified trace file.

Ancestors

Subclasses

Class variables

var csvdata
var csvfilename

Inherited members

class CentralReceiver (polygon, capacity, solarmult, shours, filename, column, label=None, build_limit=None)
Expand source code
class CentralReceiver(CST):
    """Central receiver CST generator.

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

Central receiver CST generator.

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

Construct a CST generator.

Arguments include capacity (in MW), sm (solar multiple) and shours (hours of storage).

Ancestors

Inherited members

class Coal_CCS (polygon, capacity, intensity=0.8, capture=0.85, label=None)
Expand source code
class Coal_CCS(CCS):
    """Coal with CCS."""

    def __init__(self, polygon, capacity, intensity=0.8, capture=0.85,
                 label=None):
        """Construct a coal CCS generator.

        Emissions capture rate is given in the range 0 to 1.
        """
        CCS.__init__(self, polygon, capacity, intensity, capture, label)

    def opcost_per_mwh(self, costs):
        """Return the variable O&M 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
        carbon_cost = emissions_rate * costs.carbon + \
            self.intensity * self.capture * costs.ccs_storage_per_t
        return vom + fuel_cost + carbon_cost

Coal with CCS.

Construct a coal CCS generator.

Emissions capture rate is given in the range 0 to 1.

Ancestors

Inherited members

class DemandResponse (polygon, capacity, cost_per_mwh, label=None)
Expand source code
class DemandResponse(Generator):
    """Load shedding generator.

    >>> dr = DemandResponse(polygons.WILDCARD, 500, 1500)
    """

    patch = Patch(facecolor='white')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, cost_per_mwh, label=None):
        """Construct a demand response 'generator'.

        The demand response opportunity cost is given by
        cost_per_mwh. There is assumed to be no capital cost.
        """
        Generator.__init__(self, polygon, capacity, label)
        self.setters = []
        self.runhours = 0
        self.maxresponse = 0
        self.cost_per_mwh = cost_per_mwh

    def step(self, hour, demand):
        """Specialised step method for demand response.

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

    def reset(self):
        """Reset the generator."""
        Generator.reset(self)
        self.runhours = 0
        self.maxresponse = 0

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

    def summary(self, context):
        """Return a summary of the generator activity."""
        return Generator.summary(self, context) + \
            f', max response {self.maxresponse} MW' + \
            f', ran {thousands(self.runhours)} hours'

Load shedding generator.

>>> dr = DemandResponse(polygons.WILDCARD, 500, 1500)

Construct a demand response 'generator'.

The demand response opportunity cost is given by cost_per_mwh. There is assumed to be no capital cost.

Ancestors

Class variables

var patch

Colour for plotting

Methods

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Specialised step method for demand response.

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

Specialised step method for demand response.

>>> dr = DemandResponse(polygons.WILDCARD, 500, 1500)
>>> dr.step(hour=0, demand=200)
(200, 0)
>>> dr.runhours
1

Inherited members

class Diesel (polygon, capacity, intensity=1.0, kwh_per_litre=3.3, label=None)
Expand source code
class Diesel(Fossil):
    """Diesel genset model."""

    patch = Patch(facecolor='#f35020')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, intensity=1.0, kwh_per_litre=3.3,
                 label=None):
        """Construct a diesel generator."""
        Fossil.__init__(self, polygon, capacity, intensity, label)
        self.kwh_per_litre = kwh_per_litre

    def opcost_per_mwh(self, costs):
        """Return the variable O&M 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
        carbon_cost = self.intensity * costs.carbon
        return vom + fuel_cost + carbon_cost

Diesel genset model.

Construct a diesel generator.

Ancestors

Inherited members

class Electrolyser (tank, polygon, capacity, efficiency=0.8, label=None)
Expand source code
class Electrolyser(Storage, Generator):
    """A hydrogen electrolyser."""

    patch = Patch(facecolor='teal')
    """Colour for plotting"""

    def __init__(self, tank, polygon, capacity, efficiency=0.8, label=None):
        """Construct a hydrogen electrolyser.

        Arguments include the associated storage vessel (the 'tank'),
        the capacity of the electrolyser (in MW) and electrolysis
        conversion efficiency.
        """
        if not isinstance(tank, storage.HydrogenStorage):
            raise TypeError
        Storage.__init__(self)
        Generator.__init__(self, polygon, capacity, label)
        self.efficiency = efficiency
        self.tank = tank
        self.setters += [(self.tank.set_storage, 0, 10000)]

    def soc(self):
        """Return the hydrogen tank state of charge (SOC)."""
        return self.tank.soc()

    def series(self):
        """Return the combined series."""
        dict1 = Generator.series(self)
        dict2 = Storage.series(self)
        dict1.update(dict2)
        return dict1

    def step(self, hour, demand):
        """Return 0 as this is not a generator."""
        return 0, 0

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

    def store(self, _, power):
        """Store power."""
        power = min(power, self.capacity)
        stored = self.tank.charge(power * self.efficiency)
        return stored / self.efficiency

A hydrogen electrolyser.

Construct a hydrogen electrolyser.

Arguments include the associated storage vessel (the 'tank'), the capacity of the electrolyser (in MW) and electrolysis conversion efficiency.

Ancestors

Class variables

var patch

Colour for plotting

Methods

def reset(self)
Expand source code
def reset(self):
    """Reset the generator."""
    Storage.reset(self)
    Generator.reset(self)

Reset the generator.

def series(self)
Expand source code
def series(self):
    """Return the combined series."""
    dict1 = Generator.series(self)
    dict2 = Storage.series(self)
    dict1.update(dict2)
    return dict1

Return the combined series.

def soc(self)
Expand source code
def soc(self):
    """Return the hydrogen tank state of charge (SOC)."""
    return self.tank.soc()

Return the hydrogen tank state of charge (SOC).

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Return 0 as this is not a generator."""
    return 0, 0

Return 0 as this is not a generator.

def store(self, _, power)
Expand source code
def store(self, _, power):
    """Store power."""
    power = min(power, self.capacity)
    stored = self.tank.charge(power * self.efficiency)
    return stored / self.efficiency

Store power.

Inherited members

class Fossil (polygon, capacity, intensity, label=None)
Expand source code
class Fossil(Fuelled):
    """Base class for GHG emitting power stations."""

    patch = Patch(facecolor='grey')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, intensity, label=None):
        """Construct a fossil fuelled generator.

        Greenhouse gas emissions intensity is given in tonnes per MWh.
        """
        Fuelled.__init__(self, polygon, capacity, label)
        self.intensity = intensity

    def summary(self, context):
        """Return a summary of the generator activity."""
        generation = sum(self.series_power.values()) * ureg.MWh
        emissions = generation * self.intensity * (ureg.t / ureg.MWh)
        return Fuelled.summary(self, context) + \
            f', {emissions.to("Mt")} CO2'

Base class for GHG emitting power stations.

Construct a fossil fuelled generator.

Greenhouse gas emissions intensity is given in tonnes per MWh.

Ancestors

Subclasses

Class variables

var patch

Colour for plotting

Inherited members

class Fuelled (polygon, capacity, label)
Expand source code
class Fuelled(Generator):
    """The class of generators that consume fuel."""

    def __init__(self, polygon, capacity, label):
        """Construct a fuelled generator."""
        Generator.__init__(self, polygon, capacity, label)
        self.runhours = 0

    def reset(self):
        """Reset the generator."""
        Generator.reset(self)
        self.runhours = 0

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

    def summary(self, context):
        """Return a summary of the generator activity."""
        return Generator.summary(self, context) + \
            f', ran {thousands(self.runhours)} hours'

The class of generators that consume fuel.

Construct a fuelled generator.

Ancestors

Subclasses

Methods

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

Step method for fuelled generators.

Inherited members

class Generator (polygon, capacity, label=None)
Expand source code
class Generator:
    """Base generator class."""

    # Economic lifetime of the generator in years (default 30)
    lifetime = 30

    # Is the generator a rotating machine?
    synchronous_p = True
    """Is this a synchronous generator?"""

    storage_p = False
    """A generator is not capable of storage by default."""

    def __init__(self, polygon, capacity, label=None):
        """Construct a base Generator.

        Arguments: installed polygon, installed capacity, descriptive label.
        """
        if capacity < 0:
            raise ValueError(capacity)
        self.setters = [(self.set_capacity, 0, 40)]
        self.label = self.__class__.__name__ if label is None else label
        self.capacity = capacity
        self.polygon = polygon

        # Sanity check polygon argument.
        if isinstance(polygon, polygons.regions.Region):
            raise TypeError
        if not 0 < polygon <= polygons.NUMPOLYGONS:
            raise AssertionError

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

    def series(self):
        """Return generation and spills series."""
        return {'power': pd.Series(self.series_power, dtype=float),
                'spilled': pd.Series(self.series_spilled, dtype=float)}

    def step(self, hour, demand):
        """Step the generator by one hour."""
        raise NotImplementedError

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

    def capcost(self, costs):
        """Return the 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."""
        self.series_power.clear()
        self.series_spilled.clear()

    def capfactor(self):
        """Capacity factor of this generator (in %)."""
        supplied = sum(self.series_power.values())
        hours = len(self.series_power)
        if self.capacity * hours == 0:
            return float('nan')
        return supplied / (self.capacity * hours) * 100

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

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

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

    def __str__(self):
        """Return a short string representation of the generator."""
        return f'{self.label} ({self.region()}:{self.polygon}), ' + \
            str(self.capacity * ureg.MW)

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

Base generator class.

Construct a base Generator.

Arguments: installed polygon, installed capacity, descriptive label.

Subclasses

Class variables

var lifetime
var storage_p

A generator is not capable of storage by default.

var synchronous_p

Is this a synchronous generator?

Methods

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

Return the capital cost.

def capfactor(self)
Expand source code
def capfactor(self):
    """Capacity factor of this generator (in %)."""
    supplied = sum(self.series_power.values())
    hours = len(self.series_power)
    if self.capacity * hours == 0:
        return float('nan')
    return supplied / (self.capacity * hours) * 100

Capacity factor of this generator (in %).

def fixed_om_costs(self, 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

Return the fixed O&M costs.

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

Calculate the LCOE in $/MWh.

def opcost(self, costs)
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)

Return the annual operating and maintenance cost.

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

Return the variable O&M costs.

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

Return the region the generator is in.

def reset(self)
Expand source code
def reset(self):
    """Reset the generator."""
    self.series_power.clear()
    self.series_spilled.clear()

Reset the generator.

def series(self)
Expand source code
def series(self):
    """Return generation and spills series."""
    return {'power': pd.Series(self.series_power, dtype=float),
            'spilled': pd.Series(self.series_spilled, dtype=float)}

Return generation and spills series.

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

Change the capacity of the generator to cap GW.

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Step the generator by one hour."""
    raise NotImplementedError

Step the generator by one hour.

def summary(self, context)
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
    string = f'supplied {supplied.to_compact()}'
    if self.capacity > 0 and self.capfactor() > 0:
        string += f', CF {self.capfactor():.1f}%'
    if sum(self.series_spilled.values()) > 0:
        spilled = sum(self.series_spilled.values()) * ureg.MWh
        string += f', surplus {spilled.to_compact()}'
    if self.capcost(costs) > 0:
        string += f', capcost {currency(self.capcost(costs))}'
    if self.opcost(costs) > 0:
        string += f', opcost {currency(self.opcost(costs))}'
    lcoe = self.lcoe(costs, context.years())
    if np.isfinite(lcoe) and lcoe > 0:
        string += f', LCOE {currency(int(lcoe))}'
    return string

Return a summary of the generator activity.

class Geothermal (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class Geothermal(CSVTraceGenerator):
    """Geothermal power plant."""

    patch = Patch(facecolor='indianred')
    """Colour for plotting"""

    def step(self, hour, demand):
        """Specialised step method for geothermal generators.

        Geothermal power plants do not spill.
        """
        generation = self.generation[hour] * self.capacity
        power = min(generation, demand)
        self.series_power[hour] = power
        self.series_spilled[hour] = 0
        return power, 0

Geothermal power plant.

Construct a generator with a specified trace file.

Ancestors

Subclasses

Class variables

var patch

Colour for plotting

Methods

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Specialised step method for geothermal generators.

    Geothermal power plants do not spill.
    """
    generation = self.generation[hour] * self.capacity
    power = min(generation, demand)
    self.series_power[hour] = power
    self.series_spilled[hour] = 0
    return power, 0

Specialised step method for geothermal generators.

Geothermal power plants do not spill.

Inherited members

class Geothermal_EGS (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class Geothermal_EGS(Geothermal):
    """Enhanced geothermal systems (EGS) geothermal model."""

Enhanced geothermal systems (EGS) geothermal model.

Construct a generator with a specified trace file.

Ancestors

Inherited members

class Geothermal_HSA (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class Geothermal_HSA(Geothermal):
    """Hot sedimentary aquifer (HSA) geothermal model."""

Hot sedimentary aquifer (HSA) geothermal model.

Construct a generator with a specified trace file.

Ancestors

Inherited members

class Hydro (polygon, capacity, label=None)
Expand source code
class Hydro(Fuelled):
    """Hydro power stations."""

    patch = Patch(facecolor='#4582b4')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, label=None):
        """Construct a hydroelectric generator."""
        Fuelled.__init__(self, polygon, capacity, label)
        # capacity is in MW, but build limit is in GW
        self.setters = [(self.set_capacity, 0, capacity / 1000.)]

Hydro power stations.

Construct a hydroelectric generator.

Ancestors

Subclasses

Class variables

var patch

Colour for plotting

Inherited members

class HydrogenGT (tank, polygon, capacity, efficiency=0.36, label=None)
Expand source code
class HydrogenGT(Fuelled):
    """A combustion turbine fuelled by hydrogen."""

    patch = Patch(facecolor='violet')
    """Colour for plotting"""

    def __init__(self, tank, polygon, capacity, efficiency=0.36, label=None):
        """Construct a HydrogenGT object.

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

    def step(self, hour, demand):
        """Step method for hydrogen comubstion turbine generators."""
        # calculate hydrogen requirement
        hydrogen = min(self.capacity, demand) / self.efficiency
        # discharge that amount of hydrogen
        power = self.tank.discharge(hydrogen) * self.efficiency
        self.series_power[hour] = power
        self.series_spilled[hour] = 0
        if power > 0:
            self.runhours += 1
        return power, 0

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

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

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

A combustion turbine fuelled by hydrogen.

Construct a HydrogenGT object.

>>> h = storage.HydrogenStorage(1000, 'test')
>>> gt = HydrogenGT(h, 1, 100, efficiency=0.5)
>>> gt
HydrogenGT (QLD1:1), 100.00 MW
>>> gt.step(0, 100) # discharge 100 MWh-e of hydrogen
(100.0, 0)
>>> gt.step(0, 100) # discharge another 100 MWh-e of hydrogen
(100.0, 0)
>>> h.storage == (1000 / 2.) - (200 / gt.efficiency)
True

Ancestors

Class variables

var patch

Colour for plotting

Methods

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

Return the capital cost (of an OCGT).

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

Return the fixed O&M costs (of an OCGT).

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

Return the variable O&M costs (of an OCGT).

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Step method for hydrogen comubstion turbine generators."""
    # calculate hydrogen requirement
    hydrogen = min(self.capacity, demand) / self.efficiency
    # discharge that amount of hydrogen
    power = self.tank.discharge(hydrogen) * self.efficiency
    self.series_power[hour] = power
    self.series_spilled[hour] = 0
    if power > 0:
        self.runhours += 1
    return power, 0

Step method for hydrogen comubstion turbine generators.

Inherited members

class Nuclear (polygon, capacity, label=None)
Expand source code
class Nuclear(Fuelled):
    """Nuclear power stations (large-scale)."""

    patch = Patch(facecolor='pink')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, label=None):
        """Construct a nuclear generator."""
        Fuelled.__init__(self, polygon, capacity, label)

Nuclear power stations (large-scale).

Construct a nuclear generator.

Ancestors

Class variables

var patch

Colour for plotting

Inherited members

class OCGT (polygon, capacity, intensity=0.7, label=None)
Expand source code
class OCGT(Fossil):
    """Open cycle gas turbine (OCGT) model."""

    patch = Patch(facecolor='#ffcd96')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, intensity=0.7, label=None):
        """Construct an OCGT generator."""
        Fossil.__init__(self, polygon, capacity, intensity, label)

    def opcost_per_mwh(self, costs):
        """Return the variable O&M costs."""
        vom = costs.opcost_per_mwh[type(self)]
        fuel_cost = costs.gas_price_per_gj * 11.61
        carbon_cost = self.intensity * costs.carbon
        return vom + fuel_cost + carbon_cost

Open cycle gas turbine (OCGT) model.

Construct an OCGT generator.

Ancestors

Inherited members

class PV (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class PV(CSVTraceGenerator):
    """Solar photovoltaic (PV) model."""

    synchronous_p = False
    """Is this a synchronous generator?"""

Solar photovoltaic (PV) model.

Construct a generator with a specified trace file.

Ancestors

Subclasses

Inherited members

class PV1Axis (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class PV1Axis(PV):
    """Single-axis tracking PV."""

    patch = Patch(facecolor='#fed500')
    """Colour for plotting"""

Single-axis tracking PV.

Construct a generator with a specified trace file.

Ancestors

Class variables

var patch

Colour for plotting

Inherited members

class ParabolicTrough (polygon, capacity, solarmult, shours, filename, column, label=None, build_limit=None)
Expand source code
class ParabolicTrough(CST):
    """Parabolic trough CST generator.

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

Parabolic trough CST generator.

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

Construct a CST generator.

Arguments include capacity (in MW), sm (solar multiple) and shours (hours of storage).

Ancestors

Inherited members

class PumpedHydroPump (polygon, capacity, reservoirs, rte=0.8, label=None)
Expand source code
class PumpedHydroPump(Storage, Generator):
    """Pumped hydro (pump side) model."""

    patch = Patch(facecolor='darkblue')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, reservoirs, rte=0.8, label=None):
        """Construct a pumped hydro storage generator."""
        if not isinstance(reservoirs, storage.PumpedHydroStorage):
            raise TypeError
        Storage.__init__(self)
        Generator.__init__(self, polygon, capacity, label)
        # capacity is in MW, but build limit is in GW
        self.setters = [(self.set_capacity, 0, capacity / 1000.)]
        self.reservoirs = reservoirs
        self.rte = rte

    def step(self, hour, demand):
        """Return 0 as this is not a generator."""
        return 0, 0

    def series(self):
        """Return the combined series."""
        dict1 = Hydro.series(self)
        dict2 = Storage.series(self)
        dict1.update(dict2)
        return dict1

    def soc(self):
        """Return the pumped hydro SOC (state of charge)."""
        return self.reservoirs.soc()

    def store(self, hour, power):
        """Pump water uphill for one hour."""
        if self.reservoirs.last_gen == hour:
            # Can't pump and generate in the same hour.
            return 0
        power = min(self.charge_capacity(self, hour), power,
                    self.capacity)

        stored = self.reservoirs.charge(power * self.rte)
        if stored < power * self.rte:
            power = (self.reservoirs.maxstorage - self.reservoirs.storage) \
                / self.rte

        if power > 0:
            self.record(hour, power)
            self.reservoirs.last_pump = hour
        return power

    def reset(self):
        """Reset the generator."""
        Generator.reset(self)
        Storage.reset(self)
        self.reservoirs.reset()

    def summary(self, context):
        """Return a summary of the generator activity."""
        stg = (self.reservoirs.maxstorage * ureg.MWh).to_compact()
        return Generator.summary(self, context) + \
            f', charged {thousands(len(self.series_charge))} hours' + \
            f', {stg} storage'

Pumped hydro (pump side) model.

Construct a pumped hydro storage generator.

Ancestors

Class variables

var patch

Colour for plotting

Methods

def reset(self)
Expand source code
def reset(self):
    """Reset the generator."""
    Generator.reset(self)
    Storage.reset(self)
    self.reservoirs.reset()

Reset the generator.

def series(self)
Expand source code
def series(self):
    """Return the combined series."""
    dict1 = Hydro.series(self)
    dict2 = Storage.series(self)
    dict1.update(dict2)
    return dict1

Return the combined series.

def soc(self)
Expand source code
def soc(self):
    """Return the pumped hydro SOC (state of charge)."""
    return self.reservoirs.soc()

Return the pumped hydro SOC (state of charge).

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Return 0 as this is not a generator."""
    return 0, 0

Return 0 as this is not a generator.

def store(self, hour, power)
Expand source code
def store(self, hour, power):
    """Pump water uphill for one hour."""
    if self.reservoirs.last_gen == hour:
        # Can't pump and generate in the same hour.
        return 0
    power = min(self.charge_capacity(self, hour), power,
                self.capacity)

    stored = self.reservoirs.charge(power * self.rte)
    if stored < power * self.rte:
        power = (self.reservoirs.maxstorage - self.reservoirs.storage) \
            / self.rte

    if power > 0:
        self.record(hour, power)
        self.reservoirs.last_pump = hour
    return power

Pump water uphill for one hour.

Inherited members

class PumpedHydroTurbine (polygon, capacity, reservoirs, label=None)
Expand source code
class PumpedHydroTurbine(Hydro):
    """Pumped storage hydro (generator side) model."""

    patch = Patch(facecolor='powderblue')
    """Colour for plotting"""

    def __init__(self, polygon, capacity, reservoirs, label=None):
        """Construct a pumped hydro storage generator."""
        if not isinstance(reservoirs, storage.PumpedHydroStorage):
            raise TypeError
        Hydro.__init__(self, polygon, capacity, label)
        self.reservoirs = reservoirs

    def step(self, hour, demand):
        """Step method for pumped hydro storage."""
        power = min(self.reservoirs.storage, self.capacity, demand)
        if self.reservoirs.last_pump == hour:
            # Can't pump and generate in the same hour.
            self.series_power[hour] = 0
            self.series_spilled[hour] = 0
            return 0, 0

        self.reservoirs.discharge(power)
        self.series_power[hour] = power
        self.series_spilled[hour] = 0
        if power > 0:
            self.runhours += 1
            self.reservoirs.last_gen = hour
        return power, 0

Pumped storage hydro (generator side) model.

Construct a pumped hydro storage generator.

Ancestors

Methods

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Step method for pumped hydro storage."""
    power = min(self.reservoirs.storage, self.capacity, demand)
    if self.reservoirs.last_pump == hour:
        # Can't pump and generate in the same hour.
        self.series_power[hour] = 0
        self.series_spilled[hour] = 0
        return 0, 0

    self.reservoirs.discharge(power)
    self.series_power[hour] = power
    self.series_spilled[hour] = 0
    if power > 0:
        self.runhours += 1
        self.reservoirs.last_gen = hour
    return power, 0

Step method for pumped hydro storage.

Inherited members

class Storage
Expand source code
class Storage:
    """A class to give a generator storage capability."""

    storage_p = True
    """This generator is capable of storage."""

    def __init__(self):
        """Storage constructor."""
        # Time series of charges
        self.series_charge = {}
        self.series_soc = {}

    def soc(self):
        """Return the storage SOC (state of charge)."""
        raise NotImplementedError

    def record(self, hour, energy):
        """Record storage."""
        if hour not in self.series_charge:
            self.series_charge[hour] = 0
        self.series_charge[hour] += energy
        self.series_soc[hour] = self.soc()

    def charge_capacity(self, gen, hour):
        """Return available storage capacity.

        Since a storage-capable generator can be called on multiple
        times to store energy in a single timestep, we keep track of
        how much remaining capacity is available for charging in the
        given timestep.
        """
        try:
            result = gen.capacity - self.series_charge[hour]
        except KeyError:
            return gen.capacity
        if result < 0 and isclose(result, 0, abs_tol=1e-6):
            result = 0  # pragma: no cover
        if result < 0:
            raise AssertionError
        return result

    def series(self):
        """Return generation and spills series."""
        return {'charge': pd.Series(self.series_charge, dtype=float),
                'soc': pd.Series(self.series_soc, dtype=float)}

    def store(self, hour, power):
        """Abstract method to ensure that derived classes define this."""
        raise NotImplementedError

    def reset(self):
        """Reset a generator with storage."""
        self.series_charge.clear()
        self.series_soc.clear()

A class to give a generator storage capability.

Storage constructor.

Subclasses

Class variables

var storage_p

This generator is capable of storage.

Methods

def charge_capacity(self, gen, hour)
Expand source code
def charge_capacity(self, gen, hour):
    """Return available storage capacity.

    Since a storage-capable generator can be called on multiple
    times to store energy in a single timestep, we keep track of
    how much remaining capacity is available for charging in the
    given timestep.
    """
    try:
        result = gen.capacity - self.series_charge[hour]
    except KeyError:
        return gen.capacity
    if result < 0 and isclose(result, 0, abs_tol=1e-6):
        result = 0  # pragma: no cover
    if result < 0:
        raise AssertionError
    return result

Return available storage capacity.

Since a storage-capable generator can be called on multiple times to store energy in a single timestep, we keep track of how much remaining capacity is available for charging in the given timestep.

def record(self, hour, energy)
Expand source code
def record(self, hour, energy):
    """Record storage."""
    if hour not in self.series_charge:
        self.series_charge[hour] = 0
    self.series_charge[hour] += energy
    self.series_soc[hour] = self.soc()

Record storage.

def reset(self)
Expand source code
def reset(self):
    """Reset a generator with storage."""
    self.series_charge.clear()
    self.series_soc.clear()

Reset a generator with storage.

def series(self)
Expand source code
def series(self):
    """Return generation and spills series."""
    return {'charge': pd.Series(self.series_charge, dtype=float),
            'soc': pd.Series(self.series_soc, dtype=float)}

Return generation and spills series.

def soc(self)
Expand source code
def soc(self):
    """Return the storage SOC (state of charge)."""
    raise NotImplementedError

Return the storage SOC (state of charge).

def store(self, hour, power)
Expand source code
def store(self, hour, power):
    """Abstract method to ensure that derived classes define this."""
    raise NotImplementedError

Abstract method to ensure that derived classes define this.

class TraceGenerator (polygon, capacity, label=None, build_limit=None)
Expand source code
class TraceGenerator(Generator):
    """A generator that gets its hourly dispatch from a trace."""

    def __init__(self, polygon, capacity, label=None, build_limit=None):
        """Construct a generator with a specified trace file."""
        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))]

    def step(self, hour, demand):
        """Step method for any generator using traces."""
        # self.generation must be defined by derived classes
        # pylint: disable=no-member
        generation = self.generation[hour] * self.capacity
        power = min(generation, demand)
        spilled = generation - power
        self.series_power[hour] = power
        self.series_spilled[hour] = spilled
        return power, spilled

A generator that gets its hourly dispatch from a trace.

Construct a generator with a specified trace file.

Ancestors

Subclasses

Methods

def step(self, hour, demand)
Expand source code
def step(self, hour, demand):
    """Step method for any generator using traces."""
    # self.generation must be defined by derived classes
    # pylint: disable=no-member
    generation = self.generation[hour] * self.capacity
    power = min(generation, demand)
    spilled = generation - power
    self.series_power[hour] = power
    self.series_spilled[hour] = spilled
    return power, spilled

Step method for any generator using traces.

Inherited members

class Wind (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class Wind(CSVTraceGenerator):
    """Wind power."""

    patch = Patch(facecolor='#417505')
    """Patch for plotting"""
    synchronous_p = False
    """Is this a synchronous generator?"""

Wind power.

Construct a generator with a specified trace file.

Ancestors

Subclasses

Class variables

var patch

Patch for plotting

Inherited members

class WindOffshore (polygon, capacity, filename, column, label=None, build_limit=None)
Expand source code
class WindOffshore(Wind):
    """Offshore wind power."""

    patch = Patch(facecolor='darkgreen')
    """Colour for plotting"""

Offshore wind power.

Construct a generator with a specified trace file.

Ancestors

Inherited members