Beispiel #1
0
    def sorting_algorithm(self, active_sessions: List[SessionInfo],
                          infrastructure: InfrastructureInfo) -> np.ndarray:
        """ Schedule EVs by first sorting them by sort_fn, then allocating
            them their maximum feasible rate.

        See class documentation for description of the algorithm.

        Args:
            active_sessions (List[SessionInfo]): see BaseAlgorithm
            infrastructure (InfrastructureInfo): Description of the electrical
                infrastructure.

        Returns:
            np.array[Union[float, int]]: Array of charging rates, where each
                row is the charging rates for a specific session.
        """
        queue: List[SessionInfo] = self._sort_fn(active_sessions,
                                                 self.interface)
        schedule: np.ndarray = np.zeros(infrastructure.num_stations)

        # Start each EV at its lower bound
        for session in queue:
            station_index: int = infrastructure.get_station_index(
                session.station_id)
            lb: float = max(0, session.min_rates[0])
            schedule[station_index] = lb

        if not infrastructure_constraints_feasible(schedule, infrastructure):
            raise ValueError(
                "Charging all sessions at their lower bound is not feasible.")

        for session in queue:
            station_index = infrastructure.get_station_index(
                session.station_id)
            ub: float = min(session.max_rates[0],
                            self.interface.remaining_amp_periods(session))
            lb: float = max(0, session.min_rates[0])
            if infrastructure.is_continuous[station_index]:
                charging_rate: float = self.max_feasible_rate(station_index,
                                                              ub,
                                                              schedule,
                                                              infrastructure,
                                                              eps=0.01,
                                                              lb=lb)
            else:
                allowable = [
                    a for a in infrastructure.allowable_pilots[station_index]
                    if lb <= a <= ub
                ]

                if len(allowable) == 0:
                    charging_rate = 0
                else:
                    charging_rate = self.discrete_max_feasible_rate(
                        station_index, allowable, schedule, infrastructure)
            schedule[station_index] = charging_rate
        return schedule
Beispiel #2
0
 def test_inputs_consistent(self) -> None:
     m, n = 6, 5
     # Here, the last two arguments are kwargs, left unspecified to test that the
     # order of overflow is unchanged.
     infra = InfrastructureInfo(
         np.ones((m, n)),
         np.ones((m, )),
         np.ones((n, )),
         np.ones((n, )),
         [f"C-{i}" for i in range(m)],
         [f"S-{i}" for i in range(n)],
         np.ones((n, )),
         np.zeros((n, )),
         [np.array([1, 2, 3, 4])] * n,  # allowable_pilots
         np.zeros((n, )),  # is_continuous
     )
     self.assertEqual(infra.constraint_matrix.shape, (m, n))
     self.assertEqual(infra.constraint_limits.shape, (m, ))
     self.assertEqual(infra.phases.shape, (n, ))
     self.assertEqual(infra.voltages.shape, (n, ))
     self.assertEqual(len(infra.constraint_ids), m)
     self.assertEqual(len(infra.station_ids), n)
     self.assertEqual(infra.max_pilot.shape, (n, ))
     self.assertEqual(infra.min_pilot.shape, (n, ))
     self.assertEqual(len(infra.allowable_pilots), n)
     self.assertEqual(infra.is_continuous.shape, (n, ))
     self.assertEqual(infra.is_continuous.dtype, "bool")
Beispiel #3
0
def remove_finished_sessions(
    active_sessions: List[SessionInfo],
    infrastructure: InfrastructureInfo,
    period: float,
) -> List[SessionInfo]:
    """ Remove any sessions where the remaining demand is less than threshold.
    Here, the threshold is defined as the amount of energy delivered by charging at
    the min_pilot of a session's station, at the station's voltage, for one simulation
    period.

    Args:
        active_sessions (List[SessionInfo]): List of SessionInfo objects for
            all active charging sessions.
        infrastructure (InfrastructureInfo): Description of the charging
            infrastructure.
        period (float): Length of each time period in minutes.


    Returns:
        List[SessionInfo]: Active sessions without any sessions that are finished.

    """
    modified_sessions = []
    for s in active_sessions:
        station_index = infrastructure.get_station_index(s.station_id)
        threshold = (
            infrastructure.min_pilot[station_index]
            * infrastructure.voltages[station_index]
            / (60 / period)
            / 1000
        )  # kWh
        if s.remaining_demand > threshold:
            modified_sessions.append(s)
    return modified_sessions
