<jupyter_start><jupyter_text>*If running in Colab run this first to install ACN-Portal.*<jupyter_code>import subprocess
import sys

if 'google.colab' in str(get_ipython()):
    print('Running on CoLab')
    subprocess.check_call([sys.executable, "-m", "pip", "install", "acnportal"])
    subprocess.check_call([sys.executable, "-m", "pip", "install", "git+https://github.com/caltech-netlab/adacharge"])
import os
import subprocess


if not os.path.exists("data/jpl_weekeday_40.pkl"):
    if not os.path.exists("data"):
        subprocess.run(["mkdir", "data"])
    subprocess.run(["wget", "-P", "./data", "https://ev.caltech.edu/assets/data/gmm/jpl_weekday_40.pkl"])<jupyter_output><empty_output><jupyter_text>Comparing Infrastructure Designs using ACN-Sim by <NAME> Last updated: 4/16/2020In this case study, we demonstrate how ACN-Data and ACN-Sim can be used to evaluate infrastructure configurations and algorithms. We consider the case of a site host who expects to charge approximately 100 EVs per day with a demand pattern similar to that of JPL.The site host has several options, including*   102 Uncontrolled Level-1 EVSEs with a 200 kW Transformer*   30 Uncontrolled Level-2 EVSEs with a 200 kW Transformer*   102 Uncontrolled Level-2 EVSEs with a 670 kW Transformer*   102 Smart Level-2 EVSEs running LLF with a 200 kW TransformerWe evaluate the scenarios on the number of times drivers would have to swap parking places to allow other drivers to charge, the percentage of total demand met, and the operating costs (calculated using ACN-Sim's integration with utility tariffs). This demonstrates the significant benefits of developing smart EV charging systems in terms of reducing both capital costs (transformer capacity) and operating costs.<jupyter_code>import acnportal

from copy import deepcopy
import warnings
import pytz
import numpy as np
import pandas as pd
import pickle
from datetime import datetime
from acnportal import acnsim
from acnportal import algorithms
from acnportal.signals.tariffs.tou_tariff import TimeOfUseTariff
from acnportal.acnsim.events import GaussianMixtureEvents
from acnportal.contrib.acnsim import StochasticNetwork
import adacharge<jupyter_output><empty_output><jupyter_text>Charging Network DesignsTo define our charging network options, we will use two functions which generate a StochasticNetwork object. The StochasticNetwork assigns users to spaces dynamically based on available spaces. In this example, we will assume each driver has equal preference for all spots.If all spaces are taken, drivers join a queue which is drained as drivers finish charging and move their vehicle (the early departure option specifies that drivers move their vehicle when it is done charging rather than their normal departure time). We record each time that the user leave and is replaced with someone from the queue as a swap. Swaps are undesirable as they waste time and are frustrating for users. Despite this, swapping is a common practice in many charging facilities where the number of users exceeds the number of EVSEs.<jupyter_code>def level_1_network(transformer_cap=200, evse_per_phase=34):
    """ Configurable charging network for level-1 EVSEs connected line to ground
        at 120 V.

    Args:
        transformer_cap (float): Capacity of the transformer feeding the network
          [kW]
        evse_per_phase (int): Number of EVSEs on each phase. Total number of
          EVSEs will be 3 * evse_per_phase.

    Returns:
        ChargingNetwork: Configured ChargingNetwork.
    """
    network = StochasticNetwork(early_departure=True)
    voltage = 120

    # Define the sets of EVSEs in the Caltech ACN.
    A_ids = ['A-{0}'.format(i) for i in range(evse_per_phase)]
    B_ids = ['B-{0}'.format(i) for i in range(evse_per_phase)]
    C_ids = ['C-{0}'.format(i) for i in range(evse_per_phase)]

    # Add Caltech EVSEs
    for evse_id in A_ids:
        network.register_evse(acnsim.FiniteRatesEVSE(evse_id, [0, 16]), voltage, 0)
    for evse_id in B_ids:
        network.register_evse(acnsim.FiniteRatesEVSE(evse_id, [0, 16]), voltage, 120)
    for evse_id in C_ids:
        network.register_evse(acnsim.FiniteRatesEVSE(evse_id, [0, 16]), voltage, -120)

    # Add Caltech Constraint Set
    I3a = acnsim.Current(A_ids)
    I3b = acnsim.Current(B_ids)
    I3c = acnsim.Current(C_ids)

    # Define intermediate currents
    I2a = (1 / 4) * (I3a - I3c)
    I2b = (1 / 4) * (I3b - I3a)
    I2c = (1 / 4) * (I3c - I3b)

    # Build constraint set
    primary_side_constr = transformer_cap * 1000 / 3 / 277
    secondary_side_constr = transformer_cap * 1000 / 3 / 120
    network.add_constraint(I3a, secondary_side_constr, name='Secondary A')
    network.add_constraint(I3b, secondary_side_constr, name='Secondary B')
    network.add_constraint(I3c, secondary_side_constr, name='Secondary C')
    network.add_constraint(I2a, primary_side_constr, name='Primary A')
    network.add_constraint(I2b, primary_side_constr, name='Primary B')
    network.add_constraint(I2c, primary_side_constr, name='Primary C')

    return network


def level_2_network(transformer_cap=200, evse_per_phase=34):
    """ Configurable charging network for level-2 EVSEs connected line to line
        at 208 V.

    Args:
        transformer_cap (float): Capacity of the transformer feeding the network
          [kW]
        evse_per_phase (int): Number of EVSEs on each phase. Total number of
          EVSEs will be 3 * evse_per_phase.

    Returns:
        ChargingNetwork: Configured ChargingNetwork.
    """
    network = StochasticNetwork(early_departure=True)
    voltage = 208
    evse_type = 'AeroVironment'

    # Define the sets of EVSEs in the Caltech ACN.
    AB_ids = ['AB-{0}'.format(i) for i in range(evse_per_phase)]
    BC_ids = ['BC-{0}'.format(i) for i in range(evse_per_phase)]
    CA_ids = ['CA-{0}'.format(i) for i in range(evse_per_phase)]

    # Add Caltech EVSEs
    for evse_id in AB_ids:
        network.register_evse(acnsim.get_evse_by_type(evse_id, evse_type), voltage, 30)
    for evse_id in BC_ids:
        network.register_evse(acnsim.get_evse_by_type(evse_id, evse_type), voltage, -90)
    for evse_id in CA_ids:
        network.register_evse(acnsim.get_evse_by_type(evse_id, evse_type), voltage, 150)

    # Add Caltech Constraint Set
    AB = acnsim.Current(AB_ids)
    BC = acnsim.Current(BC_ids)
    CA = acnsim.Current(CA_ids)

    # Define intermediate currents
    I3a = AB - CA
    I3b = BC - AB
    I3c = CA - BC
    I2a = (1 / 4) * (I3a - I3c)
    I2b = (1 / 4) * (I3b - I3a)
    I2c = (1 / 4) * (I3c - I3b)

    # Build constraint set
    primary_side_constr = transformer_cap * 1000 / 3 / 277
    secondary_side_constr = transformer_cap * 1000 / 3 / 120
    network.add_constraint(I3a, secondary_side_constr, name='Secondary A')
    network.add_constraint(I3b, secondary_side_constr, name='Secondary B')
    network.add_constraint(I3c, secondary_side_constr, name='Secondary C')
    network.add_constraint(I2a, primary_side_constr, name='Primary A')
    network.add_constraint(I2b, primary_side_constr, name='Primary B')
    network.add_constraint(I2c, primary_side_constr, name='Primary C')

    return network<jupyter_output><empty_output><jupyter_text>ExperimentsIn these experiments we will run a simulation for each system configuration can compare the results on key metrics.<jupyter_code># How long each time discrete time interval in the simulation should be.
PERIOD = 5  # minutes

# Voltage of the network.
VOLTAGE = 208  # volts

# Default maximum charging rate for each EV battery.
DEFAULT_BATTERY_POWER = 6.6 # kW<jupyter_output><empty_output><jupyter_text>**Network Options**We consider five infrastrucuture configurations.<jupyter_code># Network of 102 Level-1 EVSEs with a 200 kW Transformer
level_1 = level_1_network(transformer_cap=200, evse_per_phase=34)

# Network of 30 Level-2 EVSEs with a 200 kW Transformer
level_2_200kW_30 = level_2_network(transformer_cap=200, evse_per_phase=10)

# Network of 102 Level-2 EVSEs with a 200 kW Transformer
level_2_200kW_102 = level_2_network(transformer_cap=200, evse_per_phase=34)

# Network of 102 Level-2 EVSEs with a 680 kW Transformer
level_2_680kW_102 = level_2_network(transformer_cap=680, evse_per_phase=34)

# Network of 201 Level-2 EVSEs with a 200 kW Transformer
level_2_200kW_201 = level_2_network(transformer_cap=200, evse_per_phase=67)<jupyter_output><empty_output><jupyter_text>**Events**We assume that our site will have a usage profile similar to JPL, so we use a Gaussian Mixture Model trained on data from weekdays at JPL to generate events for this experiment. We assume that the site will be closed on weekends, so no charging will occur.<jupyter_code>def get_synth_events(sessions_per_day):
    gmm = pickle.load(open('./data/jpl_weekday_40.pkl', 'rb'))

    # Generate a list of the number of sessions to draw for each day.
    # This generates 30 days of charging demands.
    num_evs = [0]*2 + [sessions_per_day]*5 + [0]*2 + [sessions_per_day]*5 + [0]*2 + \
              [sessions_per_day]*5 + [0]*2 + [sessions_per_day]*5 + [0]*2

    # Note that because we are drawing from a distribution, some sessions will be
    # invalid, we ignore these sessions and remove the corresponding plugin events.
    gen = GaussianMixtureEvents(pretrained_model=gmm, duration_min=0.08334)

    synth_events = gen.generate_events(num_evs, PERIOD, VOLTAGE, DEFAULT_BATTERY_POWER)
    return synth_events
# Events with 100 EVs per weekday
sessions_100 = get_synth_events(100)
# Events with 200 EVs per weekday
sessions_200 = get_synth_events(200)
def run_experiment(network, algorithm, events):
    """ Run simulation for the events defined previously and the specified
        network / algorithm / events.
    """
    # Timezone of the ACN we are using.
    timezone = pytz.timezone('America/Los_Angeles')

    # Start and End times are used when collecting data.
    start = timezone.localize(datetime(2019, 6, 1))
    end = timezone.localize(datetime(2019, 7, 1))

    sch = deepcopy(algorithm)
    cn = deepcopy(network)
    signals = {'tariff': TimeOfUseTariff('sce_tou_ev_4_march_2019')}

    sim = acnsim.Simulator(cn, sch, events, start, period=PERIOD, verbose=False, signals=signals)
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
    sim.run()

    r = {'proportion_of_energy_delivered': acnsim.proportion_of_energy_delivered(sim),
         'energy_delivered': sum(ev.energy_delivered for ev in sim.ev_history.values()),
         'num_swaps': cn.swaps,
         'num_never_charged': cn.never_charged,
         'energy_cost': acnsim.energy_cost(sim),
         'demand_charge': acnsim.demand_charge(sim)
         }
    r['total_cost'] = r['energy_cost'] + r['demand_charge']
    r['$/kWh'] = r['total_cost'] / r['energy_delivered']
    return r<jupyter_output><empty_output><jupyter_text>**Define Algorithms**We consider Uncontrolled, Least-Laxity First and Model Predictive Control (cost-minimization).<jupyter_code>uncontrolled = algorithms.UncontrolledCharging()
llf = algorithms.SortedSchedulingAlgo(algorithms.least_laxity_first)
def days_remaining_scale_demand_charge(rates, infrastructure, interface,
                                       baseline_peak=0, **kwargs):
    day_index = interface.current_time // ((60 / interface.period) * 24)
    days_in_month = 30
    day_index = min(day_index, days_in_month - 1)
    scale = 1 / (days_in_month - day_index)
    dc = adacharge.demand_charge(rates, infrastructure, interface, baseline_peak, **kwargs)
    return scale * dc
cost_min_obj = [adacharge.ObjectiveComponent(adacharge.total_energy, 1000),
                adacharge.ObjectiveComponent(adacharge.tou_energy_cost),
                adacharge.ObjectiveComponent(days_remaining_scale_demand_charge),
                adacharge.ObjectiveComponent(adacharge.quick_charge, 1e-6),
                adacharge.ObjectiveComponent(adacharge.equal_share, 1e-12)
               ]
cost_min = adacharge.AdaptiveSchedulingAlgorithm(cost_min_obj, solver="ECOS", quantize=True, reallocate=True, peak_limit=1000, max_recompute=1)<jupyter_output><empty_output><jupyter_text>**Run Experiments (100 EVs)**<jupyter_code>level1_unctrl_100 = run_experiment(level_1, uncontrolled, deepcopy(sessions_100))
level2_200kW_untrl_100 = run_experiment(level_2_200kW_30, uncontrolled, deepcopy(sessions_100))
level2_670kW_unctrl_100 = run_experiment(level_2_670kW_102, uncontrolled, deepcopy(sessions_100))
level2_200kW_llf_100 = run_experiment(level_2_200kW_102, llf, deepcopy(sessions_100))
level2_200kW_cost_min_100 = run_experiment(level_2_200kW_102, cost_min, deepcopy(sessions_100))<jupyter_output><empty_output><jupyter_text>**Run Experiments (200 EVs)**<jupyter_code>level1_unctrl_200 = run_experiment(level_1, uncontrolled, deepcopy(sessions_200))
level2_200kW_untrl_200 = run_experiment(level_2_200kW_30, uncontrolled, deepcopy(sessions_200))
level2_680kW_unctrl_200 = run_experiment(level_2_680kW_102, uncontrolled, deepcopy(sessions_200))
level2_200kW_llf_200 = run_experiment(level_2_200kW_102, llf, deepcopy(sessions_200))
level2_200kW_cost_min_200 = run_experiment(level_2_200kW_102, cost_min, deepcopy(sessions_200))
level2_200kW_cost_min_201_200 = run_experiment(level_2_200kW_201, cost_min, deepcopy(sessions_200))<jupyter_output><empty_output><jupyter_text>Analyze Results<jupyter_code>evs_100 = pd.DataFrame({
    'Level 1: Unctrl: 200 kW : 102 EVSEs': level1_unctrl_100,
    'Level 2: Unctrl: 200 kW : 30 EVSEs':  level2_200kW_untrl_100,
    'Level 2: Unctrl: 670 kW : 102 EVSEs': level2_670kW_unctrl_100,
    'Level 2: LLF: 200 kW : 102 EVSEs': level2_200kW_llf_100,
    'Level 2: Min Cost: 200 kW : 102 EVSEs': level2_200kW_cost_min_100
})
evs_100.to_csv("results/100_EV_comparison.csv")
pd.read_csv("results/100_EV_comparison.csv", index_col=0)
evs_200 = pd.DataFrame({
    'Level 1: Unctrl: 200 kW : 102 EVSEs': level1_unctrl_200,
    'Level 2: Unctrl: 200 kW : 30 EVSEs':  level2_200kW_untrl_200,
    'Level 2: Unctrl: 680 kW : 102 EVSEs': level2_680kW_unctrl_200,
    'Level 2: LLF: 200 kW : 102 EVSEs': level2_200kW_llf_200,
    'Level 2: Min Cost: 200 kW : 102 EVSEs': level2_200kW_cost_min_200,
    'Level 2: Min Cost: 200 kW : 201 EVSEs': level2_200kW_cost_min_201_200
})
evs_200.to_csv("results/200_EV_comparison.csv")
pd.read_csv("results/200_EV_comparison.csv", index_col=0)<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup