Note

You can download this example as a Jupyter notebook or start it in interactive mode.

Simple electricity market examples#

This example gradually builds up more and more complicated energy-only electricity markets in PyPSA, starting from a single bidding zone, going up to multiple bidding zones connected with transmission (NTCs) along with variable renewables and storage.

Preliminaries#

Here libraries are imported and data is defined.

[1]:
import numpy as np

import pypsa
[2]:
# marginal costs in EUR/MWh
marginal_costs = {"Wind": 0, "Hydro": 0, "Coal": 30, "Gas": 60, "Oil": 80}

# power plant capacities (nominal powers in MW) in each country (not necessarily realistic)
power_plant_p_nom = {
    "South Africa": {"Coal": 35000, "Wind": 3000, "Gas": 8000, "Oil": 2000},
    "Mozambique": {
        "Hydro": 1200,
    },
    "Swaziland": {
        "Hydro": 600,
    },
}

# transmission capacities in MW (not necessarily realistic)
transmission = {
    "South Africa": {"Mozambique": 500, "Swaziland": 250},
    "Mozambique": {"Swaziland": 100},
}

# country electrical loads in MW (not necessarily realistic)
loads = {"South Africa": 42000, "Mozambique": 650, "Swaziland": 250}

Single bidding zone with fixed load, one period#

In this example we consider a single market bidding zone, South Africa.

The inelastic load has essentially infinite marginal utility (or higher than the marginal cost of any generator).

[3]:
country = "South Africa"

network = pypsa.Network()

network.add("Bus", country)

for tech in power_plant_p_nom[country]:
    network.add(
        "Generator",
        f"{country} {tech}",
        bus=country,
        p_nom=power_plant_p_nom[country][tech],
        marginal_cost=marginal_costs[tech],
    )


