File size: 2,457 Bytes
427a79e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from __future__ import annotations

from dataclasses import dataclass

from smartgrid_mas.models import DispatchAction, MarketObservation


@dataclass
class ReliabilityDispatchControlAgent:
    """Reduced-order dispatcher that turns market state into corrective dispatch."""

    personality: str = "balanced"

    def act(self, obs: MarketObservation, cleared_mwh: float) -> DispatchAction:
        forecast_gap = max(0.0, obs.forecast_demand_mwh - obs.forecast_renewable_mwh)
        actual_gap = max(0.0, obs.demand_mwh - obs.renewable_availability_mwh)
        scarcity = max(0.0, obs.scarcity_index)
        reserve_pressure = min(1.0, max(0.0, scarcity - 0.15))

        if self.personality == "risk_averse":
            reserve_scale = 0.62
            peaker_scale = 1.08
            storage_scale = 0.82
            redispatch_scale = 0.38
        elif self.personality == "opportunistic":
            reserve_scale = 0.34
            peaker_scale = 0.72
            storage_scale = 0.52
            redispatch_scale = 0.22
        else:
            reserve_scale = 0.48
            peaker_scale = 0.92
            storage_scale = 0.68
            redispatch_scale = 0.30

        reserve_activation_mwh = max(0.0, forecast_gap * reserve_scale * (0.75 + 0.25 * reserve_pressure))
        peaker_adjustment_mwh = max(0.0, actual_gap * peaker_scale * (0.85 + 0.15 * reserve_pressure))

        storage_dispatch_mwh = 0.0
        if scarcity > 0.25:
            storage_dispatch_mwh = min(obs.ev_storage_mwh, actual_gap * storage_scale)
        elif obs.renewable_availability_mwh > obs.demand_mwh * 0.95:
            storage_dispatch_mwh = -min(
                max(0.0, obs.ev_storage_capacity_mwh - obs.ev_storage_mwh),
                (obs.renewable_availability_mwh - obs.demand_mwh) * 0.25,
            )

        corrective_redispatch_mwh = max(0.0, (cleared_mwh - obs.demand_mwh + actual_gap) * redispatch_scale)
        if scarcity > 0.40:
            corrective_redispatch_mwh += actual_gap * 0.20
        elif scarcity < 0.12:
            corrective_redispatch_mwh -= min(corrective_redispatch_mwh, obs.demand_mwh * 0.03)

        return DispatchAction(
            reserve_activation_mwh=round(reserve_activation_mwh, 3),
            peaker_adjustment_mwh=round(peaker_adjustment_mwh, 3),
            storage_dispatch_mwh=round(storage_dispatch_mwh, 3),
            corrective_redispatch_mwh=round(corrective_redispatch_mwh, 3),
        )