diff --git a/Rejestracje/ABR-STW/18.09.23 14.48.51.906_result.png b/Rejestracje/ABR-STW/18.09.23 14.48.51.906_result.png new file mode 100644 index 0000000..31ccc82 Binary files /dev/null and b/Rejestracje/ABR-STW/18.09.23 14.48.51.906_result.png differ diff --git a/Rejestracje/ABR-STW/18.09.23 14.48.51.906_rezultat.md b/Rejestracje/ABR-STW/18.09.23 14.48.51.906_rezultat.md new file mode 100644 index 0000000..623216c --- /dev/null +++ b/Rejestracje/ABR-STW/18.09.23 14.48.51.906_rezultat.md @@ -0,0 +1,10 @@ +# Wynik analizy dla 18.09.23 14.48.51.906.000 + +## 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: NIE \ No newline at end of file diff --git a/Rejestracje/ABR-STW/Nastawy.txt b/Rejestracje/ABR-STW/Nastawy.txt new file mode 100644 index 0000000..0a9fa0d --- /dev/null +++ b/Rejestracje/ABR-STW/Nastawy.txt @@ -0,0 +1,18 @@ + + +Wartości wtórne: +012.001: X1,PG (polygon) PS1: 16.03 — +002.076: X1,PP (polygon) PS1: 16.03 — +012.002: X2,PG (polygon) PS1: 22.58 — +002.080: X2,PP (polygon) PS1: 22.58 — +012.003: X3,PG (polygon) PS1: 60.00 — +002.084: X3,PP (polygon) PS1: 60.00 — +012.005: R1,PG (polygon) PS1: 21.91 — +012.006: R1,PP (polygon) PS1: 13.73 — +012.007: R2,PG (polygon) PS1: 23.07 — +012.008: R2,PP (polygon) PS1: 14.89 — +012.009: R3,PG (polygon) PS1: 29.66 — +012.010: R3,PP (polygon) PS1: 21.48 — +012.037: Abs. value kG PS1: 0.35(współczynnik ziemnozwarciowy) + +strefa 1W=strefa 2 diff --git a/Rejestracje/EKB-ALY/nastawy stref.txt b/Rejestracje/EKB-ALY/nastawy stref.txt new file mode 100644 index 0000000..fd40910 --- /dev/null +++ b/Rejestracje/EKB-ALY/nastawy stref.txt @@ -0,0 +1,4 @@ + + + + diff --git a/Rejestracje/Lublin-Kozienice/nastawy stref.txt b/Rejestracje/Lublin-Kozienice/nastawy stref.txt new file mode 100644 index 0000000..89655b9 --- /dev/null +++ b/Rejestracje/Lublin-Kozienice/nastawy stref.txt @@ -0,0 +1,15 @@ + + + + + R1ff= 22,28 + R1Wff=23,08 + R2ff=24,32 + R3ff=38,68 + + R1fe=32,28 + R1Wfe=33,08 + R2fe=34,32 + R3fe=48,68 + + diff --git a/Rejestracje/MIL-KOZ/nastawy stref.txt b/Rejestracje/MIL-KOZ/nastawy stref.txt new file mode 100644 index 0000000..fd40910 --- /dev/null +++ b/Rejestracje/MIL-KOZ/nastawy stref.txt @@ -0,0 +1,4 @@ + + + + diff --git a/Rejestracje/MOR-MIL/Nastawy.txt b/Rejestracje/MOR-MIL/Nastawy.txt new file mode 100644 index 0000000..f958868 --- /dev/null +++ b/Rejestracje/MOR-MIL/Nastawy.txt @@ -0,0 +1,38 @@ +Strona wtórna: + +1103 Measurement: Full Scale Voltage (100%) 220,0 kV +1104 Measurement: Full Scale Current (100%) 1200 A +1105 Line Angle 82 ° +1211 Angle of inclination, distance charact. 82 ° +1111 Line Length 47 km +1116 Zero seq. comp. factor RE/RL for Z1 0,00 +1117 Zero seq. comp. factor XE/XL for Z1 0,50 +1118 Zero seq. comp.factor RE/RL(> Z1) 0,00 +1119 Zero seq. comp.factor XE/XL(> Z1) 0,00 + +k0 = 0,4951 (dopisał RL) +fi_k0 = 8 st. (dopisał RL) +k1,2 = 0 (dopisał RL) +fi_k1,2 = -82 st (dopisał RL) + + + +1301 Operating mode Z1 Forward +1302 R(Z1), Resistance for ph-ph-faults 8,00 Ohm +1303 X(Z1), Reactance 8,95 Ohm +1304 RE(Z1), Resistance for ph-e faults 22,00 Ohm + +1351 Operating mode Z1B (overrreach zone) Forward +1352 R(Z1B), Resistance for ph-ph-faults 8,50 Ohm +1353 X(Z1B), Reactance 12,11 Ohm +1354 RE(Z1B), Resistance for ph-e faults 22,50 Ohm + +1311 Operating mode Z2 Forward +1312 R(Z2), Resistance for ph-ph-faults 9,00 Ohm +1313 X(Z2), Reactance 15,93 Ohm +1314 RE(Z2), Resistance for ph-e faults 23,00 Ohm + +1321 Operating mode Z3 Forward +1322 R(Z3), Resistance for ph-ph-faults 11,00 Ohm +1323 X(Z3), Reactance 36,22 Ohm +1324 RE(Z3), Resistance for ph-e faults 24,00 Ohm \ No newline at end of file diff --git a/Rejestracje/OLT-ROG/nastawy stref.txt b/Rejestracje/OLT-ROG/nastawy stref.txt new file mode 100644 index 0000000..5cf7c10 --- /dev/null +++ b/Rejestracje/OLT-ROG/nastawy stref.txt @@ -0,0 +1,8 @@ + + + +Nastawy strona wtórna. + + + + diff --git a/Rejestracje/PEL-RAD/Nastawy.txt b/Rejestracje/PEL-RAD/Nastawy.txt new file mode 100644 index 0000000..28ed1b2 --- /dev/null +++ b/Rejestracje/PEL-RAD/Nastawy.txt @@ -0,0 +1,18 @@ + + +Wartości wtórne: +X1 =14,85 Ω R1ff=7,43 R1fe=15,62 +X1w =20,09 Ω R1wff=8,13 R1wfe=16,31 +X2 =20,09 Ω R2ff=8,13 R2fe=16,31 +X3 =60,00 Ω R3ff=13,45 R3fe=21,64 + +Measurement: Full Scale Voltage (100%) 220,0 kV +Measurement: Full Scale Current (100%) 1200 A +Line Angle 82 ° +Angle of inclination, distance charact. 82 ° +Line Reactance per length unit 0,2290 Ohm / km +Line Length 76,3 km +Zero seq. comp. factor RE/RL for Z1 1,60 +Zero seq. comp. factor XE/XL for Z1 0,40 +Zero seq. comp.factor RE/RL(> Z1) 1,60 +Zero seq. comp.factor XE/XL(> Z1) 0,40 \ No newline at end of file diff --git a/Rejestracje/STW-ABR/Nastawy.txt b/Rejestracje/STW-ABR/Nastawy.txt new file mode 100644 index 0000000..fe2ee7c --- /dev/null +++ b/Rejestracje/STW-ABR/Nastawy.txt @@ -0,0 +1,21 @@ + + +Wartości wtórne: +012.001: X1,PG (polygon) PS1: 26.71 — +002.076: X1,PP (polygon) PS1: 26.71 — +012.002: X2,PG (polygon) PS1: 41.27 — +002.080: X2,PP (polygon) PS1: 41.27 — +012.003: X3,PG (polygon) PS1: 109.09 — +002.084: X3,PP (polygon) PS1: 109.09 — +012.005: R1,PG (polygon) PS1: 36.52 — +012.006: R1,PP (polygon) PS1: 22.89 — +012.007: R2,PG (polygon) PS1: 39.09 — +012.008: R2,PP (polygon) PS1: 25.45 — +012.009: R3,PG (polygon) PS1: 51.04 — +012.010: R3,PP (polygon) PS1: 37.40 — +012.013: €1 (polygon) PS1: 80 ° +012.014: €2 (polygon) PS1: 80 ° +012.015: €3 (polygon) PS1: 80 ° +012.037: Abs. value kG PS1: 0.36(współczynnik ziemnozwarciowy) + +strefa 1W=strefa 2 \ No newline at end of file diff --git a/Rejestracje/ZAM-DOB/Nastawy.txt b/Rejestracje/ZAM-DOB/Nastawy.txt new file mode 100644 index 0000000..c08a70c --- /dev/null +++ b/Rejestracje/ZAM-DOB/Nastawy.txt @@ -0,0 +1,34 @@ +Strona wtórna: + +1103 Measurement: Full Scale Voltage (100%) 220,0 kV +1104 Measurement: Full Scale Current (100%) 1200 A +1105 Line Angle 80 ° +1211 Angle of inclination, distance charact. 85 ° +1111 Line Length 96,5 km +1116 Zero seq. comp. factor RE/RL for Z1 1,38 +1117 Zero seq. comp. factor XE/XL for Z1 0,40 +1118 Zero seq. comp.factor RE/RL(> Z1) 1,38 +1119 Zero seq. comp.factor XE/XL(> Z1) 0,40 +1301 Operating mode Z1 Forward +1302 R(Z1), Resistance for ph-ph-faults 15,000 Ohm +1303 X(Z1), Reactance 18,580 Ohm +1304 RE(Z1), Resistance for ph-e faults 22,000 Ohm + +1351 Operating mode Z1B (overrreach zone) Forward +1352 R(Z1B), Resistance for ph-ph-faults 15,000 Ohm +1353 X(Z1B), Reactance 25,000 Ohm +1354 RE(Z1B), Resistance for ph-e faults 22,000 Ohm + +1312 R(Z2), Resistance for ph-ph-faults 18,000 Ohm +1313 X(Z2), Reactance 27,820 Ohm +1314 RE(Z2), Resistance for ph-e faults 24,000 Ohm + +1321 Operating mode Z3 Forward +1322 R(Z3), Resistance for ph-ph-faults 20,000 Ohm +1323 X(Z3), Reactance 43,090 Ohm +1324 RE(Z3), Resistance for ph-e faults 26,000 Ohm + +1331 Operating mode Z4 Forward +1332 R(Z4), Resistance for ph-ph-faults 20,000 Ohm +1333 X(Z4), Reactance 81,820 Ohm +1334 RE(Z4), Resistance for ph-e faults 26,000 Ohm \ No newline at end of file diff --git a/Rejestracje/rej.1 - wysokoopor OLS-WLA/Nastawy.txt b/Rejestracje/rej.1 - wysokoopor OLS-WLA/Nastawy.txt new file mode 100644 index 0000000..25a8089 --- /dev/null +++ b/Rejestracje/rej.1 - wysokoopor OLS-WLA/Nastawy.txt @@ -0,0 +1,33 @@ +Strona wtórna: + +1103 Measurement: Full Scale Voltage (100%) 220,0 kV +1104 Measurement: Full Scale Current (100%) 600 A +1105 Line Angle 80 ° +1211 Angle of inclination, distance charact. 80 ° +1111 Line Length 181,8 km +1116 Zero seq. comp. factor RE/RL for Z1 1,02 +1117 Zero seq. comp. factor XE/XL for Z1 0,60 +1118 Zero seq. comp.factor RE/RL(> Z1) 0,85 +1119 Zero seq. comp.factor XE/XL(> Z1) 0,50 + + + +1301 Operating mode Z1 Forward +1302 R(Z1), Resistance for ph-ph-faults 24,850 Ohm +1303 X(Z1), Reactance 16,585 Ohm +1304 RE(Z1), Resistance for ph-e faults 66,000 Ohm + +1351 Operating mode Z1B (overrreach zone) Forward +1352 R(Z1B), Resistance for ph-ph-faults 37,300 Ohm +1353 X(Z1B), Reactance 24,880 Ohm +1354 RE(Z1B), Resistance for ph-e faults 99,500 Ohm + +1311 Operating mode Z2 Forward +1312 R(Z2), Resistance for ph-ph-faults 37,300 Ohm +1313 X(Z2), Reactance 24,880 Ohm +1314 RE(Z2), Resistance for ph-e faults 99,500 Ohm + +1321 Operating mode Z3 Forward +1322 R(Z3), Resistance for ph-ph-faults 55,500 Ohm +1323 X(Z3), Reactance 37,100 Ohm +1324 RE(Z3), Resistance for ph-e faults 110,000 Ohm \ No newline at end of file diff --git a/Rejestracje/rej.2 - wysokoopor OLS-WLA/Nastawy.txt b/Rejestracje/rej.2 - wysokoopor OLS-WLA/Nastawy.txt new file mode 100644 index 0000000..25a8089 --- /dev/null +++ b/Rejestracje/rej.2 - wysokoopor OLS-WLA/Nastawy.txt @@ -0,0 +1,33 @@ +Strona wtórna: + +1103 Measurement: Full Scale Voltage (100%) 220,0 kV +1104 Measurement: Full Scale Current (100%) 600 A +1105 Line Angle 80 ° +1211 Angle of inclination, distance charact. 80 ° +1111 Line Length 181,8 km +1116 Zero seq. comp. factor RE/RL for Z1 1,02 +1117 Zero seq. comp. factor XE/XL for Z1 0,60 +1118 Zero seq. comp.factor RE/RL(> Z1) 0,85 +1119 Zero seq. comp.factor XE/XL(> Z1) 0,50 + + + +1301 Operating mode Z1 Forward +1302 R(Z1), Resistance for ph-ph-faults 24,850 Ohm +1303 X(Z1), Reactance 16,585 Ohm +1304 RE(Z1), Resistance for ph-e faults 66,000 Ohm + +1351 Operating mode Z1B (overrreach zone) Forward +1352 R(Z1B), Resistance for ph-ph-faults 37,300 Ohm +1353 X(Z1B), Reactance 24,880 Ohm +1354 RE(Z1B), Resistance for ph-e faults 99,500 Ohm + +1311 Operating mode Z2 Forward +1312 R(Z2), Resistance for ph-ph-faults 37,300 Ohm +1313 X(Z2), Reactance 24,880 Ohm +1314 RE(Z2), Resistance for ph-e faults 99,500 Ohm + +1321 Operating mode Z3 Forward +1322 R(Z3), Resistance for ph-ph-faults 55,500 Ohm +1323 X(Z3), Reactance 37,100 Ohm +1324 RE(Z3), Resistance for ph-e faults 110,000 Ohm \ No newline at end of file diff --git a/distance_algorithm.py b/distance_algorithm.py index 8b5fb83..4308201 100644 --- a/distance_algorithm.py +++ b/distance_algorithm.py @@ -11,12 +11,13 @@ class DistanceRelay: Algorytm zabezpieczenia odległościowego Implementacja charakterystyki wielokątnej (quadrilateral) """ - def __init__(self, Z_line_R=2.0, Z_line_X=8.0, line_angle=75.0): + def __init__(self, Z_line_R=2.0, Z_line_X=8.0, line_angle=75.0, settings=None, kierunek=0): # Impedancja linii (obliczona z danych) self.Z_line_R = Z_line_R self.Z_line_X = Z_line_X self.Z_line_mag = np.sqrt(Z_line_R**2 + Z_line_X**2) self.line_angle = line_angle + self.kierunek = kierunek # === Nastawy stref jako % impedancji linii === # Strefa 1 - 80% linii (natychmiastowa) @@ -33,6 +34,20 @@ class DistanceRelay: self.Z3_R = self.Z_line_R * 2.0 self.Z3_X = self.Z_line_X * 2.0 self.tZ3 = 600 # 600ms + + # Nadpisz nastawy, jeśli dostarczono plik konfiguracyjny + if settings: + print("\n--- Nadpisywanie nastaw z pliku konfiguracyjnego ---") + self.Z1_R = float(settings.get('Z1_R', self.Z1_R)) + self.Z1_X = float(settings.get('Z1_X', self.Z1_X)) + self.tZ1 = int(settings.get('tZ1', self.tZ1)) + self.Z2_R = float(settings.get('Z2_R', self.Z2_R)) + self.Z2_X = float(settings.get('Z2_X', self.Z2_X)) + self.tZ2 = int(settings.get('tZ2', self.tZ2)) + self.Z3_R = float(settings.get('Z3_R', self.Z3_R)) + self.Z3_X = float(settings.get('Z3_X', self.Z3_X)) + self.tZ3 = int(settings.get('tZ3', self.tZ3)) + print("--- Koniec nadpisywania nastaw ---\n") # Kąt charakterystyki (na podstawie kąta linii) self.angle_r1 = line_angle diff --git a/distance_algorithm_zimba.py b/distance_algorithm_zimba.py index 82a82fb..dfc35a6 100644 --- a/distance_algorithm_zimba.py +++ b/distance_algorithm_zimba.py @@ -37,7 +37,8 @@ class DistanceRelayZDistA: # Typ charakterystyki (0=poligonalna, 1=okragla) typ_zwarc=0, # Włącznik - wyl=False): + wyl=False, + settings=None): # Parametry linii self.Z_line_R = Z_line_R @@ -55,12 +56,7 @@ class DistanceRelayZDistA: self.fi2 = fi2 self.fi3 = fi3 self.fi4 = fi4 - - # Kąty pomocnicze - RnaS = math.pi / 180 - self.tanfi1 = math.tan(RnaS * fi1) - self.tanfi2 = math.tan(RnaS * fi2) - + # Współczynniki stref self.z1_reach = z1_reach self.z2_reach = z2_reach @@ -68,9 +64,6 @@ class DistanceRelayZDistA: self.z4_reach = z4_reach self.z5_reach = z5_reach - # Obliczanie parametrów stref - self._calc_zones() - # Opóźnienia stref [ms] self.t_z1 = t_z1 self.t_z2 = t_z2 @@ -85,36 +78,86 @@ class DistanceRelayZDistA: # Kierunek self.kierunek = kierunek - + # Kompensacja ziemnozwarciowa self.Kk1 = Kk1 self.Kk1_kat = Kk1_kat self.KkC = KkC self.KkC_kat = KkC_kat - - # Oblicz współczynniki kompensacji - self.ReK1 = 3 * Kk1 * math.cos(RnaS * -Kk1_kat) - self.ImK1 = 3 * Kk1 * math.sin(RnaS * -Kk1_kat) - self.ReKr = 3 * KkC * math.cos(RnaS * -KkC_kat) - self.ImKr = 3 * KkC * math.sin(RnaS * -KkC_kat) - + # Typ charakterystyki self.typ_zwarc = typ_zwarc # Stan wyłącznika self.wyl = wyl + # Nadpisz nastawy z pliku konfiguracyjnego + if settings: + print("\n--- Nadpisywanie nastaw ZDistA z pliku konfiguracyjnego ---") + self.kierunek = int(settings.get('Kierunek', self.kierunek)) + + # Strefy - obsługa zarówno zasięgu w % (np. z1_reach) jak i wartości bezwzględnych (np. Z1_R) + if 'Z1_R' in settings and self.Z_line_R > 0: + self.z1_reach = float(settings['Z1_R']) / self.Z_line_R + else: + self.z1_reach = float(settings.get('z1_reach', self.z1_reach)) + + if 'Z2_R' in settings and self.Z_line_R > 0: + self.z2_reach = float(settings['Z2_R']) / self.Z_line_R + else: + self.z2_reach = float(settings.get('z2_reach', self.z2_reach)) + + if 'Z3_R' in settings and self.Z_line_R > 0: + self.z3_reach = float(settings['Z3_R']) / self.Z_line_R + else: + self.z3_reach = float(settings.get('z3_reach', self.z3_reach)) + + if 'Z4_R' in settings and self.Z_line_R > 0: + self.z4_reach = float(settings['Z4_R']) / self.Z_line_R + else: + self.z4_reach = float(settings.get('z4_reach', self.z4_reach)) + + if 'Z5_R' in settings and self.Z_line_R > 0: + self.z5_reach = float(settings['Z5_R']) / self.Z_line_R + else: + self.z5_reach = float(settings.get('z5_reach', self.z5_reach)) + + self.t_z1 = int(settings.get('tZ1', self.t_z1)) + self.t_z2 = int(settings.get('tZ2', self.t_z2)) + self.t_z3 = int(settings.get('tZ3', self.t_z3)) + self.t_z4 = int(settings.get('tZ4', self.t_z4)) + self.t_z5 = int(settings.get('tZ5', self.t_z5)) + + self.Kk1 = float(settings.get('Kk1', self.Kk1)) + self.Kk1_kat = float(settings.get('Kk1_kat', self.Kk1_kat)) + + print("--- Koniec nadpisywania nastaw ZDistA ---\n") + + # Kąty pomocnicze + RnaS = math.pi / 180 + self.tanfi1 = math.tan(RnaS * self.fi1) + self.tanfi2 = math.tan(RnaS * self.fi2) + + # Obliczanie parametrów stref + self._calc_zones() + + # Oblicz współczynniki kompensacji + self.ReK1 = 3 * self.Kk1 * math.cos(RnaS * -self.Kk1_kat) + self.ImK1 = 3 * self.Kk1 * math.sin(RnaS * -self.Kk1_kat) + self.ReKr = 3 * self.KkC * math.cos(RnaS * -self.KkC_kat) + self.ImKr = 3 * self.KkC * math.sin(RnaS * -self.KkC_kat) + # Debug info print(f"Nastawy zabezpieczenia ZDistA:") print(f" Linia: R={Z_line_R:.2f} Ohm, X={Z_line_X:.2f} Ohm, |Z|={self.Z_line_mag:.2f} Ohm") print(f" Kąt linii: {line_angle:.1f} deg") - print(f" Strefa 1: {z1_reach*100:.0f}% (natychmiast)") - print(f" Strefa 2: {z2_reach*100:.0f}% ({t_z2}ms)") - print(f" Strefa 3: {z3_reach*100:.0f}% ({t_z3}ms)") - print(f" Strefa 4: {z4_reach*100:.0f}% ({t_z4}ms)") - print(f" Strefa 5: {z5_reach*100:.0f}% ({t_z5}ms)") - print(f" Kierunek: {kierunek} (0=bez, 1=do linii, 2=do szyn)") - print(f" Kompensacja Kk1: {Kk1}, kąt: {Kk1_kat} deg") + print(f" Strefa 1: {self.z1_reach*100:.0f}% ({self.t_z1}ms)") + print(f" Strefa 2: {self.z2_reach*100:.0f}% ({self.t_z2}ms)") + print(f" Strefa 3: {self.z3_reach*100:.0f}% ({self.t_z3}ms)") + print(f" Strefa 4: {self.z4_reach*100:.0f}% ({self.t_z4}ms)") + print(f" Strefa 5: {self.z5_reach*100:.0f}% ({self.t_z5}ms)") + print(f" Kierunek: {self.kierunek} (0=bez, 1=do linii, 2=do szyn)") + print(f" Kompensacja Kk1: {self.Kk1}, kąt: {self.Kk1_kat} deg") # Inicjalizacja stanów self.init_state() diff --git a/docx2txt.ps1 b/docx2txt.ps1 new file mode 100644 index 0000000..841dfee --- /dev/null +++ b/docx2txt.ps1 @@ -0,0 +1,5 @@ +$word = New-Object -ComObject Word.Application +$doc = $word.Documents.Open("nastawy.docx") +$doc.SaveAs([ref]"nastawy.txt", [ref]2) # 2 to format Text +$doc.Close() +$word.Quit() \ No newline at end of file diff --git a/docx2txt.py b/docx2txt.py new file mode 100644 index 0000000..50a80e5 --- /dev/null +++ b/docx2txt.py @@ -0,0 +1,68 @@ +import docx +import sys +import os + +def convert_docx_to_txt(input_path): + # Sprawdzenie czy plik istnieje + if not os.path.exists(input_path): + print(f"Błąd: Plik '{input_path}' nie istnieje.") + return + + # Sprawdzenie rozszerzenia pliku + if not input_path.lower().endswith('.docx'): + print(f"Info: Plik '{input_path}' nie jest plikiem .docx, pomijanie.") + return + + try: + # Wczytanie dokumentu + doc = docx.Document(input_path) + full_text = [] + + # Wyciąganie tekstu z każdego akapitu + for para in doc.paragraphs: + full_text.append(para.text) + + # Łączenie tekstu znakami nowej linii + result = "\n".join(full_text) + + # Tworzenie nazwy pliku wyjściowego (zmiana rozszerzenia na .txt) + output_path = os.path.splitext(input_path)[0] + ".txt" + + # Zapis do pliku tekstowego z kodowaniem UTF-8 + with open(output_path, "w", encoding="utf-8") as f: + f.write(result) + + print(f"Sukces! Tekst został zapisany w: {output_path}") + + except Exception as e: + print(f"Wystąpił błąd podczas konwersji pliku {input_path}: {e}") + +def process_directory(directory_path): + for root, dirs, files in os.walk(directory_path): + for file in files: + if file.lower().endswith(('.docx', '.doc')): + file_path = os.path.join(root, file) + if file.lower().endswith('.doc'): + print(f"Info: Konwersja plików .doc nie jest wspierana w tej wersji. Plik: {file_path}") + else: + convert_docx_to_txt(file_path) + +if __name__ == "__main__": + # Obsługa argumentu z linii poleceń + if len(sys.argv) > 1: + path_arg = sys.argv[1] + if os.path.isdir(path_arg): + process_directory(path_arg) + elif os.path.isfile(path_arg): + convert_docx_to_txt(path_arg) + else: + print(f"Błąd: Podana ścieżka '{path_arg}' nie jest ani plikiem, ani katalogiem.") + else: + path_input = input("Podaj nazwę pliku .docx lub ścieżkę do katalogu: ") + if os.path.isdir(path_input): + process_directory(path_input) + elif os.path.isfile(path_input): + convert_docx_to_txt(path_input) + else: + print(f"Błąd: Podana ścieżka '{path_input}' nie jest ani plikiem, ani katalogiem.") + diff --git a/tester.py b/tester.py index 5227b4d..3b83ff6 100644 --- a/tester.py +++ b/tester.py @@ -5,6 +5,7 @@ import matplotlib.pyplot as plt from comtrade import Comtrade import math import sys +import os # ============================================================ # KONFIGURACJA - WYBIERZ ALGORYTM @@ -30,28 +31,77 @@ else: print(f"=== Używany algorytm: {ALGORITHM_DESC} ===") # ============================================================ -# Przekładnia prądowa (domyślna) - zmień tę wartość dla swoich danych -# Dla rejestracji MOR-MIL: przekładnia 1000/5 = 200 -# Dane z rejestracji są w wartościach PIERWOTNYCH -# Algorytm oczekuje wartości WTÓRNYCH -# Konwersja: dane_wtórne = dane_pierwotne / PRZEKLADNIA -PRZEKLADNIA = 200.0 # <-- zmień tę wartość (np. 200 dla przekładni 1000/5) -PRZEKLADNIA_NAPIECIA = 1100.0 # 110kV/100V = 1100 -# Przekładnia efektywna dla Z = U/I -# Z_wt = (U_pierw/n_U) / (I_pierw/n_I) = Z_pierw * n_I/n_U -# Z_wt = Z_pierw / (PRZEKLADNIA_NAPIECIA / PRZEKLADNIA) -PRZEKLADNIA_EFF = PRZEKLADNIA_NAPIECIA / PRZEKLADNIA # = 1100/200 = 5.5 +def parse_settings(directory): + """ + Parsuje pliki Nastawy.txt lub nastawy stref.txt w podanym katalogu. + """ + settings = {} + for filename in ["Nastawy.txt", "nastawy stref.txt"]: + filepath = os.path.join(directory, filename) + if os.path.exists(filepath): + print(f"Znaleziono plik nastaw: {filepath}") + try: + with open(filepath, 'r', encoding='utf-8') as f: + content = f.readlines() + except UnicodeDecodeError: + with open(filepath, 'r', encoding='cp1250') as f: + content = f.readlines() -# Kierunek zabezpieczenia: + for line in content: + line = line.strip() + if not line or line.startswith('#'): + continue + + separator = None + if '=' in line: + separator = '=' + elif ':' in line: + separator = ':' + + if separator: + key, value = line.split(separator, 1) + key = key.strip() + value = value.strip() + + try: + settings[key] = float(value) + except ValueError: + settings[key] = value + break + + if settings: + print("Wczytano następujące nastawy:") + for key, value in settings.items(): + print(f" {key}: {value}") + + return settings -# Kierunek zabezpieczenia: 0=bez, 1=do linii, 2=do szyn -KIERUNEK = 0 # <-- zmień tę wartość (0=bez, 1=do linii, 2=do szyn) +# Przekładnia prądowa (domyślna) +PRZEKLADNIA = 200.0 +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] # Nazwa pliku bez rozszerzenia + base_name = sys.argv[1] else: - base_name = "zwarcie_testowe" # Domyslna nazwa + base_name = "zwarcie_testowe" + +# Wczytywanie nastaw z pliku +directory = os.path.dirname(os.path.abspath(base_name)) +settings = parse_settings(directory) + +# Nadpisz domyślne wartości, jeśli istnieją w pliku nastaw +PRZEKLADNIA = float(settings.get('Przekladnia pradowa', PRZEKLADNIA)) +PRZEKLADNIA_NAPIECIA = float(settings.get('Przekladnia napieciowa', PRZEKLADNIA_NAPIECIA)) +KIERUNEK = int(settings.get('Kierunek', KIERUNEK)) + +print(f"\nUżywane parametry globalne:") +print(f" Przekładnia prądowa: {PRZEKLADNIA}") +print(f" Przekładnia napięciowa: {PRZEKLADNIA_NAPIECIA}") +print(f" Kierunek: {KIERUNEK}\n") + +PRZEKLADNIA_EFF = PRZEKLADNIA_NAPIECIA / PRZEKLADNIA cfg_file = f"{base_name}.cfg" dat_file = f"{base_name}.dat" @@ -84,92 +134,63 @@ if num_analog < 6: print(f"BŁĄD: Za mało kanałów analogowych (wymagane minimum 6)") sys.exit(1) -# Wyciagniecie danych (przyjmujemy indeksy kanałów analogowych) +# Wyciagniecie danych t = np.array(rec.time) -Fs = rec.cfg.sample_rates[0][0] # Czestotliwosc probkowania -f_nom = 50.0 # Czestotliwosc sieci -N = int(Fs / f_nom) # Liczba probek na okres +Fs = rec.cfg.sample_rates[0][0] +f_nom = 50.0 +N = int(Fs / f_nom) if f_nom > 0 else 20 if N < 1: - N = 1 # Zabezpieczenie + N = 1 print(f" Czestotliwosc probkowania: {Fs} Hz") print(f" Liczba probek na okres: {N}") -# Zabezpieczenie przed index error -max_analog = min(6, num_analog) - -# Pobierz tyle ile mamy -# Indeksy dla standardowych rejestracji (6 kanałów): -# 0:I_L1, 1:I_L2, 2:I_L3, 3:U_L1, 4:U_L2, 5:U_L3 -# Dla MOR-MIL (22 kanały): -# 0:I_L1, 1:I_L2, 2:I_L3, 3:3Io, 4:U_L1, 5:U_L2, 6:U_L3 i1_raw = np.array(rec.analog[0]) i2_raw = np.array(rec.analog[1]) i3_raw = np.array(rec.analog[2]) -# Wykryj typ rejestracji na podstawie liczby kanałów if num_analog >= 7: - # Rejestracja z 7+ kanałami - użyj indeksów dla U_L1, U_L2, U_L3 u1_raw = np.array(rec.analog[4]) u2_raw = np.array(rec.analog[5]) u3_raw = np.array(rec.analog[6]) else: - # Standardowa rejestracja 6 kanałowa u1_raw = np.array(rec.analog[3]) u2_raw = np.array(rec.analog[4]) u3_raw = np.array(rec.analog[5]) # === ANALIZA DANYCH - wyznaczenie impedancji przed zwarciem === -print(f" Czestotliwosc probkowania: {Fs} Hz") -print(f" Liczba probek na okres: {N}") -print(f" Liczba probek: {len(t)}") -print(f" Czas trwania: {t[-1]:.3f} s") - -# 3. Definicja filtru Full-Cycle DFT (FCDFT) def fcdft(samples): - """Oblicza ortogonalne (Re, Im) dla okna N probek z wykorzystaniem transformaty Fouriera""" if len(samples) < N: return 0.0, 0.0 k = np.arange(N) cos_wave = np.cos(2 * np.pi * k / N) sin_wave = np.sin(2 * np.pi * k / N) - re = (2.0 / N) * np.sum(samples * cos_wave) im = -(2.0 / N) * np.sum(samples * sin_wave) return re, im -# Funkcja pomocnicza do obliczania impedancji def calculate_impedance_from_raw(u_raw, i_raw, idx): - """Oblicza impedancję dla próbki idx""" - if idx < N: - return 0, 0 + if idx < N: return 0, 0 window_i = i_raw[idx-N:idx] window_u = u_raw[idx-N:idx] i_re, i_im = fcdft(window_i) u_re, u_im = fcdft(window_u) + i_re /= PRZEKLADNIA + i_im /= PRZEKLADNIA + u_re /= PRZEKLADNIA_NAPIECIA + u_im /= PRZEKLADNIA_NAPIECIA i_mag_sq = i_re**2 + i_im**2 - if i_mag_sq < 1e-9: - return 0, 0 - # Z_wt = Z_pierw / (n_U / n_I) = Z_pierw / PRZEKLADNIA_EFF - i_re = i_re / PRZEKLADNIA - i_im = i_im / PRZEKLADNIA - u_re = u_re / PRZEKLADNIA_NAPIECIA - u_im = u_im / PRZEKLADNIA_NAPIECIA - i_mag_sq = i_re**2 + i_im**2 + if i_mag_sq < 1e-9: return 0, 0 z_re = (u_re * i_re + u_im * i_im) / i_mag_sq z_x = (u_im * i_re - u_re * i_im) / i_mag_sq return z_re, z_x -# Oblicz impedancję przed zwarciem (próbki 10-100) pre_fault_start = 10 -pre_fault_end = 100 - -# Srednia impedancja przed zwarciem +pre_fault_end = min(100, len(t) - N) z_r_list, z_x_list = [], [] - -# Poprawka: krok = N, ale N nie moze byc 0 step = max(N, 1) -for idx in range(pre_fault_start, min(pre_fault_end, len(t)), step): + +for idx in range(pre_fault_start, pre_fault_end, step): z_r, z_x = calculate_impedance_from_raw(u1_raw, i1_raw, idx) if z_r > 0: z_r_list.append(z_r) @@ -179,25 +200,19 @@ if z_r_list: Z_line_R = np.median(z_r_list) Z_line_X = np.median(z_x_list) Z_line_mag = np.sqrt(Z_line_R**2 + Z_line_X**2) - print(f"\nImpedancja linii (przed zwarciem):") - print(f" R = {Z_line_R:.2f} Ohm") - print(f" X = {Z_line_X:.2f} Ohm") - print(f" |Z| = {Z_line_mag:.2f} Ohm") - - # Kat linii line_angle = np.degrees(np.arctan2(Z_line_X, Z_line_R)) - print(f" Kat = {line_angle:.1f} deg") + print(f"\nImpedancja linii (przed zwarciem): R={Z_line_R:.2f}, X={Z_line_X:.2f}, |Z|={Z_line_mag:.2f} Ohm, Kat={line_angle:.1f} deg") else: - # Wartosci domyslne jesli nie mozna obliczyc Z_line_R = 2.0 Z_line_X = 8.0 Z_line_mag = np.sqrt(Z_line_R**2 + Z_line_X**2) line_angle = 75.0 print("Nie mozna wyznaczyc impedancji linii, uzywam wartosci domyslnych") -# Utworzenie relay z automatycznie dobranymi nastawami -relay = DistanceRelay(Z_line_R=Z_line_R, Z_line_X=Z_line_X, line_angle=line_angle, kierunek=KIERUNEK) +# 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) @@ -249,9 +264,7 @@ for i in range(N, len(t)): u2_re, u2_im = fcdft(window_u2) u3_re, u3_im = fcdft(window_u3) - # Konwersja do wartości wtórnych (dla calculate_impedance_from_raw) - - # Tworzenie liczb zespolonych dla łatwiejszej matematyki symetrycznej + # Tworzenie liczb zespolonych I1_cpx = complex(i1_re, i1_im) I2_cpx = complex(i2_re, i2_im) I3_cpx = complex(i3_re, i3_im) @@ -262,36 +275,18 @@ for i in range(N, len(t)): # 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 - I2pr_cpx = (I1_cpx + a2 * I2_cpx + a * 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 - U2pr_cpx = (U1_cpx + a2 * U2_cpx + a * 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_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) - 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 - ) - - # Rejestracja wyniku trip_history_L1.append(trip_l1) trip_history_L2.append(trip_l2) trip_history_L3.append(trip_l3) - # Obliczanie impedancji Z = U / I z1_r, z1_x = calculate_impedance(U1_cpx, I1_cpx) z2_r, z2_x = calculate_impedance(U2_cpx, I2_cpx) z3_r, z3_x = calculate_impedance(U3_cpx, I3_cpx) @@ -305,7 +300,7 @@ for i in range(N, len(t)): # 5. Rysowanie wyników 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') @@ -319,44 +314,28 @@ plt.grid(True) # Charakterystyka R-X plt.subplot(3, 2, 2) -# Rysuj strefy angle_rad = math.radians(relay.angle_r1) - # Strefa 1 -r1_vals = np.linspace(0, relay.Z1_R, 50) -x1_upper = relay.Z1_X * (1 - r1_vals / relay.Z1_R * math.tan(math.radians(15))) -x1_lower = -relay.Z1_X * (1 - r1_vals / relay.Z1_R * math.tan(math.radians(15))) -plt.fill_between(r1_vals, x1_lower, x1_upper, alpha=0.2, color='green', label='Strefa 1') - +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 -r2_vals = np.linspace(0, relay.Z2_R, 50) -x2_upper = relay.Z2_X * (1 - r2_vals / relay.Z2_R * math.tan(math.radians(15))) -x2_lower = -relay.Z2_X * (1 - r2_vals / relay.Z2_R * math.tan(math.radians(15))) -plt.fill_between(r2_vals, x2_lower, x2_upper, alpha=0.15, color='yellow', label='Strefa 2') - +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 -r3_vals = np.linspace(0, relay.Z3_R, 50) -x3_upper = relay.Z3_X * (1 - r3_vals / relay.Z3_R * math.tan(math.radians(15))) -x3_lower = -relay.Z3_X * (1 - r3_vals / relay.Z3_R * math.tan(math.radians(15))) -plt.fill_between(r3_vals, x3_lower, x3_upper, alpha=0.1, color='red', label='Strefa 3') +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') -# Linia impedancji linii 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') - -# Trajektorie impedancji -# Filtruj tylko próbki podczas zwarcia (użyj indeksu gdzie napięcie spada) -fault_indices = np.where(np.array(z1_x_history) < 1.0)[0] -if len(fault_indices) > 0: - plt.plot(np.array(z1_r_history)[fault_indices], np.array(z1_x_history)[fault_indices], - 'b-', linewidth=2, label='Z_L1 (zwarcie)') 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') -plt.xlim(-1, relay.Z3_R * 1.2) -plt.ylim(-relay.Z3_X * 1.2, relay.Z3_X * 1.2) +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) plt.xlabel('R [Ohm]') plt.ylabel('X [Ohm]') plt.title('Charakterystyka R-X zabezpieczenia odleglosciowego') @@ -392,14 +371,14 @@ plt.grid(True) # Wyjscia zabezpieczenia plt.subplot(3, 2, 5) -max_val = max(max(i1_raw), max(i2_raw), max(i3_raw)) +max_val = max(max(i1_raw), max(i2_raw), max(i3_raw)) if any(i1_raw) else 1 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.title('Wynik testu algorytmu ZDistA') +plt.title(f'Wynik testu algorytmu {ALGORITHM_NAME}') plt.xlabel('Czas [s]') plt.ylabel('Wartosc') plt.legend() @@ -413,12 +392,15 @@ 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') -z1_reach = np.sqrt(relay.Z1_R**2 + relay.Z1_X**2) -z2_reach = np.sqrt(relay.Z2_R**2 + relay.Z2_X**2) -z3_reach = np.sqrt(relay.Z3_R**2 + relay.Z3_X**2) -plt.axhline(y=z1_reach, color='green', linestyle='--', label='Z1 reach') -plt.axhline(y=z2_reach, color='orange', linestyle='--', label='Z2 reach') -plt.axhline(y=z3_reach, color='red', linestyle='--', label='Z3 reach') +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]') @@ -426,180 +408,62 @@ plt.legend() plt.grid(True) plt.tight_layout() -plt.savefig(f'test_result_{ALGORITHM_NAME}.png') -print(f"Wynik zapisany do test_result_{ALGORITHM_NAME}.png") - -# Generowanie pliku rezultat.md -def detect_fault_and_generate_report(t, trip_history, z_r_history, z_x_history, - i1_raw, i2_raw, i3_raw, - u1_raw, u2_raw, u3_raw, - relay, base_name): - """Wykrywa zwarcie i generuje raport""" - - # Znajdz indeks pierwszego trip - fault_idx = None - for i, trip in enumerate(trip_history): - if trip > 0: - fault_idx = i - break - - # Przygotuj dane do raportu - report = [] - report.append(f"# Wynik analizy zabezpieczenia odleglosciowego") - report.append(f"") - report.append(f"## Rejestracja: {base_name}") - report.append(f"") - report.append(f"## Parametry zabezpieczenia") - report.append(f"- Impedancja linii: {relay.Z_line_mag:.2f} Ohm") - report.append(f"- Kat linii: {relay.line_angle:.1f} deg") - report.append(f"- Strefa 1: R={relay.Z1_R:.2f} Ohm, X={relay.Z1_X:.2f} Ohm (natychmiast)") - report.append(f"- Strefa 2: R={relay.Z2_R:.2f} Ohm, X={relay.Z2_X:.2f} Ohm (300ms)") - report.append(f"- Strefa 3: R={relay.Z3_R:.2f} Ohm, X={relay.Z3_X:.2f} Ohm (600ms)") - report.append(f"") - - if fault_idx is not None: - # Zwarcie wykryte - fault_time = t[N + fault_idx] - report.append(f"## Zwarcie wykryte") - report.append(f"- Czas zwarcia: {fault_time*1000:.2f} ms") - report.append(f"") - - # Ktora faza - if trip_history[fault_idx] > 0: - report.append(f"- Faza: L1 (trip)") - report.append(f"") - - # Wartosci podczas zwarcia - z_r_fault = z_r_history[fault_idx] - z_x_fault = z_x_history[fault_idx] - z_mag_fault = np.sqrt(z_r_fault**2 + z_x_fault**2) - - report.append(f"## Wartosci podczas zwarcia") - report.append(f"- Impedancja Z: {z_mag_fault:.2f} Ohm") - report.append(f"- R: {z_r_fault:.2f} Ohm") - report.append(f"- X: {z_x_fault:.2f} Ohm") - report.append(f"") - - # Prad i napiecie - i1_fault = i1_raw[N + fault_idx] - u1_fault = u1_raw[N + fault_idx] - report.append(f"- Prad L1: {i1_fault:.2f} A") - report.append(f"- Napiecie L1: {u1_fault:.2f} V") - else: - report.append(f"## Brak zwarcia") - report.append(f"- Nie wykryto zadzialania zabezpieczenia") - report.append(f"") - - # Podsumowanie - any_trip = any(t > 0 for t in trip_history) - report.append(f"## Podsumowanie") - report.append(f"- Trip L1: {'TAK' if any(trip_history) else 'NIE'}") - report.append(f"") - - # Zapisz raport - report_content = "\n".join(report) - with open(f'rezultat_{ALGORITHM_NAME}.md', 'w', encoding='utf-8') as f: - f.write(report_content) - - print(f"Raport zapisany do rezultat_{ALGORITHM_NAME}.md") - return fault_idx is not None - -# Generuj raport -has_fault = detect_fault_and_generate_report( - t, - [trip_history_L1[i] + trip_history_L2[i] + trip_history_L3[i] for i in range(len(trip_history_L1))], - z1_r_history, z1_x_history, - i1_raw, i2_raw, i3_raw, - u1_raw, u2_raw, u3_raw, - relay, base_name -) +output_filename = f"{os.path.splitext(base_name)[0]}_result.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, z2_r_history, z2_x_history, z3_r_history, z3_x_history, - Z_line_R, Z_line_X, relay): + z1_r_history, z1_x_history, relay, base_name): """Generuje plik rezultat.md z informacjami o wykryciu zwarcia""" - - md_content = [] - md_content.append("# Wynik analizy zabezpieczenia odleglosciowego") - md_content.append("") - md_content.append(f"## Parametry zabezpieczenia") - md_content.append(f"- Impedancja linii: R={Z_line_R:.2f} Ohm, X={Z_line_X:.2f} Ohm") - md_content.append(f"- Kat linii: {relay.angle_r1:.1f} st.") - md_content.append(f"- Strefa 1: R={relay.Z1_R:.2f} Ohm, X={relay.Z1_X:.2f} Ohm (natychmiast)") - md_content.append(f"- Strefa 2: R={relay.Z2_R:.2f} Ohm, X={relay.Z2_X:.2f} Ohm (300ms)") - md_content.append(f"- Strefa 3: R={relay.Z3_R:.2f} Ohm, X={relay.Z3_X:.2f} Ohm (600ms)") + 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'): + 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)") md_content.append("") - # Sprawdz czy bylo zwarcie trip_any = any(trip_history_L1) or any(trip_history_L2) or any(trip_history_L3) if trip_any: - md_content.append("## Wykrycie zwarcia") - md_content.append("") - md_content.append("**Zwarcie wykryte - zadzialanie zabezpieczenia**") - md_content.append("") - - # Znajdz czas pierwszego zadzialania - fault_time = None - fault_phase = None - + 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] - if t1: - fault_phase = "L1-E" - elif t2: - fault_phase = "L2-E" - elif t3: - fault_phase = "L3-E" + fault_idx = i + if t1: fault_phase = "L1" + elif t2: fault_phase = "L2" + else: fault_phase = "L3" break + + 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_mag = math.sqrt(z_r**2 + z_x**2) + z_angle = math.degrees(math.atan2(z_x, z_r)) - if fault_time is not None: - md_content.append(f"- Czas wykrycia: {fault_time:.4f} s") - md_content.append(f"- Faza: {fault_phase}") - md_content.append("") - - # Wartosci impedancji w momencie zwarcia - idx = int((fault_time - t[N]) * Fs) if Fs > 0 else 0 - idx = max(0, min(idx, len(z1_r_history) - 1)) - - if fault_phase == "L1-E": - z_r, z_x = z1_r_history[idx], z1_x_history[idx] - elif fault_phase == "L2-E": - z_r, z_x = z2_r_history[idx], z2_x_history[idx] - else: - z_r, z_x = z3_r_history[idx], z3_x_history[idx] - - z_mag = math.sqrt(z_r**2 + z_x**2) if z_r or z_x else 0 - z_angle = math.degrees(math.atan2(z_x, z_r)) if z_r else 0 - - md_content.append("## Wartosci w momencie wykrycia zwarcia") - md_content.append(f"- R = {z_r:.4f} Ohm") - md_content.append(f"- X = {z_x:.4f} Ohm") - md_content.append(f"- |Z| = {z_mag:.4f} Ohm") - md_content.append(f"- Kat Z = {z_angle:.2f} st.") + md_content.append("## Wartości w momencie zwarcia") + md_content.append(f"- R = {z_r:.4f} Ohm, X = {z_x:.4f} Ohm") + md_content.append(f"- |Z| = {z_mag:.4f} Ohm, Kat Z = {z_angle:.2f} st.") else: - md_content.append("## Wykrycie zwarcia") - md_content.append("") - md_content.append("**Brak wykrycia zwarcia**") - md_content.append("") - md_content.append("Zabezpieczenie nie zadzialalo w okresie rejestracji.") + md_content.append("## Wykrycie zwarcia: NIE") + + report_filename = f"{os.path.splitext(base_name)[0]}_rezultat.md" + with open(report_filename, 'w', encoding='utf-8') as f: + f.write("\n".join(md_content)) + print(f"Raport zapisany do {report_filename}") - md_content.append("") - md_content.append("---") - md_content.append("*Wygenerowano automatycznie przez tester zabezpieczenia odleglosciowego*") +generate_result_md(t, trip_history_L1, trip_history_L2, trip_history_L3, + z1_r_history, z1_x_history, relay, base_name) - return "\n".join(md_content) +print("Analiza zakończona.") -# Generuj i zapisz rezultat.md -result_md = generate_result_md( - t, trip_history_L1, trip_history_L2, trip_history_L3, - z1_r_history, z1_x_history, z2_r_history, z2_x_history, z3_r_history, z3_x_history, - Z_line_R, Z_line_X, relay -) - -with open(f'rezultat_{ALGORITHM_NAME}.md', 'w', encoding='utf-8') as f: - f.write(result_md) - -print(f"Zapisano rezultat_{ALGORITHM_NAME}.md")