diff --git a/pomiary/zwarcie_testowe_result_zdistA.png b/pomiary/zwarcie_testowe_result_zdistA.png new file mode 100644 index 0000000..565bb0b Binary files /dev/null and b/pomiary/zwarcie_testowe_result_zdistA.png differ diff --git a/pomiary/zwarcie_testowe_rezultat_zdistA.md b/pomiary/zwarcie_testowe_rezultat_zdistA.md new file mode 100644 index 0000000..3079e0e --- /dev/null +++ b/pomiary/zwarcie_testowe_rezultat_zdistA.md @@ -0,0 +1,15 @@ +# Wynik analizy dla zwarcie_testowe + +## Parametry zabezpieczenia (DistanceRelayZDistA (bazowany na ZDistA_komp.c)) +- Impedancja linii: R=2.00 Ohm, X=8.00 Ohm +- Kat linii: 75.0 st. +- Strefa 1: 80% (0ms) +- Strefa 2: 120% (300ms) +- Strefa 3: 150% (600ms) + +## Wykrycie zwarcia: TAK +- Czas wykrycia: 256.00 ms +- Faza: L2 +## Wartości w momencie zwarcia +- R = -11.7875 Ohm, X = -3.3899 Ohm +- |Z| = 12.2652 Ohm, Kat Z = -163.96 st. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6ce692a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy +matplotlib +comtrade diff --git a/tester.py b/tester.py index 3b83ff6..2b71a84 100644 --- a/tester.py +++ b/tester.py @@ -1,20 +1,48 @@ import numpy as np import matplotlib -matplotlib.use('Agg') # Non-interactive backend for saving plots +# matplotlib.use('Agg') jest teraz warunkowe w zależności od argumentów import matplotlib.pyplot as plt from comtrade import Comtrade import math import sys import os +import argparse + +# ============================================================ +# KONFIGURACJA - ARGUMENTY +# ============================================================ +parser = argparse.ArgumentParser( + description="Analizator rejestracji zwarciowych dla zabezpieczeń odległościowych.", + formatter_class=argparse.RawTextHelpFormatter # Umożliwia lepsze formatowanie tekstu pomocy +) +parser.add_argument("base_name", nargs='?', default="pomiary/zwarcie_testowe", + help="Ścieżka do pliku COMTRADE (bez rozszerzenia .cfg/.dat).\n" + "Domyślnie: pomiary/zwarcie_testowe") +parser.add_argument("--live-plot", type=str, choices=['z', 'u', 'i'], + help="Włącz podgląd wybranej wielkości na żywo w formie wektorowej:\n" + " 'z' - wektory impedancji\n" + " 'u' - wektory napięć\n" + " 'i' - wektory prądów") +parser.add_argument("--speed", type=float, default=0.001, + help="Szybkość przetwarzania dla podglądu na żywo (opóźnienie w sekundach między próbkami).\n" + "Mniejsza wartość = szybciej. Użyj 0 dla maksymalnej prędkości.\n" + "Domyślnie: 0.001s.") +parser.add_argument('--algorithm', type=int, default=2, choices=[1, 2], + help="Wybierz algorytm:\n" + " 1 - DistanceRelay (podstawowy)\n" + " 2 - DistanceRelayZDistA (bazowany na ZDistA_komp.c)\n" + "Domyślnie: 2") + +args = parser.parse_args() + +# Konfiguracja backendu Matplotlib +if not args.live_plot: + matplotlib.use('Agg') # Użyj nieinteraktywnego backendu, jeśli nie ma podglądu na żywo # ============================================================ # KONFIGURACJA - WYBIERZ ALGORYTM # ============================================================ -# Dostępne algorytmy: -# 1 - distance_algorithm (DistanceRelay) - podstawowy -# 2 - distance_algorithm_zimba (DistanceRelayZDistA) - bazowany na ZDistA_komp.c - -ALGORITHM = 2 # <-- zmień tę wartość aby przełączyć algorytm +ALGORITHM = args.algorithm if ALGORITHM == 1: from distance_algorithm import DistanceRelay @@ -26,6 +54,7 @@ elif ALGORITHM == 2: ALGORITHM_NAME = "zdistA" ALGORITHM_DESC = "DistanceRelayZDistA (bazowany na ZDistA_komp.c)" else: + # Ten kod jest na wszelki wypadek, argparse powinien to wyłapać raise ValueError(f"Nieznany algorytm: {ALGORITHM}") print(f"=== Używany algorytm: {ALGORITHM_DESC} ===") @@ -82,13 +111,19 @@ PRZEKLADNIA_NAPIECIA = 1100.0 KIERUNEK = 0 # 0=bez, 1=do linii, 2=do szyn # Obsluga argumentow wiersza polecen -if len(sys.argv) > 1: - base_name = sys.argv[1] -else: - base_name = "zwarcie_testowe" +base_name = args.base_name # Wczytywanie nastaw z pliku -directory = os.path.dirname(os.path.abspath(base_name)) +if os.path.isdir(base_name): + directory = base_name + print(f"BŁĄD: Podano katalog '{directory}', proszę podać pełną ścieżkę do pliku bez rozszerzenia.") + sys.exit(1) +else: + directory = os.path.dirname(os.path.abspath(base_name)) + +if not directory: + directory = '.' # przypadek, gdy podano tylko nazwę pliku + settings = parse_settings(directory) # Nadpisz domyślne wartości, jeśli istnieją w pliku nastaw @@ -106,6 +141,10 @@ PRZEKLADNIA_EFF = PRZEKLADNIA_NAPIECIA / PRZEKLADNIA cfg_file = f"{base_name}.cfg" dat_file = f"{base_name}.dat" +if not os.path.exists(cfg_file): + print(f"BŁĄD: Plik konfiguracyjny {cfg_file} nie istnieje.") + sys.exit(1) + print(f"Wczytywanie rejestracji: {cfg_file}") rec = Comtrade() @@ -123,7 +162,7 @@ for encoding in encodings: continue if not loaded: - print(f"BŁĄD: Nie można wczytać pliku {cfg_file}") + print(f"BŁĄD: Nie można wczytać pliku {cfg_file} z żadnym ze znanych kodowań.") sys.exit(1) # Sprawdz czy mamy wystarczajaco kanalow analogowych @@ -212,7 +251,6 @@ else: # Utworzenie relay z nastawami z pliku relay = DistanceRelay(Z_line_R=Z_line_R, Z_line_X=Z_line_X, line_angle=line_angle, kierunek=KIERUNEK, settings=settings) -# ... (reszta skryptu bez zmian) ... # Macierz operatora obrotu dla składowych symetrycznych a = complex(-0.5, np.sqrt(3)/2) a2 = complex(-0.5, -np.sqrt(3)/2) @@ -225,38 +263,67 @@ trip_history_L1 = [] trip_history_L2 = [] trip_history_L3 = [] -# Impedance phasors (R + jX) -z1_r_history = [] -z1_x_history = [] -z2_r_history = [] -z2_x_history = [] -z3_r_history = [] -z3_x_history = [] +z1_r_history, z1_x_history = [], [] +z2_r_history, z2_x_history = [], [] +z3_r_history, z3_x_history = [], [] def calculate_impedance(u_cpx, i_cpx): """Oblicza impedancję Z = U/I jako liczbę zespoloną""" - # Konwersja do wartości wtórnych - i_cpx = complex(i_cpx.real / PRZEKLADNIA, i_cpx.imag / PRZEKLADNIA) - u_cpx = complex(u_cpx.real / PRZEKLADNIA_NAPIECIA, u_cpx.imag / PRZEKLADNIA_NAPIECIA) + i_cpx_sec = complex(i_cpx.real / PRZEKLADNIA, i_cpx.imag / PRZEKLADNIA) + u_cpx_sec = complex(u_cpx.real / PRZEKLADNIA_NAPIECIA, u_cpx.imag / PRZEKLADNIA_NAPIECIA) - i_mag_sq = i_cpx.real**2 + i_cpx.imag**2 - if i_mag_sq < 1e-9: # Zabezpieczenie przed dzieleniem przez zero + i_mag_sq = i_cpx_sec.real**2 + i_cpx_sec.imag**2 + if i_mag_sq < 1e-9: return 0.0, 0.0 - # Z = U / I = U * conj(I) / |I|^2 - z_cpx = u_cpx * complex(i_cpx.real, -i_cpx.imag) / i_mag_sq + z_cpx = u_cpx_sec * i_cpx_sec.conjugate() / i_mag_sq return z_cpx.real, z_cpx.imag -# Symulacja "czasu rzeczywistego" próbka po próbce -for i in range(N, len(t)): - # Pobranie okna danych (historyczne N próbek aż do obecnej chwili i) - window_i1 = i1_raw[i-N:i] - window_i2 = i2_raw[i-N:i] - window_i3 = i3_raw[i-N:i] - window_u1 = u1_raw[i-N:i] - window_u2 = u2_raw[i-N:i] - window_u3 = u3_raw[i-N:i] +# ===== Inicjalizacja podglądu na żywo ===== +live_plot_fig = None +live_ax = None +live_lines = {} +live_data = {} + +if args.live_plot: + plt.ion() + live_plot_fig, live_ax = plt.subplots(figsize=(8, 8), subplot_kw={'projection': 'polar'}) + live_data = {'L1': ([], []), 'L2': ([], []), 'L3': ([], [])} # Kąty, Moduły + + if args.live_plot == 'z': + live_ax.set_title('Podgląd wektorów impedancji na żywo') + line_angle_rad = math.radians(line_angle) + live_ax.plot([line_angle_rad, line_angle_rad], [0, Z_line_mag], color='k', linestyle='--', linewidth=2, label=f'Linia Z ({line_angle:.1f} deg)') + rmax = getattr(relay, 'Z3_R', getattr(relay, 'Z5_R', 20)) * 1.5 + live_ax.set_rmax(rmax) + + elif args.live_plot == 'u': + live_ax.set_title('Podgląd wektorów napięć na żywo') + data_max = max(d.max() for d in (u1_raw, u2_raw, u3_raw) if len(d) > 0) + live_ax.set_rmax(data_max * 1.2 if data_max > 0 else 1) + + elif args.live_plot == 'i': + live_ax.set_title('Podgląd wektorów prądów na żywo') + data_max = max(d.max() for d in (i1_raw, i2_raw, i3_raw) if len(d) > 0) + live_ax.set_rmax(data_max * 1.2 if data_max > 0 else 1) + + # Wspólna konfiguracja dla wszystkich wykresów wektorowych + live_lines['L1'], = live_ax.plot([], [], 'b.', markersize=7, label='L1') + live_lines['L2'], = live_ax.plot([], [], 'g.', markersize=7, label='L2') + live_lines['L3'], = live_ax.plot([], [], 'o', color='orange', markerfacecolor='none', markersize=7, label='L3') + + live_ax.legend(loc='upper right', fontsize=8) + live_ax.grid(True) + live_plot_fig.tight_layout() + live_plot_fig.canvas.draw() + live_plot_fig.canvas.flush_events() +# ========================================== + + +# Główna pętla symulacji +for i in range(N, len(t)): + window_i1, window_i2, window_i3 = i1_raw[i-N:i], i2_raw[i-N:i], i3_raw[i-N:i] + window_u1, window_u2, window_u3 = u1_raw[i-N:i], u2_raw[i-N:i], u3_raw[i-N:i] - # Filtracja DFT dla wszystkich faz i1_re, i1_im = fcdft(window_i1) i2_re, i2_im = fcdft(window_i2) i3_re, i3_im = fcdft(window_i3) @@ -264,21 +331,14 @@ for i in range(N, len(t)): u2_re, u2_im = fcdft(window_u2) u3_re, u3_im = fcdft(window_u3) - # Tworzenie liczb zespolonych - I1_cpx = complex(i1_re, i1_im) - I2_cpx = complex(i2_re, i2_im) - I3_cpx = complex(i3_re, i3_im) - U1_cpx = complex(u1_re, u1_im) - U2_cpx = complex(u2_re, u2_im) - U3_cpx = complex(u3_re, u3_im) + I1_cpx, I2_cpx, I3_cpx = complex(i1_re, i1_im), complex(i2_re, i2_im), complex(i3_re, i3_im) + U1_cpx, U2_cpx, U3_cpx = complex(u1_re, u1_im), complex(u2_re, u2_im), complex(u3_re, u3_im) - # Obliczanie składowych symetrycznych I0_cpx = (I1_cpx + I2_cpx + I3_cpx) / 3.0 I1zg_cpx = (I1_cpx + a * I2_cpx + a2 * I3_cpx) / 3.0 U0_cpx = (U1_cpx + U2_cpx + U3_cpx) / 3.0 U1zg_cpx = (U1_cpx + a * U2_cpx + a2 * U3_cpx) / 3.0 - # Przekazanie danych do algorytmu zabezpieczeniowego trip_l1 = relay.step_relay('L1', U1_cpx.real, U1_cpx.imag, I1_cpx.real, I1_cpx.imag, I0_cpx.real, I0_cpx.imag, U0_cpx.real, U0_cpx.imag, U1zg_cpx.real, U1zg_cpx.imag, I1zg_cpx.real, I1zg_cpx.imag) trip_l2 = relay.step_relay('L2', U2_cpx.real, U2_cpx.imag, I2_cpx.real, I2_cpx.imag, I0_cpx.real, I0_cpx.imag, U0_cpx.real, U0_cpx.imag, U1zg_cpx.real, U1zg_cpx.imag, I1zg_cpx.real, I1zg_cpx.imag) trip_l3 = relay.step_relay('L3', U3_cpx.real, U3_cpx.imag, I3_cpx.real, I3_cpx.imag, I0_cpx.real, I0_cpx.imag, U0_cpx.real, U0_cpx.imag, U1zg_cpx.real, U1zg_cpx.imag, I1zg_cpx.real, I1zg_cpx.imag) @@ -298,9 +358,55 @@ for i in range(N, len(t)): z3_r_history.append(z3_r) z3_x_history.append(z3_x) -# 5. Rysowanie wyników + # ===== Aktualizacja podglądu na żywo ===== + if args.live_plot and live_plot_fig: + if args.live_plot == 'z': + z1_mag, z1_ang = math.sqrt(z1_r**2 + z1_x**2), math.atan2(z1_x, z1_r) + z2_mag, z2_ang = math.sqrt(z2_r**2 + z2_x**2), math.atan2(z2_x, z2_r) + z3_mag, z3_ang = math.sqrt(z3_r**2 + z3_x**2), math.atan2(z3_x, z3_r) + if z1_mag > 0.01: live_data['L1'][0].append(z1_ang); live_data['L1'][1].append(z1_mag) + if z2_mag > 0.01: live_data['L2'][0].append(z2_ang); live_data['L2'][1].append(z2_mag) + if z3_mag > 0.01: live_data['L3'][0].append(z3_ang); live_data['L3'][1].append(z3_mag) + + elif args.live_plot == 'u': + u1_mag, u1_ang = abs(U1_cpx), np.angle(U1_cpx) + u2_mag, u2_ang = abs(U2_cpx), np.angle(U2_cpx) + u3_mag, u3_ang = abs(U3_cpx), np.angle(U3_cpx) + live_data['L1'][0].append(u1_ang); live_data['L1'][1].append(u1_mag) + live_data['L2'][0].append(u2_ang); live_data['L2'][1].append(u2_mag) + live_data['L3'][0].append(u3_ang); live_data['L3'][1].append(u3_mag) + + elif args.live_plot == 'i': + i1_mag, i1_ang = abs(I1_cpx), np.angle(I1_cpx) + i2_mag, i2_ang = abs(I2_cpx), np.angle(I2_cpx) + i3_mag, i3_ang = abs(I3_cpx), np.angle(I3_cpx) + live_data['L1'][0].append(i1_ang); live_data['L1'][1].append(i1_mag) + live_data['L2'][0].append(i2_ang); live_data['L2'][1].append(i2_mag) + live_data['L3'][0].append(i3_ang); live_data['L3'][1].append(i3_mag) + + # Wspólna aktualizacja dla wszystkich wykresów wektorowych + live_lines['L1'].set_data(live_data['L1']) + live_lines['L2'].set_data(live_data['L2']) + live_lines['L3'].set_data(live_data['L3']) + + live_ax.set_title(f'Podgląd na żywo (próbka {i+1}/{len(t)})') + live_plot_fig.canvas.draw() + live_plot_fig.canvas.flush_events() + + if args.speed > 0: + plt.pause(args.speed) +# ========================================== + +if args.live_plot: + plt.ioff() + print("Podgląd na żywo zakończony. Zostanie wyświetlony końcowy raport graficzny.") + plt.show() # Utrzymaj okno podglądu otwarte + + +# ===== Rysowanie końcowych wyników ===== +print("Generowanie końcowego raportu graficznego...") plt.figure(figsize=(16, 12)) -# ... (reszta kodu rysującego i generującego raporty bez zmian, ponieważ bazuje na obiekcie `relay`, który jest już poprawnie skonfigurowany) + # Prądy faz L1, L2, L3 plt.subplot(3, 2, 1) plt.plot(t[N:], i1_raw[N:], label='I_L1', color='blue') @@ -314,33 +420,31 @@ plt.grid(True) # Charakterystyka R-X plt.subplot(3, 2, 2) -angle_rad = math.radians(relay.angle_r1) -# Strefa 1 +angle_rad = math.radians(relay.line_angle) if hasattr(relay, 'Z1_R'): - plt.fill_between(np.linspace(0, relay.Z1_R, 2), -relay.Z1_X, relay.Z1_X, alpha=0.2, color='green', label='Strefa 1') -# Strefa 2 + plt.fill_between([0, relay.Z1_R], relay.Z1_X, -relay.Z1_X, color='green', alpha=0.2, label='Strefa 1') if hasattr(relay, 'Z2_R'): - plt.fill_between(np.linspace(0, relay.Z2_R, 2), -relay.Z2_X, relay.Z2_X, alpha=0.15, color='yellow', label='Strefa 2') -# Strefa 3 + plt.fill_between([0, relay.Z2_R], relay.Z2_X, -relay.Z2_X, color='yellow', alpha=0.15, label='Strefa 2') if hasattr(relay, 'Z3_R'): - plt.fill_between(np.linspace(0, relay.Z3_R, 2), -relay.Z3_X, relay.Z3_X, alpha=0.1, color='red', label='Strefa 3') + plt.fill_between([0, relay.Z3_R], relay.Z3_X, -relay.Z3_X, color='red', alpha=0.1, label='Strefa 3') z_line = np.linspace(0, relay.Z_line_mag * 1.5, 50) x_line = z_line * math.tan(angle_rad) -plt.plot(z_line, x_line, 'k--', linewidth=1, label='Linia Z') +plt.plot(z_line, x_line, 'k--', linewidth=1, label=f'Linia Z ({line_angle:.1f} deg)') plt.plot(z1_r_history, z1_x_history, 'b.', markersize=1, alpha=0.5, label='Trajektoria L1') plt.plot(z2_r_history, z2_x_history, 'g.', markersize=1, alpha=0.5, label='Trajektoria L2') plt.plot(z3_r_history, z3_x_history, 'o', color='orange', markersize=1, alpha=0.5, label='Trajektoria L3') -xlim_max = (hasattr(relay, 'Z3_R') and relay.Z3_R or (hasattr(relay, 'Z5_R') and relay.Z5_R or 10)) -ylim_max = (hasattr(relay, 'Z3_X') and relay.Z3_X or (hasattr(relay, 'Z5_X') and relay.Z5_X or 20)) -plt.xlim(-1, xlim_max * 1.2) -plt.ylim(-ylim_max, ylim_max * 1.2) +xlim_max = getattr(relay, 'Z3_R', getattr(relay, 'Z5_R', 20)) +ylim_max = getattr(relay, 'Z3_X', getattr(relay, 'Z5_X', 40)) +plt.xlim(max(-5, -xlim_max * 0.2), xlim_max * 1.2) +plt.ylim(max(-5, -ylim_max * 1.2), ylim_max * 1.2) plt.xlabel('R [Ohm]') plt.ylabel('X [Ohm]') plt.title('Charakterystyka R-X zabezpieczenia odleglosciowego') plt.legend(loc='upper right', fontsize=8) plt.grid(True) +plt.gca().set_aspect('equal', adjustable='box') plt.axhline(y=0, color='k', linewidth=0.5) plt.axvline(x=0, color='k', linewidth=0.5) @@ -371,13 +475,17 @@ plt.grid(True) # Wyjscia zabezpieczenia plt.subplot(3, 2, 5) -max_val = max(max(i1_raw), max(i2_raw), max(i3_raw)) if any(i1_raw) else 1 +max_i = max(np.max(i1_raw), np.max(i2_raw), np.max(i3_raw)) if len(i1_raw)>0 else 1 +min_i = min(np.min(i1_raw), np.min(i2_raw), np.min(i3_raw)) if len(i1_raw)>0 else -1 +trip_plot_height = max_i if max_i > abs(min_i) else abs(min_i) +trip_plot_height = trip_plot_height if trip_plot_height > 0 else 1.0 + plt.plot(t[N:], i1_raw[N:], label='I_L1', color='blue', alpha=0.5) plt.plot(t[N:], i2_raw[N:], label='I_L2', color='green', alpha=0.5) plt.plot(t[N:], i3_raw[N:], label='I_L3', color='orange', alpha=0.5) -plt.plot(t[N:], np.array(trip_history_L1) * max_val, label='Trip L1-E', color='red', linewidth=2) -plt.plot(t[N:], np.array(trip_history_L2) * max_val, label='Trip L2-E', color='darkred', linewidth=2, linestyle='--') -plt.plot(t[N:], np.array(trip_history_L3) * max_val, label='Trip L3-E', color='darkorange', linewidth=2, linestyle=':') +plt.plot(t[N:], np.array(trip_history_L1) * trip_plot_height, label='Trip L1-E', color='red', linewidth=2) +plt.plot(t[N:], np.array(trip_history_L2) * trip_plot_height, label='Trip L2-E', color='darkred', linewidth=2, linestyle='--') +plt.plot(t[N:], np.array(trip_history_L3) * trip_plot_height, label='Trip L3-E', color='darkorange', linewidth=2, linestyle=':') plt.title(f'Wynik testu algorytmu {ALGORITHM_NAME}') plt.xlabel('Czas [s]') plt.ylabel('Wartosc') @@ -392,42 +500,56 @@ z3_mag = np.sqrt(np.array(z3_r_history)**2 + np.array(z3_x_history)**2) plt.plot(t[N:], z1_mag, label='|Z_L1|', color='blue') plt.plot(t[N:], z2_mag, label='|Z_L2|', color='green') plt.plot(t[N:], z3_mag, label='|Z_L3|', color='orange') -if hasattr(relay, 'Z1_R'): - z1_reach = np.sqrt(relay.Z1_R**2 + relay.Z1_X**2) - plt.axhline(y=z1_reach, color='green', linestyle='--', label='Z1 reach') -if hasattr(relay, 'Z2_R'): - z2_reach = np.sqrt(relay.Z2_R**2 + relay.Z2_X**2) - plt.axhline(y=z2_reach, color='orange', linestyle='--', label='Z2 reach') -if hasattr(relay, 'Z3_R'): - z3_reach = np.sqrt(relay.Z3_R**2 + relay.Z3_X**2) - plt.axhline(y=z3_reach, color='red', linestyle='--', label='Z3 reach') +if hasattr(relay, 'z1_reach'): # Dla algorytmu bazującego na procencie linii + z1_val = relay.z1_reach * relay.Z_line_mag + plt.axhline(y=z1_val, color='green', linestyle='--', label=f'Z1 ({relay.z1_reach*100:.0f}%)') + z2_val = relay.z2_reach * relay.Z_line_mag + plt.axhline(y=z2_val, color='orange', linestyle='--', label=f'Z2 ({relay.z2_reach*100:.0f}%)') + z3_val = relay.z3_reach * relay.Z_line_mag + plt.axhline(y=z3_val, color='red', linestyle='--', label=f'Z3 ({relay.z3_reach*100:.0f}%)') +else: # Dla algorytmu bazującego na R i X + if hasattr(relay, 'Z1_R'): + z1_reach = np.sqrt(relay.Z1_R**2 + relay.Z1_X**2) + plt.axhline(y=z1_reach, color='green', linestyle='--', label='Z1 reach') + if hasattr(relay, 'Z2_R'): + z2_reach = np.sqrt(relay.Z2_R**2 + relay.Z2_X**2) + plt.axhline(y=z2_reach, color='orange', linestyle='--', label='Z2 reach') + if hasattr(relay, 'Z3_R'): + z3_reach = np.sqrt(relay.Z3_R**2 + relay.Z3_X**2) + plt.axhline(y=z3_reach, color='red', linestyle='--', label='Z3 reach') + plt.title('Modul impedancji |Z|') plt.xlabel('Czas [s]') plt.ylabel('|Z| [Ohm]') plt.legend() plt.grid(True) +plt.ylim(bottom=0) plt.tight_layout() -output_filename = f"{os.path.splitext(base_name)[0]}_result.png" +output_filename = f"{os.path.splitext(base_name)[0]}_result_{ALGORITHM_NAME}.png" plt.savefig(output_filename) print(f"Wynik zapisany do {output_filename}") # Generowanie pliku rezultat.md def generate_result_md(t, trip_history_L1, trip_history_L2, trip_history_L3, - z1_r_history, z1_x_history, relay, base_name): + z_history, relay, base_name): """Generuje plik rezultat.md z informacjami o wykryciu zwarcia""" + z1_r_hist, z1_x_hist = z_history['L1'] + md_content = [f"# Wynik analizy dla {os.path.basename(base_name)}", ""] md_content.append(f"## Parametry zabezpieczenia ({ALGORITHM_DESC})") md_content.append(f"- Impedancja linii: R={relay.Z_line_R:.2f} Ohm, X={relay.Z_line_X:.2f} Ohm") md_content.append(f"- Kat linii: {relay.line_angle:.1f} st.") - if hasattr(relay, 'z1_reach'): + + # Dodanie informacji o strefach w zależności od typu algorytmu + if hasattr(relay, 'z1_reach'): # algorytm % md_content.append(f"- Strefa 1: {relay.z1_reach*100:.0f}% ({relay.t_z1}ms)") md_content.append(f"- Strefa 2: {relay.z2_reach*100:.0f}% ({relay.t_z2}ms)") md_content.append(f"- Strefa 3: {relay.z3_reach*100:.0f}% ({relay.t_z3}ms)") - else: - md_content.append(f"- Strefa 1: R={relay.Z1_R:.2f} Ohm, X={relay.Z1_X:.2f} Ohm ({relay.tZ1}ms)") - md_content.append(f"- Strefa 2: R={relay.Z2_R:.2f} Ohm, X={relay.Z2_X:.2f} Ohm ({relay.tZ2}ms)") - md_content.append(f"- Strefa 3: R={relay.Z3_R:.2f} Ohm, X={relay.Z3_X:.2f} Ohm ({relay.tZ3}ms)") + else: # algorytm R/X + md_content.append(f"- Strefa 1: R={getattr(relay, 'Z1_R', 'N/A'):.2f} Ohm, X={getattr(relay, 'Z1_X', 'N/A'):.2f} Ohm ({getattr(relay, 'tZ1', 'N/A')}ms)") + md_content.append(f"- Strefa 2: R={getattr(relay, 'Z2_R', 'N/A'):.2f} Ohm, X={getattr(relay, 'Z2_X', 'N/A'):.2f} Ohm ({getattr(relay, 'tZ2', 'N/A')}ms)") + md_content.append(f"- Strefa 3: R={getattr(relay, 'Z3_R', 'N/A'):.2f} Ohm, X={getattr(relay, 'Z3_X', 'N/A'):.2f} Ohm ({getattr(relay, 'tZ3', 'N/A')}ms)") md_content.append("") trip_any = any(trip_history_L1) or any(trip_history_L2) or any(trip_history_L3) @@ -435,6 +557,7 @@ def generate_result_md(t, trip_history_L1, trip_history_L2, trip_history_L3, if trip_any: md_content.append("## Wykrycie zwarcia: TAK") fault_time, fault_phase, fault_idx = -1, "Brak", -1 + for i, (t1, t2, t3) in enumerate(zip(trip_history_L1, trip_history_L2, trip_history_L3)): if t1 or t2 or t3: fault_time = t[N + i] @@ -447,9 +570,9 @@ def generate_result_md(t, trip_history_L1, trip_history_L2, trip_history_L3, md_content.append(f"- Czas wykrycia: {fault_time * 1000:.2f} ms") md_content.append(f"- Faza: {fault_phase}") - z_r, z_x = z1_r_history[fault_idx], z1_x_history[fault_idx] + z_r, z_x = z1_r_hist[fault_idx], z1_x_hist[fault_idx] z_mag = math.sqrt(z_r**2 + z_x**2) - z_angle = math.degrees(math.atan2(z_x, z_r)) + z_angle = math.degrees(math.atan2(z_x, z_r)) if z_mag > 0 else 0 md_content.append("## Wartości w momencie zwarcia") md_content.append(f"- R = {z_r:.4f} Ohm, X = {z_x:.4f} Ohm") @@ -457,13 +580,17 @@ def generate_result_md(t, trip_history_L1, trip_history_L2, trip_history_L3, else: md_content.append("## Wykrycie zwarcia: NIE") - report_filename = f"{os.path.splitext(base_name)[0]}_rezultat.md" + report_filename = f"{os.path.splitext(base_name)[0]}_rezultat_{ALGORITHM_NAME}.md" with open(report_filename, 'w', encoding='utf-8') as f: f.write("\n".join(md_content)) - print(f"Raport zapisany do {report_filename}") + print(f"Raport tekstowy zapisany do {report_filename}") +z_history_all = { + 'L1': (z1_r_history, z1_x_history), + 'L2': (z2_r_history, z2_x_history), + 'L3': (z3_r_history, z3_x_history) +} generate_result_md(t, trip_history_L1, trip_history_L2, trip_history_L3, - z1_r_history, z1_x_history, relay, base_name) + z_history_all, relay, base_name) print("Analiza zakończona.") -