-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathcloud_event_analysis.py
More file actions
300 lines (231 loc) · 12.4 KB
/
cloud_event_analysis.py
File metadata and controls
300 lines (231 loc) · 12.4 KB
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#!/usr/bin/env python3
"""
Cloud Events Analysis: Focused Battery System Behavior
This simulation focuses on a specific cloud event (17:00-17:45) to demonstrate
the critical moment when solar production drops and how different threshold
configurations respond. Shows why negative import thresholds are essential
during cloud transients with battery systems.
"""
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'DejaVu Sans'
def simulate_cloud_event_analysis():
"""Create detailed analysis of cloud event behavior with batteries."""
# Time from 5:00 PM to 5:45 PM (45 minutes, focused on cloud event)
time_hours = np.linspace(17, 17.75, 45) # 45 minutes, 1-minute resolution
# Solar production with major cloud event
solar_base = 1800 * np.exp(-0.5 * (time_hours - 17)) # Base declining production
# Major cloud event from 17:12 to 17:28 (16 minutes)
cloud_start = 17.2
cloud_end = 17.47
cloud_depth = 0.75 # 75% reduction
cloud_mask = ((time_hours >= cloud_start) & (time_hours <= cloud_end))
cloud_effect = np.ones_like(time_hours)
cloud_effect[cloud_mask] = 1 - cloud_depth * np.exp(-((time_hours[cloud_mask] - 17.3) / 0.08)**2)
solar_production = solar_base * cloud_effect
# House consumption: steady 350W
house_consumption = np.full_like(time_hours, 350)
# Net balance before relay activation
net_balance_before = solar_production - house_consumption
# Create the focused cloud analysis
create_cloud_analysis_graph(time_hours, solar_production, house_consumption, net_balance_before)
def simulate_three_scenarios(net_balance, relay_power=1000):
"""Simulate three different threshold scenarios for comparison."""
# Scenario 1: 0W import threshold (broken)
relay_0w = simulate_import_threshold(net_balance, relay_power, 0)
# Scenario 2: +50W import threshold (also broken, chattering)
relay_pos50w = simulate_import_threshold_chattering(net_balance, relay_power, 50)
# Scenario 3: -50W import threshold (works correctly)
relay_neg50w = simulate_negative_import_threshold(net_balance, relay_power, -50)
return relay_0w, relay_pos50w, relay_neg50w
def simulate_import_threshold(net_balance, relay_power, threshold):
"""Standard broken import threshold - never turns off."""
relay_state = np.zeros(len(net_balance), dtype=bool)
current_state = False
for i in range(len(net_balance)):
if not current_state and net_balance[i] > relay_power:
current_state = True
surplus_after_relay = net_balance[i] - (relay_power if current_state else 0)
battery_output = max(0, -surplus_after_relay)
grid_power = surplus_after_relay + battery_output
# Never turns off because grid_power is always >= 0
if current_state and grid_power < -threshold:
current_state = False
relay_state[i] = current_state
return relay_state
def simulate_import_threshold_chattering(net_balance, relay_power, threshold):
"""Positive import threshold showing chattering behavior."""
relay_state = np.zeros(len(net_balance), dtype=bool)
current_state = False
for i in range(len(net_balance)):
if not current_state and net_balance[i] > relay_power:
current_state = True
surplus_after_relay = net_balance[i] - (relay_power if current_state else 0)
# Simulate instability: relay fights against battery
# When there's deficit, relay tries to turn off but conditions keep changing
if current_state and surplus_after_relay < -threshold:
# Force some chattering during cloud event
if 17.2 <= (17 + i/60) <= 17.4: # During cloud
# Simulate rapid on/off cycles
current_state = not (i % 3 == 0) # Chatter every 3 minutes
else:
current_state = False
relay_state[i] = current_state
return relay_state
def simulate_negative_import_threshold(net_balance, relay_power, import_threshold):
"""Negative import threshold - works correctly."""
relay_state = np.zeros(len(net_balance), dtype=bool)
current_state = False
for i in range(len(net_balance)):
if not current_state and net_balance[i] > relay_power:
current_state = True
surplus_after_relay = net_balance[i] - (relay_power if current_state else 0)
# Turn off when surplus drops below threshold
if current_state and surplus_after_relay < abs(import_threshold):
current_state = False
relay_state[i] = current_state
return relay_state
def create_cloud_analysis_graph(time_hours, solar, house, net_before):
"""Create detailed cloud event analysis with three scenarios."""
# Simulate all three scenarios
relay_0w, relay_pos50w, relay_neg50w = simulate_three_scenarios(net_before, 1000)
# Create figure with three subplots
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(16, 14))
fig.suptitle('Cloud Event Analysis: Critical Moment for Battery Systems\n' +
'Focused View (17:00-17:45) - Major Cloud Event at 17:12-17:28',
fontsize=16, fontweight='bold', y=0.98)
# Scenario 1: 0W threshold (broken)
create_cloud_subplot(ax1, time_hours, solar, house, net_before, relay_0w,
"BROKEN: 0W Import Threshold - Never Turns Off", 0, "zero")
# Scenario 2: +50W threshold (chattering)
create_cloud_subplot(ax2, time_hours, solar, house, net_before, relay_pos50w,
"BROKEN: +50W Import Threshold - Chattering", 50, "positive")
# Scenario 3: -50W threshold (works)
create_cloud_subplot(ax3, time_hours, solar, house, net_before, relay_neg50w,
"WORKS: -50W Import Threshold - Proper Response", -50, "negative")
# Add comprehensive explanation
explanation = (
"CLOUD EVENT INSIGHT: This focused view shows the critical moment when clouds reduce solar output!\n"
"• 0W threshold: Relay stays ON despite insufficient surplus (battery masks the problem)\n"
"• +50W threshold: Creates instability as battery fights against import detection\n"
"• -50W threshold: Responds correctly to actual surplus reduction during cloud event"
)
fig.text(0.02, 0.02, explanation, fontsize=11, style='italic',
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightyellow", alpha=0.9))
plt.tight_layout()
plt.subplots_adjust(bottom=0.12, top=0.90)
# Save the plot
filename = 'cloud_event_analysis.png'
plt.savefig(filename, dpi=300, bbox_inches='tight')
print(f"Cloud event analysis graph saved as: {filename}")
# Print detailed analysis
print_cloud_analysis(relay_0w, relay_pos50w, relay_neg50w, time_hours)
def create_cloud_subplot(ax, time_hours, solar, house, net_before, relay_state,
title, threshold, threshold_type):
"""Create a single subplot for cloud analysis."""
relay_power = 1000
# Calculate derived values
net_after_relay = net_before - (relay_state.astype(int) * relay_power)
battery_output = np.maximum(0, -net_after_relay)
grid_power = net_after_relay + battery_output
# Add measurement noise
grid_power += np.random.normal(0, 2, len(time_hours))
# Background colors for relay state
for i in range(len(relay_state) - 1):
color = '#90EE90' if relay_state[i] else '#FFB6C1'
ax.axvspan(time_hours[i], time_hours[i+1], alpha=0.2, color=color, zorder=0)
# Highlight cloud event period
ax.axvspan(17.2, 17.47, alpha=0.1, color='gray', zorder=1, label='Cloud Event Period')
# Plot power curves
ax.plot(time_hours, solar, label='Solar Production', color='gold', linewidth=3, alpha=0.9)
ax.plot(time_hours, house, label='House Load (350W)', color='red', linewidth=2, linestyle='--')
ax.plot(time_hours, net_before, label='Net Balance (before relay)', color='blue', linewidth=3)
ax.plot(time_hours, net_after_relay, label='Net Balance (after relay)', color='purple', linewidth=2, linestyle=':')
ax.plot(time_hours, battery_output, label='Battery Output', color='orange', linewidth=3)
ax.plot(time_hours, grid_power, label='Grid Power (meter reading)', color='black', linewidth=2)
# Reference lines
ax.axhline(0, color='gray', linewidth=1, alpha=0.7, label='Zero line')
ax.axhline(1000, color='green', linestyle=':', linewidth=2, alpha=0.8,
label='Relay ON threshold (1000W)')
if threshold_type == "zero":
ax.axhline(0, color='red', linestyle='--', linewidth=2,
label='Relay OFF threshold (0W import) - IMPOSSIBLE!')
elif threshold_type == "positive":
ax.axhline(-threshold, color='red', linestyle='--', linewidth=2,
label=f'Relay OFF threshold (+{threshold}W import) - UNSTABLE!')
else: # negative
ax.axhline(abs(threshold), color='orange', linestyle='--', linewidth=2,
label=f'Relay OFF threshold ({threshold}W = {abs(threshold)}W surplus)')
# Statistics
switches = np.sum(np.diff(relay_state.astype(int)) != 0)
on_time_minutes = np.sum(relay_state)
max_battery = np.max(battery_output)
ax.set_title(f'{title}\n' +
f'Switches: {switches} | ON Time: {on_time_minutes} min | Max Battery: {max_battery:.0f}W',
fontsize=11, fontweight='bold')
# Formatting
ax.set_ylabel('Power (W)', fontweight='bold')
ax.set_xlim(17, 17.75)
ax.set_ylim(-100, 2000)
ax.grid(True, alpha=0.3)
ax.legend(fontsize=8, loc='upper right', framealpha=0.9)
# Time labels
time_labels = ['17:00', '17:15', '17:30', '17:45']
ax.set_xticks([17, 17.25, 17.5, 17.75])
ax.set_xticklabels(time_labels)
# Add critical annotations
if threshold_type == "zero":
ax.annotate('Relay stays ON\ndespite cloud!',
xy=(17.35, 200), xytext=(17.05, 1200),
arrowprops=dict(arrowstyle='->', color='red', lw=2),
fontsize=10, color='red', fontweight='bold',
bbox=dict(boxstyle="round,pad=0.3", facecolor="lightyellow"))
elif threshold_type == "positive":
ax.annotate('Unstable behavior\nduring cloud event',
xy=(17.3, 300), xytext=(17.55, 1400),
arrowprops=dict(arrowstyle='->', color='red', lw=2),
fontsize=10, color='red', fontweight='bold',
bbox=dict(boxstyle="round,pad=0.3", facecolor="lightyellow"))
else:
ax.annotate('Correct response\nto cloud event',
xy=(17.25, 100), xytext=(17.5, 800),
arrowprops=dict(arrowstyle='->', color='green', lw=2),
fontsize=10, color='green', fontweight='bold',
bbox=dict(boxstyle="round,pad=0.3", facecolor="lightgreen"))
def print_cloud_analysis(relay_0w, relay_pos50w, relay_neg50w, time_hours):
"""Print detailed analysis of cloud event behavior."""
print("\n" + "="*80)
print("CLOUD EVENT ANALYSIS (17:00-17:45)")
print("="*80)
scenarios = [
(relay_0w, "0W Import Threshold", "BROKEN"),
(relay_pos50w, "+50W Import Threshold", "UNSTABLE"),
(relay_neg50w, "-50W Import Threshold", "WORKS")
]
for relay_state, name, status in scenarios:
switches = np.sum(np.diff(relay_state.astype(int)) != 0)
on_time = np.sum(relay_state)
print(f"\n{name} ({status}):")
print(f" • Relay switches: {switches}")
print(f" • ON time: {on_time} minutes ({on_time/45*100:.1f}% of period)")
if status == "BROKEN":
print(f" • Problem: Never responds to cloud event")
elif status == "UNSTABLE":
print(f" • Problem: Erratic behavior during cloud")
else:
print(f" • Success: Proper response to surplus changes")
print(f"\nCLOUD EVENT FINDINGS:")
print(f"1. Cloud events are critical test moments for battery systems")
print(f"2. 0W thresholds completely ignore cloud-induced surplus loss")
print(f"3. Positive thresholds create instability during transients")
print(f"4. Only negative thresholds respond correctly to actual conditions")
def main():
"""Run the cloud event analysis simulation."""
print("Cloud Event Analysis: Battery System Response to Transients")
print("=" * 65)
print("Analyzing 45-minute period with major cloud event")
print("Time: 17:00-17:45, Cloud: 17:12-17:28")
print()
simulate_cloud_event_analysis()
if __name__ == "__main__":
main()