Beispiel #4
0
 def test_num_stations_num_constraints_mismatch(self) -> None:
     m, n = 5, 6
     with self.assertRaises(ValueError):
         InfrastructureInfo(
             np.ones((m + 1, n)),
             np.ones((m, )),
             np.ones((n, )),
             np.ones((n, )),
             [f"C-{i}" for i in range(m - 1)],
             [f"S-{i}" for i in range(n)],
             np.ones((n, )),
             np.zeros((n + 1, )),
             allowable_pilots=[np.array([1, 2, 3, 4])] * n,
             is_continuous=np.zeros((n - 1, )),
         )
Beispiel #5
0
 def test_inputs_allowable_pilot_defaults(self) -> None:
     m, n = 6, 5
     infra = InfrastructureInfo(
         np.ones((m, n)),
         np.ones((m, )),
         np.ones((n, )),
         np.ones((n, )),
         [f"C-{i}" for i in range(m)],
         [f"S-{i}" for i in range(n)],
         np.ones((n, )),
         np.zeros((n, )),
         is_continuous=np.zeros((n, )),
     )
     self.assertEqual(len(infra.allowable_pilots), n)
     self.assertEqual(infra.allowable_pilots, [None] * n)
 def test_pilot_less_than_existing_max(self) -> None:  # pylint: disable=no-self-use
     sessions = session_generator(
         num_sessions=N,
         arrivals=[ARRIVAL_TIME] * N,
         departures=[ARRIVAL_TIME + SESSION_DUR] * N,
         requested_energy=[3.3] * N,
         remaining_energy=[3.3] * N,
         max_rates=[np.repeat(40, SESSION_DUR)] * N,
     )
     sessions = [SessionInfo(**s) for s in sessions]
     infrastructure = InfrastructureInfo(
         **single_phase_single_constraint(num_evses=N, limit=32))
     modified_sessions = enforce_pilot_limit(sessions, infrastructure)
     for session in modified_sessions:
         nptest.assert_almost_equal(session.max_rates, 32)
Beispiel #7
0
 def test_inputs_is_continuous_default(self) -> None:
     m, n = 6, 5
     infra = InfrastructureInfo(
         np.ones((m, n)),
         np.ones((m, )),
         np.ones((n, )),
         np.ones((n, )),
         [f"C-{i}" for i in range(m)],
         [f"S-{i}" for i in range(n)],
         np.ones((n, )),
         np.zeros((n, )),
         allowable_pilots=[np.array([1, 2, 3, 4])] * n,
     )
     nptest.assert_array_equal(infra.is_continuous, True)
     self.assertEqual(infra.is_continuous.shape, (n, ))
     self.assertEqual(infra.is_continuous.dtype, "bool")
 def _test_remove_sessions_within_threshold(
         self, remaining_energies: List[float]) -> None:
     sessions = session_generator(
         num_sessions=N,
         arrivals=[1, 2, 3],
         departures=[1 + SESSION_DUR, 2 + SESSION_DUR, 3 + SESSION_DUR],
         requested_energy=[3.3] * N,
         remaining_energy=remaining_energies,
         max_rates=[np.repeat(32, SESSION_DUR)] * N,
     )
     infrastructure = InfrastructureInfo(
         **three_phase_balanced_network(1, limit=100))
     sessions = [SessionInfo(**s) for s in sessions]
     modified_sessions = remove_finished_sessions(sessions, infrastructure,
                                                  5)
     self.assertEqual(len(modified_sessions), 2)
Beispiel #9
0
def remaining_amp_periods(session: SessionInfo,
                          infrastructure: InfrastructureInfo,
                          period: float) -> float:
    """ Return the session's remaining demand in A*periods. This function is a static
    version of acnsim.Interface.remaining_amp_periods.

    Args:
        session (SessionInfo): The SessionInfo object for which to get remaining demand.
        infrastructure (InfrastructureInfo): The InfrastructureInfo object that contains
            voltage information about the network.
        period (float): Period of the simulation in minutes.

    Returns:
        float: the EV's remaining demand in A*periods.
    """
    i = infrastructure.get_station_index(session.station_id)
    amp_hours = session.remaining_demand * 1000 / infrastructure.voltages[i]
    return amp_hours * 60 / period
Beispiel #10
0
 def test_num_constraints_mismatch(self) -> None:
     m, n = 5, 6
     for i in range(3):
         for error in [-1, 1]:
             errors = [0] * 3
             errors[i] = error
             with self.assertRaises(ValueError):
                 InfrastructureInfo(
                     np.ones((m + errors[0], n)),
                     np.ones((m + errors[1], )),
                     np.ones((n, )),
                     np.ones((n, )),
                     [f"C-{i}" for i in range(m + errors[2])],
                     [f"S-{i}" for i in range(n)],
                     np.ones((n, )),
                     np.zeros((n, )),
                     allowable_pilots=[np.array([1, 2, 3, 4])] * n,
                     is_continuous=np.zeros((n, )),
                 )
