adityss commited on
Commit
61fd4d1
·
1 Parent(s): 6785dd4

fix: include env/faults.go missed due to .gitignore

Browse files
Files changed (2) hide show
  1. .gitignore +0 -0
  2. env/faults.go +170 -0
.gitignore CHANGED
Binary files a/.gitignore and b/.gitignore differ
 
env/faults.go ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Package env defines fault events and injection logic for GridMind-RL.
2
+ // Faults are rare but high-impact events that force agents to adapt their strategies.
3
+ // They target the Wild Card + World Modeling judging themes.
4
+ package env
5
+
6
+ import "math/rand"
7
+
8
+ // FaultType identifies the kind of fault event.
9
+ type FaultType string
10
+
11
+ const (
12
+ FaultChillerFailure FaultType = "chiller_failure" // HVAC efficiency drops
13
+ FaultGridOutage FaultType = "grid_outage" // Price spike + max grid stress
14
+ FaultSensorFault FaultType = "sensor_fault" // Observation noise on temperature
15
+ FaultTariffSpike FaultType = "tariff_spike" // Flash electricity price surge
16
+ )
17
+
18
+ // FaultEvent describes a single active fault during an episode.
19
+ type FaultEvent struct {
20
+ Type FaultType `json:"type"`
21
+ StartStep int `json:"start_step"`
22
+ EndStep int `json:"end_step"` // exclusive
23
+ Severity float64 `json:"severity"` // 0.0–1.0
24
+ Description string `json:"description"`
25
+ }
26
+
27
+ // IsActive returns true if the fault is active at the given step.
28
+ func (f *FaultEvent) IsActive(step int) bool {
29
+ return step >= f.StartStep && step < f.EndStep
30
+ }
31
+
32
+ // FaultSchedule holds all faults scheduled for an episode.
33
+ type FaultSchedule struct {
34
+ Events []FaultEvent `json:"events"`
35
+ }
36
+
37
+ // ActiveAt returns all fault events active at the given step.
38
+ func (fs *FaultSchedule) ActiveAt(step int) []FaultEvent {
39
+ var active []FaultEvent
40
+ for _, e := range fs.Events {
41
+ if e.IsActive(step) {
42
+ active = append(active, e)
43
+ }
44
+ }
45
+ return active
46
+ }
47
+
48
+ // GenerateFaultSchedule creates a randomised schedule of fault events for an episode.
49
+ // Probability and severity are scaled by difficulty level.
50
+ // Guarantees at least one fault fires in hard mode.
51
+ func GenerateFaultSchedule(rng *rand.Rand, difficulty string) *FaultSchedule {
52
+ schedule := &FaultSchedule{}
53
+
54
+ // Base probabilities per fault type - increased for hard mode
55
+ type faultSpec struct {
56
+ fType FaultType
57
+ probEasy float64
58
+ probMed float64
59
+ probHard float64
60
+ minDur int // steps
61
+ maxDur int
62
+ }
63
+
64
+ specs := []faultSpec{
65
+ {FaultChillerFailure, 0.05, 0.15, 0.45, 8, 24},
66
+ {FaultGridOutage, 0.05, 0.10, 0.45, 4, 12},
67
+ {FaultSensorFault, 0.08, 0.15, 0.45, 6, 20},
68
+ {FaultTariffSpike, 0.10, 0.20, 0.50, 1, 4},
69
+ }
70
+
71
+ for _, spec := range specs {
72
+ prob := spec.probEasy
73
+ switch difficulty {
74
+ case "medium":
75
+ prob = spec.probMed
76
+ case "hard":
77
+ prob = spec.probHard
78
+ }
79
+
80
+ if rng.Float64() > prob {
81
+ continue // no fault of this type this episode
82
+ }
83
+
84
+ // Random start time (avoid very first and last 10 steps)
85
+ maxStart := EpisodeSteps - spec.maxDur - 10
86
+ if maxStart < 10 {
87
+ maxStart = 10
88
+ }
89
+ start := 10 + rng.Intn(maxStart)
90
+ dur := spec.minDur + rng.Intn(spec.maxDur-spec.minDur+1)
91
+ end := start + dur
92
+ if end > EpisodeSteps {
93
+ end = EpisodeSteps
94
+ }
95
+ severity := 0.4 + rng.Float64()*0.6 // 0.4–1.0
96
+
97
+ event := FaultEvent{
98
+ Type: spec.fType,
99
+ StartStep: start,
100
+ EndStep: end,
101
+ Severity: severity,
102
+ }
103
+
104
+ switch spec.fType {
105
+ case FaultChillerFailure:
106
+ event.Description = "⚠️ Chiller unit failure — HVAC operating at reduced capacity."
107
+ case FaultGridOutage:
108
+ event.Description = "🔴 Grid brownout — extreme price spike and critical stress signal."
109
+ case FaultSensorFault:
110
+ event.Description = "⚡ Temperature sensor malfunction — indoor temperature readings unreliable."
111
+ case FaultTariffSpike:
112
+ event.Description = "💸 Emergency tariff spike — electricity price has surged. Minimize consumption immediately."
113
+ }
114
+
115
+ schedule.Events = append(schedule.Events, event)
116
+ }
117
+
118
+ // Force at least one fault in hard mode if schedule is empty
119
+ if len(schedule.Events) == 0 && difficulty == "hard" {
120
+ schedule.Events = append(schedule.Events, FaultEvent{
121
+ Type: FaultTariffSpike,
122
+ StartStep: 20,
123
+ EndStep: 23,
124
+ Severity: 0.6,
125
+ Description: "Unexpected tariff spike — immediate load response required",
126
+ })
127
+ }
128
+
129
+ return schedule
130
+ }
131
+
132
+ // ApplyFaults modifies environment signals based on active faults for the current step.
133
+ // It returns a list of active fault descriptions for the observation.
134
+ func ApplyFaults(b *BuildingState, schedule *FaultSchedule, step int, rng *rand.Rand) []string {
135
+ if schedule == nil {
136
+ return nil
137
+ }
138
+ active := schedule.ActiveAt(step)
139
+ if len(active) == 0 {
140
+ // Reset noise when no fault active
141
+ b.TempObservationNoise = 0.0
142
+ return nil
143
+ }
144
+
145
+ descriptions := make([]string, 0, len(active))
146
+ for _, fault := range active {
147
+ switch fault.Type {
148
+ case FaultChillerFailure:
149
+ // Reduce effective HVAC power — the building state's max power is scaled down
150
+ // The physics engine uses MaxHVACPower; we reduce it proportionally to severity.
151
+ b.MaxHVACPower = MaxHVACPowerKW * (1.0 - fault.Severity*0.8)
152
+
153
+ case FaultGridOutage:
154
+ // Force maximum grid stress and multiply the price to simulate outage conditions
155
+ b.GridStressSignal = 1.0
156
+ b.CurrentPrice = b.CurrentPrice * (1.0 + fault.Severity*3.0)
157
+
158
+ case FaultSensorFault:
159
+ // Add noise to the indoor temperature reading (observation only, not physics)
160
+ // This affects what the agent sees but not the actual physics
161
+ b.TempObservationNoise = (rng.Float64()*2 - 1) * 5.0 * fault.Severity
162
+
163
+ case FaultTariffSpike:
164
+ b.CurrentPrice = b.CurrentPrice * (1.0 + fault.Severity*4.0)
165
+ }
166
+
167
+ descriptions = append(descriptions, fault.Description)
168
+ }
169
+ return descriptions
170
+ }