Source code for cdcam.interventions

"""Decide on interventions
"""
# pylint: disable=C0103
from cdcam.model import PostcodeSector

import copy
import math

################################################################
# EXAMPLE COST LOOKUP TABLE
# - TODO come back to net present value or total cost of ownership for costs
################################################################

# Postcode-sector level individual interventions
INTERVENTIONS = {
    'upgrade_to_lte': {
        'name': 'Upgrade site to LTE',
        'description': 'If a site has only 2G/3G',
        'result': '800 and 2600 bands available',
        'cost': 331487,
        'assets_to_build': [
            {
                # site_ngr to match upgraded
                'site_ngr': None,
                'frequency': '800',
                'technology': 'LTE',
                'type': 'macrocell_site',
                'bandwidth': '2x10MHz',
                # set build date when deciding
                'build_date': None,
            },
            {
                # site_ngr to match upgraded
                'site_ngr': None,
                'frequency': '2600',
                'technology': 'LTE',
                'type': 'macrocell_site',
                'bandwidth': '2x10MHz',
                # set build date when deciding
                'build_date': None,
            },
        ]
    },
    'carrier_700': {
        'name': 'Build 700 MHz carrier',
        'description': 'Available if a site has LTE',
        'result': '700 band available',
        'cost': 59210,
        'assets_to_build': [
            {
                # site_ngr to match upgraded
                'site_ngr': None,
                'frequency': ['700'],
                'technology': '5G',
                'type': 'macrocell_site',
                'bandwidth': '2x10MHz',
                # set build date when deciding
                'build_date': None,
            },
        ]
    },
    'carrier_3500': {
        'name': 'Build 3500 MHz carrier',
        'description': 'Available if a site has LTE',
        'result': '3500 band available',
        'cost': 59210,
        'assets_to_build': [
            {
                # site_ngr to match upgraded
                'site_ngr': None,
                'frequency': ['3500'],
                'technology': '5G',
                'type': 'macrocell_site',
                'bandwidth': '2x10MHz',
                # set build date when deciding
                'build_date': None,
            },
        ]
    },
    'carrier_26000': {
        'name': 'Build 26000 MHz carrier',
        'description': 'Available if a site has LTE',
        'result': '26000 band available',
        'cost': 59210,
        'assets_to_build': [
            {
                # site_ngr to match upgraded
                'site_ngr': None,
                'frequency': ['26000'],
                'technology': '5G',
                'type': 'macrocell_site',
                'bandwidth': '2x100MHz',
                # set build date when deciding
                'build_date': None,
            },
        ]
    },
    'small_cell': {
        'name': 'Build a small cell',
        'description': 'Must be deployed at preset densities to be modelled',
        'result': 'Small cells available at given density',
        'cost': 45882,
        'assets_to_build': [
            {
                # site_ngr not used
                'site_ngr': 'small_cell_site',
                'frequency': ['3700', '26000'],
                'technology': '5G',
                'type': 'small_cell',
                'bandwidth': ['50', '200'],
                # set build date when deciding
                'build_date': None,
            },
        ]
    },
}

AVAILABLE_STRATEGY_INTERVENTIONS = {
    # Intervention Strategy 1
    # Minimal Intervention 'Do Nothing Scenario'
    # Undertake no upgrades -> will lead to a capacity margin deficit
    # Capacity will be the sum of 800 and 2600 MHz
    'minimal': (),

    # Not a specific intervention strategy, but a foundational step in the
    # following strategies.
    'upgrade_to_lte': ('upgrade_to_lte'),

    # Intervention Strategy 2
    # Integrate 700, 3500 and 26000 MHz on to the macrocellular layer
    # The cost will be the addtion of another carrier on each basestation
    # (providing there is 4G already)
    # If 4G isn't present, the site will need major upgrades.
    'macrocell': (
        'upgrade_to_lte',
        'carrier_700',
        'carrier_3500',
        # 'carrier_26000'
        ),

    # Intervention Strategy 3
    # Deploy a small cell layer at 3700 MHz and 26 GHz
    # The cost will include the small cell unit and the civil works per cell
    'small-cell': ('upgrade_to_lte', 'small_cell'),

    # Intervention Strategy 4
    # Deploy a small cell layer at 3700 MHz and 26 GHz
    # The cost will include the small cell unit and the civil works per cell
    'small-cell-and-spectrum': (
        'upgrade_to_lte',
        'carrier_700',
        'carrier_3500',
        # 'carrier_26000',
        'small_cell'
        ),
}