Beispiel #11
0
    def infrastructure_info(self) -> InfrastructureInfo:
        """ Returns an InfrastructureInfo object generated from interface.

        Returns:
            InfrastructureInfo: A description of the charging infrastructure.
        """
        infrastructure = self._get_or_error("infrastructure_info")
        return InfrastructureInfo(
            np.array(infrastructure["constraint_matrix"]),
            np.array(infrastructure["constraint_limits"]),
            np.array(infrastructure["phases"]),
            np.array(infrastructure["voltages"]),
            infrastructure["constraint_ids"],
            infrastructure["station_ids"],
            np.array(infrastructure["max_pilot"]),
            np.array(infrastructure["min_pilot"]),
            [np.array(allowable) for allowable in infrastructure["allowable_pilots"]],
            np.array(infrastructure["is_continuous"]),
        )
Beispiel #12
0
def enforce_pilot_limit(
    active_sessions: List[SessionInfo], infrastructure: InfrastructureInfo
) -> List[SessionInfo]:
    """ Update the max_rates vector for each session to be less than the max
        pilot supported by its EVSE.

    Args:
        active_sessions (List[SessionInfo]): List of SessionInfo objects for
            all active charging sessions.
        infrastructure (InfrastructureInfo): Description of the charging
            infrastructure.

    Returns:
        List[SessionInfo]: Active sessions with max_rates updated to be at
            most the max_pilot of the corresponding EVSE.
    """
    for session in active_sessions:
        i = infrastructure.get_station_index(session.station_id)
        session.max_rates = np.minimum(session.max_rates, infrastructure.max_pilot[i])
    return active_sessions
Beispiel #13
0
 def test_num_stations_mismatch(self) -> None:
     m, n = 5, 6
     for i in range(8):
         for error in [-1, 1]:
             errors = [0] * 8
             errors[i] = error
             with self.assertRaises(ValueError):
                 InfrastructureInfo(
                     np.ones((m, n + errors[0])),
                     np.ones((m, )),
                     np.ones((n + errors[1], )),
                     np.ones((n + errors[2], )),
                     [f"C-{i}" for i in range(m)],
                     [f"S-{i}" for i in range(n + errors[3])],
                     np.ones((n + errors[4], )),
                     np.zeros((n + errors[5], )),
                     allowable_pilots=[np.array([1, 2, 3, 4])] *
                     (n + errors[6]),
                     is_continuous=np.zeros((n + errors[7], )),
                 )
Beispiel #14
0
 def test_apply_min_infeasible(self) -> None:  # pylint: disable=no-self-use
     sessions = session_generator(
         num_sessions=N,
         arrivals=[1, 2, 3],
         departures=[1 + SESSION_DUR, 2 + SESSION_DUR, 3 + SESSION_DUR],
         requested_energy=[3.3] * N,
         remaining_energy=[3.3] * N,
         max_rates=[np.repeat(32, SESSION_DUR)] * N,
     )
     sessions = [SessionInfo(**s) for s in sessions]
     infrastructure = InfrastructureInfo(
         **single_phase_single_constraint(N, 16))
     modified_sessions = apply_minimum_charging_rate(
         sessions, infrastructure, PERIOD)
     for i in range(2):
         nptest.assert_almost_equal(modified_sessions[i].min_rates[0], 8)
         nptest.assert_almost_equal(modified_sessions[i].min_rates[1:], 0)
     # It is not feasible to deliver 8 A to session '2', so max and
     # min should be 0 at time t=0.
     nptest.assert_almost_equal(modified_sessions[2].min_rates, 0)
     nptest.assert_almost_equal(modified_sessions[2].max_rates[0], 0)
Beispiel #15
0
 def _session_generation_helper(
     max_rate_list: List[Union[float, np.ndarray]],
     min_rate_list: Optional[List[Union[float, np.ndarray]]] = None,
     remaining_energy: float = 3.3,
 ) -> List[SessionInfo]:
     if min_rate_list is not None:
         min_rate_list *= N
     sessions: List[SessionDict] = session_generator(
         num_sessions=N,
         arrivals=[ARRIVAL_TIME] * N,
         departures=[ARRIVAL_TIME + SESSION_DUR] * N,
         requested_energy=[3.3] * N,
         remaining_energy=[remaining_energy] * N,
         max_rates=max_rate_list * N,
         min_rates=min_rate_list,
     )
     sessions: List[SessionInfo] = [SessionInfo(**s) for s in sessions]
     infrastructure = InfrastructureInfo(
         **single_phase_single_constraint(N, 32))
     modified_sessions = apply_minimum_charging_rate(
         sessions, infrastructure, PERIOD)
     return modified_sessions