network.add("Load", f"{country} load", bus=country, p_set=loads[country])
[3]:
Index(['South Africa load'], dtype='object')
[4]:
# Run optimisation to determine market dispatch
network.optimize()
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/v0.31.0/lib/python3.12/site-packages/linopy/common.py:147: UserWarning: coords for dimension(s) ['Generator'] is not aligned with the pandas object. Previously, the indexes of the pandas were ignored and overwritten in these cases. Now, the pandas object's coordinates are taken considered for alignment.
  warn(
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.03s
INFO:linopy.solvers:Log file at /tmp/highs.log
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 4 primals, 13 duals
Objective: 1.29e+06
Solver model: available
Solver message: optimal

INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper were not assigned to the network.
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
  Matrix [1e+00, 1e+00]
  Cost   [3e+01, 8e+01]
  Bound  [0e+00, 0e+00]
  RHS    [2e+03, 4e+04]
Presolving model
1 rows, 3 cols, 3 nonzeros  0s
0 rows, 0 cols, 0 nonzeros  0s
Presolve : Reductions: rows 0(-13); columns 0(-4); elements 0(-16) - Reduced to empty
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Objective value     :  1.2900000000e+06
HiGHS run time      :          0.00
Writing the solution to /tmp/linopy-solve-kg4vo8zb.sol
[4]:
('ok', 'optimal')
[5]:
# print the load active power (P) consumption
network.loads_t.p
[5]:
Load South Africa load
snapshot
now 42000.0
[6]:
# print the generator active power (P) dispatch
network.generators_t.p
[6]:
Generator South Africa Coal South Africa Wind South Africa Gas South Africa Oil
snapshot
now 35000.0 3000.0 4000.0 -0.0
[7]:
# print the clearing price (corresponding to gas)
network.buses_t.marginal_price
[7]:
Bus South Africa
snapshot
now 60.0

Two bidding zones connected by transmission, one period#

In this example we have bidirectional transmission capacity between two bidding zones. The power transfer is treated as controllable (like an A/NTC (Available/Net Transfer Capacity) or HVDC line). Note that in the physical grid, power flows passively according to the network impedances.

[8]:
network = pypsa.Network()

countries = ["Mozambique", "South Africa"]

for country in countries:
    network.add("Bus", country)

    for tech in power_plant_p_nom[country]:
        network.add(
            "Generator",
            f"{country} {tech}",
            bus=country,
            p_nom=power_plant_p_nom[country][tech],
            marginal_cost=marginal_costs[tech],
        )

    network.add("Load", f"{country} load", bus=country, p_set=loads[country])

    # add transmission as controllable Link
    if country not in transmission:
        continue

    for other_country in countries:
        if other_country not in transmission[country]:
            continue

        # NB: Link is by default unidirectional, so have to set p_min_pu = -1
        # to allow bidirectional (i.e. also negative) flow
        network.add(
            "Link",
            f"{country} - {other_country} link",
            bus0=country,
            bus1=other_country,
            p_nom=transmission[country][other_country],
            p_min_pu=-1,
        )
[9]:
network.optimize()
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['Mozambique', 'South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following links have carriers which are not defined:
Index(['South Africa - Mozambique link'], dtype='object', name='Link')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['Mozambique', 'South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following links have carriers which are not defined:
Index(['South Africa - Mozambique link'], dtype='object', name='Link')
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/v0.31.0/lib/python3.12/site-packages/linopy/common.py:147: UserWarning: coords for dimension(s) ['Generator'] is not aligned with the pandas object. Previously, the indexes of the pandas were ignored and overwritten in these cases. Now, the pandas object's coordinates are taken considered for alignment.
  warn(
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.04s
INFO:linopy.solvers:Log file at /tmp/highs.log
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 6 primals, 19 duals
Objective: 1.26e+06
Solver model: available
Solver message: optimal

INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper, Link-fix-p-lower, Link-fix-p-upper were not assigned to the network.
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
  Matrix [1e+00, 1e+00]
  Cost   [3e+01, 8e+01]
  Bound  [0e+00, 0e+00]
  RHS    [5e+02, 4e+04]
Presolving model
1 rows, 3 cols, 3 nonzeros  0s
0 rows, 0 cols, 0 nonzeros  0s
Presolve : Reductions: rows 0(-19); columns 0(-6); elements 0(-24) - Reduced to empty
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Objective value     :  1.2600000000e+06
HiGHS run time      :          0.00
Writing the solution to /tmp/linopy-solve-vq6_yonl.sol
[9]:
('ok', 'optimal')
[10]:
network.loads_t.p
[10]:
Load Mozambique load South Africa load
snapshot
now 650.0 42000.0
[11]:
network.generators_t.p
[11]:
Generator Mozambique Hydro South Africa Coal South Africa Wind South Africa Gas South Africa Oil
snapshot
now 1150.0 35000.0 3000.0 3500.0 -0.0
[12]:
network.links_t.p0
[12]:
Link South Africa - Mozambique link
snapshot
now -500.0
[13]:
# print the clearing price (corresponding to water in Mozambique and gas in SA)
network.buses_t.marginal_price
[13]:
Bus Mozambique South Africa
snapshot
now -0.0 60.0
[14]:
# link shadow prices
network.links_t.mu_lower
[14]:
Link
snapshot
now

Three bidding zones connected by transmission, one period#

In this example we have bidirectional transmission capacity between three bidding zones. The power transfer is treated as controllable (like an A/NTC (Available/Net Transfer Capacity) or HVDC line). Note that in the physical grid, power flows passively according to the network impedances.

[15]:
network = pypsa.Network()

countries = ["Swaziland", "Mozambique", "South Africa"]

for country in countries:
    network.add("Bus", country)

    for tech in power_plant_p_nom[country]:
        network.add(
            "Generator",
            f"{country} {tech}",
            bus=country,
            p_nom=power_plant_p_nom[country][tech],
            marginal_cost=marginal_costs[tech],
        )

    network.add("Load", f"{country} load", bus=country, p_set=loads[country])

    # add transmission as controllable Link
    if country not in transmission:
        continue

    for other_country in countries:
        if other_country not in transmission[country]:
            continue

        # NB: Link is by default unidirectional, so have to set p_min_pu = -1
        # to allow bidirectional (i.e. also negative) flow
        network.add(
            "Link",
            f"{country} - {other_country} link",
            bus0=country,
            bus1=other_country,
            p_nom=transmission[country][other_country],
            p_min_pu=-1,
        )
[16]:
network.optimize()
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['Swaziland', 'Mozambique', 'South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following links have carriers which are not defined:
Index(['Mozambique - Swaziland link', 'South Africa - Swaziland link',
       'South Africa - Mozambique link'],
      dtype='object', name='Link')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['Swaziland', 'Mozambique', 'South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following links have carriers which are not defined:
Index(['Mozambique - Swaziland link', 'South Africa - Swaziland link',
       'South Africa - Mozambique link'],
      dtype='object', name='Link')
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/v0.31.0/lib/python3.12/site-packages/linopy/common.py:147: UserWarning: coords for dimension(s) ['Generator'] is not aligned with the pandas object. Previously, the indexes of the pandas were ignored and overwritten in these cases. Now, the pandas object's coordinates are taken considered for alignment.
  warn(
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.03s
INFO:linopy.solvers:Log file at /tmp/highs.log
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 9 primals, 27 duals
Objective: 1.24e+06
Solver model: available
Solver message: optimal

INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper, Link-fix-p-lower, Link-fix-p-upper were not assigned to the network.
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
  Matrix [1e+00, 1e+00]
  Cost   [3e+01, 8e+01]
  Bound  [0e+00, 0e+00]
  RHS    [1e+02, 4e+04]
Presolving model
2 rows, 5 cols, 6 nonzeros  0s
0 rows, 0 cols, 0 nonzeros  0s
Presolve : Reductions: rows 0(-27); columns 0(-9); elements 0(-36) - Reduced to empty
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Objective value     :  1.2450000000e+06
HiGHS run time      :          0.00
Writing the solution to /tmp/linopy-solve-t2zzaq1s.sol
[16]:
('ok', 'optimal')
[17]:
network.loads_t.p
[17]:
Load Swaziland load Mozambique load South Africa load
snapshot
now 250.0 650.0 42000.0
[18]:
network.generators_t.p
[18]:
Generator Swaziland Hydro Mozambique Hydro South Africa Coal South Africa Wind South Africa Gas South Africa Oil
snapshot
now 600.0 1050.0 35000.0 3000.0 3250.0 -0.0
[19]:
network.links_t.p0
[19]:
Link Mozambique - Swaziland link South Africa - Swaziland link South Africa - Mozambique link
snapshot
now -100.0 -250.0 -500.0
[20]:
# print the clearing price (corresponding to hydro in S and M, and gas in SA)
network.buses_t.marginal_price
[20]:
Bus Swaziland Mozambique South Africa
snapshot
now -0.0 -0.0 60.0
[21]:
# link shadow prices
network.links_t.mu_lower
[21]:
Link
snapshot
now

Single bidding zone with price-sensitive industrial load, one period#

In this example we consider a single market bidding zone, South Africa.

Now there is a large industrial load with a marginal utility which is low enough to interact with the generation marginal cost.

[22]:
country = "South Africa"

network = pypsa.Network()

network.add("Bus", country)

for tech in power_plant_p_nom[country]:
    network.add(
        "Generator",
        f"{country} {tech}",
        bus=country,
        p_nom=power_plant_p_nom[country][tech],
        marginal_cost=marginal_costs[tech],
    )

# standard high marginal utility consumers
network.add("Load", f"{country} load", bus=country, p_set=loads[country])

# add an industrial load as a dummy negative-dispatch generator with marginal utility of 70 EUR/MWh for 8000 MW
network.add(
    "Generator",
    f"{country} industrial load",
    bus=country,
    p_max_pu=0,
    p_min_pu=-1,
    p_nom=8000,
    marginal_cost=70,
)
[22]:
Index(['South Africa industrial load'], dtype='object')
[23]:
network.optimize()
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/v0.31.0/lib/python3.12/site-packages/linopy/common.py:147: UserWarning: coords for dimension(s) ['Generator'] is not aligned with the pandas object. Previously, the indexes of the pandas were ignored and overwritten in these cases. Now, the pandas object's coordinates are taken considered for alignment.
  warn(
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.03s
INFO:linopy.solvers:Log file at /tmp/highs.log
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 5 primals, 16 duals
Objective: 1.25e+06
Solver model: available
Solver message: optimal

INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper were not assigned to the network.
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
  Matrix [1e+00, 1e+00]
  Cost   [3e+01, 8e+01]
  Bound  [0e+00, 0e+00]
  RHS    [2e+03, 4e+04]
Presolving model
1 rows, 4 cols, 4 nonzeros  0s
0 rows, 0 cols, 0 nonzeros  0s
Presolve : Reductions: rows 0(-16); columns 0(-5); elements 0(-20) - Reduced to empty
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Objective value     :  1.2500000000e+06
HiGHS run time      :          0.00
Writing the solution to /tmp/linopy-solve-4mhj91jr.sol
[23]:
('ok', 'optimal')
[24]:
network.loads_t.p
[24]:
Load South Africa load
snapshot
now 42000.0
[25]:
# NB only half of industrial load is served, because this maxes out
# Gas. Oil is too expensive with a marginal cost of 80 EUR/MWh
network.generators_t.p
[25]:
Generator South Africa Coal South Africa Wind South Africa Gas South Africa Oil South Africa industrial load
snapshot
now 35000.0 3000.0 8000.0 -0.0 -4000.0
[26]:
network.buses_t.marginal_price
[26]:
Bus South Africa
snapshot
now 70.0

Single bidding zone with fixed load, several periods#

In this example we consider a single market bidding zone, South Africa.

We consider multiple time periods (labelled [0,1,2,3]) to represent variable wind generation.

[27]:
country = "South Africa"

network = pypsa.Network()

# snapshots labelled by [0,1,2,3]
network.set_snapshots(range(4))

network.add("Bus", country)

# p_max_pu is variable for wind
for tech in power_plant_p_nom[country]:
    network.add(
        "Generator",
        f"{country} {tech}",
        bus=country,
        p_nom=power_plant_p_nom[country][tech],
        marginal_cost=marginal_costs[tech],
        p_max_pu=([0.3, 0.6, 0.4, 0.5] if tech == "Wind" else 1),
    )

# load which varies over the snapshots
network.add(
    "Load",
    f"{country} load",
    bus=country,
    p_set=loads[country] + np.array([0, 1000, 3000, 4000]),
)
[27]:
Index(['South Africa load'], dtype='object')
[28]:
network.optimize()
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/v0.31.0/lib/python3.12/site-packages/linopy/common.py:147: UserWarning: coords for dimension(s) ['Generator'] is not aligned with the pandas object. Previously, the indexes of the pandas were ignored and overwritten in these cases. Now, the pandas object's coordinates are taken considered for alignment.
  warn(
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.03s
INFO:linopy.solvers:Log file at /tmp/highs.log
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 16 primals, 40 duals
Objective: 6.08e+06
Solver model: available
Solver message: optimal

INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper were not assigned to the network.
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
  Matrix [1e+00, 1e+00]
  Cost   [3e+01, 8e+01]
  Bound  [0e+00, 0e+00]
  RHS    [9e+02, 5e+04]
Presolving model
4 rows, 12 cols, 12 nonzeros  0s
0 rows, 0 cols, 0 nonzeros  0s
Presolve : Reductions: rows 0(-40); columns 0(-16); elements 0(-64) - Reduced to empty
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Objective value     :  6.0820000000e+06
HiGHS run time      :          0.00
Writing the solution to /tmp/linopy-solve-q68edpi_.sol
[28]:
('ok', 'optimal')
[29]:
network.loads_t.p
[29]:
Load South Africa load
snapshot
0 42000.0
1 43000.0
2 45000.0
3 46000.0
[30]:
network.generators_t.p
[30]:
Generator South Africa Coal South Africa Wind South Africa Gas South Africa Oil
snapshot
0 35000.0 900.0 6100.0 -0.0
1 35000.0 1800.0 6200.0 -0.0
2 35000.0 1200.0 8000.0 800.0
3 35000.0 1500.0 8000.0 1500.0
[31]:
network.buses_t.marginal_price
[31]:
Bus South Africa
snapshot
0 60.0
1 60.0
2 80.0
3 80.0

Single bidding zone with fixed load and storage, several periods#

In this example we consider a single market bidding zone, South Africa.

We consider multiple time periods (labelled [0,1,2,3]) to represent variable wind generation. Storage is allowed to do price arbitrage to reduce oil consumption.

[32]:
country = "South Africa"

network = pypsa.Network()

# snapshots labelled by [0,1,2,3]
network.set_snapshots(range(4))

network.add("Bus", country)

# p_max_pu is variable for wind
for tech in power_plant_p_nom[country]:
    network.add(
        "Generator",
        f"{country} {tech}",
        bus=country,
        p_nom=power_plant_p_nom[country][tech],
        marginal_cost=marginal_costs[tech],
        p_max_pu=([0.3, 0.6, 0.4, 0.5] if tech == "Wind" else 1),
    )

# load which varies over the snapshots
network.add(
    "Load",
    f"{country} load",
    bus=country,
    p_set=loads[country] + np.array([0, 1000, 3000, 4000]),
)

# storage unit to do price arbitrage
network.add(
    "StorageUnit",
    f"{country} pumped hydro",
    bus=country,
    p_nom=1000,
    max_hours=6,  # energy storage in terms of hours at full power
)
[32]:
Index(['South Africa pumped hydro'], dtype='object')
[33]:
network.optimize()
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['South Africa'], dtype='object', name='Bus')
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/v0.31.0/lib/python3.12/site-packages/linopy/common.py:147: UserWarning: coords for dimension(s) ['Generator'] is not aligned with the pandas object. Previously, the indexes of the pandas were ignored and overwritten in these cases. Now, the pandas object's coordinates are taken considered for alignment.
  warn(
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.06s
INFO:linopy.solvers:Log file at /tmp/highs.log
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 28 primals, 68 duals
Objective: 6.05e+06
Solver model: available
Solver message: optimal

INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper, StorageUnit-fix-p_dispatch-lower, StorageUnit-fix-p_dispatch-upper, StorageUnit-fix-p_store-lower, StorageUnit-fix-p_store-upper, StorageUnit-fix-state_of_charge-lower, StorageUnit-fix-state_of_charge-upper, StorageUnit-energy_balance were not assigned to the network.
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
  Matrix [1e+00, 1e+00]
  Cost   [3e+01, 8e+01]
  Bound  [0e+00, 0e+00]
  RHS    [9e+02, 5e+04]
Presolving model
8 rows, 24 cols, 35 nonzeros  0s
3 rows, 7 cols, 9 nonzeros  0s
3 rows, 7 cols, 9 nonzeros  0s
Presolve : Reductions: rows 3(-65); columns 7(-21); elements 9(-102)
Solving the presolved LP
Using EKK dual simplex solver - serial
  Iteration        Objective     Infeasibilities num(sum)
          0     0.0000000000e+00 Ph1: 0(0) 0s
          4     6.0460000000e+06 Pr: 0(0) 0s
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Simplex   iterations: 4
Objective value     :  6.0460000000e+06
HiGHS run time      :          0.00
Writing the solution to /tmp/linopy-solve-d32691bd.sol
[33]:
('ok', 'optimal')
[34]:
network.loads_t.p
[34]:
Load South Africa load
snapshot
0 42000.0
1 43000.0
2 45000.0
3 46000.0
[35]:
network.generators_t.p
[35]:
Generator South Africa Coal South Africa Wind South Africa Gas South Africa Oil
snapshot
0 35000.0 900.0 6900.0 -0.0
1 35000.0 1800.0 7200.0 -0.0
2 35000.0 1200.0 8000.0 -0.0
3 35000.0 1500.0 8000.0 500.0
[36]:
network.storage_units_t.p
[36]:
StorageUnit South Africa pumped hydro
snapshot
0 -800.0
1 -1000.0
2 800.0
3 1000.0
[37]:
network.storage_units_t.state_of_charge
[37]:
StorageUnit South Africa pumped hydro
snapshot
0 800.0
1 1800.0
2 1000.0
3 -0.0
[38]:
network.buses_t.marginal_price
[38]:
Bus South Africa
snapshot
0 60.0
1 60.0
2 60.0
3 80.0