[docs]def decide_interventions(strategy, budget, service_obligation_capacity, system, timestep, simulation_parameters): """ Given a system and a set of strategy parameters, return the best selected interventions. Parameters ---------- strategy : str One of 'minimal', 'macrocell', 'small_cell' intervention strategies. budget : int Annual budget in GBP. service_obligation_capacity : float Threshold for universal mobile service, in Mbps/km². system : NetworkManager Gives areas (postcode sectors) with population density, demand. timestep : int The current simulation timestep. simulation_parameters : dict All necessary simulation parameters. """ available_interventions = AVAILABLE_STRATEGY_INTERVENTIONS[strategy] if service_obligation_capacity > 0: service_built, budget, service_spend = meet_service_obligation(budget, available_interventions, timestep, service_obligation_capacity, system, simulation_parameters) else: service_built = [] service_spend = [] built, budget, spend = meet_demand( budget, available_interventions, timestep, system, simulation_parameters) print("Built {} assets to meet service obligation, {} to meet demand".format( len(service_built), len(built))) return built + service_built, budget, spend + service_spend
[docs]def meet_service_obligation(budget, available_interventions, timestep, service_obligation_capacity, system, simulation_parameters): """ Suggest areas based on meeting a desired capacity threshold. Parameters ---------- budget : int Annual budget in GBP. available_interventions : tuple Contains the different intervention options that can be selected. timestep : int The current simulation timestep. service_obligation_capacity : float Threshold for universal mobile service, in Mbps/km². system : NetworkManager Gives areas (postcode sectors) with population density, demand. simulation_parameters : dict All necessary simulation parameters. """ areas = _suggest_target_postcodes(system, service_obligation_capacity) return _suggest_interventions(budget, available_interventions, areas, timestep, simulation_parameters, service_obligation_capacity)
[docs]def meet_demand(budget, available_interventions, timestep, system, simulation_parameters): """ Suggest areas based on meeting highest demand first. Parameters ---------- budget : int Annual budget in GBP. available_interventions : tuple Contains the different intervention options that can be selected. timestep : int The current simulation timestep. system : NetworkManager Gives areas (postcode sectors) with population density, demand. simulation_parameters : dict All necessary simulation parameters. """ areas = _suggest_target_postcodes(system) return _suggest_interventions( budget, available_interventions, areas, timestep, simulation_parameters)
def _suggest_interventions(budget, available_interventions, areas, timestep, simulation_parameters, threshold=None): """ Suggest suitable interventions. Parameters ---------- available_interventions : tuple Contains the different intervention options that can be selected. areas : List of objects Contains ranked list of postcode sector objects requiring upgrades. timestep : int The current simulation timestep. simulation_parameters : dict Contains all simulation parameters for the simulation threshold : int A target capacity value desired for each area. """ built_interventions = [] spend = [] for area in areas: area_interventions = [] if budget <= 0: break if _area_satisfied(area, area_interventions, threshold, simulation_parameters): continue # group assets by site assets_by_site = {} for asset in area.assets: if asset['site_ngr'] not in assets_by_site: assets_by_site[asset['site_ngr']] = [asset] else: assets_by_site[asset['site_ngr']].append(asset) # integrate_800 and integrate_2.6 if 'upgrade_to_lte' in available_interventions: build_option = INTERVENTIONS['upgrade_to_lte']['assets_to_build'] cost = INTERVENTIONS['upgrade_to_lte']['cost'] for site_ngr, site_assets in assets_by_site.items(): if site_ngr == 'small_cell_site': continue # built_lte = if ('LTE' not in [asset['technology'] for asset in site_assets] and site_ngr not in [a['site_ngr'] for a in area_interventions if a['technology'] == 'LTE']): # set both assets to this site_ngr for option in build_option: to_build = copy.copy(option) to_build['site_ngr'] = site_ngr to_build['pcd_sector'] = area.id to_build['lad_id'] = area.lad_id to_build['population_density'] = area.population_density to_build['build_date'] = timestep area_interventions.append(to_build) built_interventions.append(to_build) budget -= cost spend.append((area.id, area.lad_id, area.population_density, 'upgrade_to_lte', cost)) if budget <= 0: break if budget <= 0: break # integrate_700 if 'carrier_700' in available_interventions and timestep >= 2020: if _area_satisfied(area, area_interventions, threshold, simulation_parameters): continue build_option = INTERVENTIONS['carrier_700']['assets_to_build'] cost = INTERVENTIONS['carrier_700']['cost'] for site_ngr, site_assets in assets_by_site.items(): if site_ngr == 'small_cell_site': continue if 'LTE' in [asset['technology'] for asset in site_assets] and \ '700' not in [asset['frequency'] for asset in site_assets]: # set both assets to this site_ngr for option in build_option: if option['frequency'] == ['700']: to_build = copy.copy(option) to_build['site_ngr'] = site_ngr to_build['pcd_sector'] = area.id to_build['lad_id'] = area.lad_id to_build['population_density'] = area.population_density to_build['build_date'] = timestep area_interventions.append(to_build) built_interventions.append(to_build) spend.append((area.id, area.lad_id, area.population_density, 'carrier_700', cost)) budget -= cost if budget <= 0: break if budget <= 0: break # integrate_3.5 if 'carrier_3500' in available_interventions and timestep >= 2020: if _area_satisfied(area, area_interventions, threshold, simulation_parameters): continue build_option = INTERVENTIONS['carrier_3500']['assets_to_build'] cost = INTERVENTIONS['carrier_3500']['cost'] for site_ngr, site_assets in assets_by_site.items(): if site_ngr == 'small_cell_site': continue if 'LTE' in [asset['technology'] for asset in site_assets] and \ '3500' not in [asset['frequency'] for asset in site_assets]: # set both assets to this site_ngr for option in build_option: to_build = copy.copy(option) to_build['site_ngr'] = site_ngr to_build['pcd_sector'] = area.id to_build['lad_id'] = area.lad_id to_build['population_density'] = area.population_density to_build['build_date'] = timestep area_interventions.append(to_build) built_interventions.append(to_build) spend.append((area.id, area.lad_id, area.population_density, 'carrier_3500', cost)) budget -= cost if budget <= 0: break if budget <= 0: break if 'carrier_26000' in available_interventions and timestep >= 2020: if not area.clutter_environment == 'urban': continue if _area_satisfied(area, area_interventions, threshold, simulation_parameters): continue build_option = INTERVENTIONS['carrier_26000']['assets_to_build'] cost = INTERVENTIONS['carrier_26000']['cost'] for site_ngr, site_assets in assets_by_site.items(): if site_ngr == 'small_cell_site': continue if 'LTE' in [asset['technology'] for asset in site_assets] and \ '26000' not in [asset['frequency'] for asset in site_assets]: # set both assets to this site_ngr for option in build_option: to_build = copy.copy(option) to_build['site_ngr'] = site_ngr to_build['pcd_sector'] = area.id to_build['lad_id'] = area.lad_id to_build['population_density'] = area.population_density to_build['build_date'] = timestep area_interventions.append(to_build) built_interventions.append(to_build) spend.append((area.id, area.lad_id, area.population_density, 'carrier_26000', cost)) budget -= cost if budget <= 0: break if budget <= 0: break # build small cells to next density if 'small_cell' in available_interventions and timestep >= 2020: # if area.clutter_environment == 'rural': # continue if _area_satisfied(area, area_interventions, threshold, simulation_parameters): continue build_option = INTERVENTIONS['small_cell']['assets_to_build'] cost = INTERVENTIONS['small_cell']['cost'] loop_number = 0 while True: to_build = copy.deepcopy(build_option) to_build[0]['build_date'] = timestep to_build[0]['pcd_sector'] = area.id to_build[0]['lad_id'] = area.lad_id to_build[0]['population_density'] = area.population_density area_interventions += to_build built_interventions += to_build spend.append((area.id, area.lad_id, area.population_density, 'small_cells', cost)) budget -= cost loop_number += 1 if _area_satisfied(area, area_interventions, threshold, simulation_parameters): break if budget <= 0: break return built_interventions, budget, spend def _suggest_target_postcodes(system, threshold=None): """ Sort postcodes by population density (descending) - if considering threshold, filter out any with capacity above threshold. """ postcodes = system.postcode_sectors.values() if threshold is not None: considered_postcodes = [pcd for pcd in postcodes if pcd.capacity < threshold] else: considered_postcodes = [p for p in postcodes] return sorted(considered_postcodes, key=lambda pcd: -pcd.population_density) def _area_satisfied(area, built_interventions, threshold, simulation_parameters): """ Check if area demand has been satisfied by current capacity. """ if threshold is None: target_capacity = area.demand else: target_capacity = threshold data = { "id": area.id, "lad_id": area.lad_id, "population": area.population, "area_km2": area.area, "user_throughput": area.user_throughput, } assets = area.assets + built_interventions test_area = PostcodeSector( data, assets, area._capacity_lookup_table, area._clutter_lookup, simulation_parameters, ) reached_capacity = test_area.capacity return reached_capacity >= target_capacity