Beispiel #16
0
    def energy_constraints(
        rates: cp.Variable,
        active_sessions: List[SessionInfo],
        infrastructure: InfrastructureInfo,
        period,
        enforce_energy_equality=False,
    ):
        """Get constraints on the energy delivered for each session.

        Args:
            rates (cp.Variable): cvxpy variable representing all charging rates. Shape should be (N, T) where N is the
                total number of EVSEs in the system and T is the length of the optimization horizon.
            active_sessions (List[SessionInfo]): List of SessionInfo objects for all active charging sessions.
            infrastructure (InfrastructureInfo): InfrastructureInfo object describing the electrical infrastructure at
                a site.
            period (int): Length of each discrete time period. (min)
            enforce_energy_equality (bool): If True, energy delivered must be equal to energy requested for each EV.
                If False, energy delivered must be less than or equal to request.

        Returns:
            List[cp.Constraint]: List of energy delivered constraints for each session.
        """
        constraints = {}
        for session in active_sessions:
            i = infrastructure.get_station_index(session.station_id)
            planned_energy = cp.sum(
                rates[i, session.arrival_offset:session.arrival_offset +
                      session.remaining_time, ])
            planned_energy *= infrastructure.voltages[i] * period / 1e3 / 60
            constraint_name = f"energy_constraints.{session.session_id}"
            if enforce_energy_equality:
                constraints[constraint_name] = (
                    planned_energy == session.remaining_demand)
            else:
                constraints[constraint_name] = (planned_energy <=
                                                session.remaining_demand)
        return constraints
Beispiel #17
0
    def round_robin(self, active_sessions: List[SessionInfo],
                    infrastructure: InfrastructureInfo) -> np.ndarray:
        """ Schedule EVs using a round robin based equal sharing scheme.

        Implements abstract method schedule from BaseAlgorithm.

        See class documentation for description of the algorithm.

        Args:
            active_sessions (List[SessionInfo]): see BaseAlgorithm
            infrastructure (InfrastructureInfo): Description of electrical
                infrastructure.

        Returns:
            np.array[Union[float, int]]: Array of charging rates, where each
                row is the charging rates for a specific session.
        """
        queue = deque(self._sort_fn(active_sessions, self.interface))
        schedule = np.zeros(infrastructure.num_stations)
        rate_idx = np.zeros(infrastructure.num_stations, dtype=int)
        allowable_pilots = infrastructure.allowable_pilots.copy()
        for session in queue:
            i = infrastructure.get_station_index(session.station_id)
            # If pilot signal is continuous discretize it with increments of
            # continuous_inc.
            if infrastructure.is_continuous[i]:
                allowable_pilots[i] = np.arange(
                    session.min_rates[0],
                    session.max_rates[0] + self.continuous_inc / 2,
                    self.continuous_inc,
                )
            ub = min(
                session.max_rates[0],
                infrastructure.max_pilot[i],
                self.interface.remaining_amp_periods(session),
            )
            lb = max(0, session.min_rates[0])
            # Remove any charging rates which are not feasible.
            allowable_pilots[i] = allowable_pilots[i][
                lb <= allowable_pilots[i]]
            allowable_pilots[i] = allowable_pilots[i][
                allowable_pilots[i] <= ub]
            # All charging rates should start at their lower bound
            schedule[i] = allowable_pilots[i][0] if len(
                allowable_pilots[i]) > 0 else 0

        if not infrastructure_constraints_feasible(schedule, infrastructure):
            raise ValueError(
                "Charging all sessions at their lower bound is not feasible.")

        while len(queue) > 0:
            session = queue.popleft()
            i = infrastructure.get_station_index(session.station_id)
            if rate_idx[i] < len(allowable_pilots[i]) - 1:
                schedule[i] = allowable_pilots[i][rate_idx[i] + 1]
                if infrastructure_constraints_feasible(schedule,
                                                       infrastructure):
                    rate_idx[i] += 1
                    queue.append(session)
                else:
                    schedule[i] = allowable_pilots[i][rate_idx[i]]
        return schedule