[PYTHON] Schreiben wir einen einfachen Gleichstromlöser

Einführung

Dieser Artikel ist der 23. Tag von Competitive Programming (2) Adventskalender 2019. Ich habe einen Gleichstromlöser geschrieben, der berechnet, wie viele Ströme und kombinierte Widerstände durch jeden Widerstand in einem Stromkreis fließen, in dem mehrere elektrische Widerstände in Reihe oder parallel geschaltet sind. (Obwohl es in der aktuellen Wettbewerbsprogrammierung selten gefragt wird, ist es in Ordnung, da es sich um eine Grafik und eine Berechnung handelt.)

Gleichstromlöser

Angenommen, Sie möchten den kombinierten Widerstand der folgenden Widerstandsschaltung kennen, die durch Kombinieren von Widerständen mit jedem Widerstand von 1,0 Ω hergestellt wird. スライド2.PNG Obwohl es einfach aussieht, ist es sehr schwierig, es tatsächlich zu berechnen. In diesem Artikel wird das Programm, das dies berechnet, als Gleichstromlöser bezeichnet. Als Voraussetzung

Wie löst man

Durch Lösen der nach dem Kirchhofschen Gesetz von Spannung und Strom formulierten Gleichungen kann das Potential jedes Knotens und der Strom jedes Widerstands erhalten werden. Daher ist es notwendig, die Variablen entsprechend zu definieren. スライド3.PNG

Formulieren Sie das Teil in rot als Variable. Da es schließlich auf eine lineare Gleichung reduziert wird, wird das minimale unbekannte Element zu einer Variablen gemacht. In Physikklassen und Prüfungen an Gymnasien wird die aktuelle Berechnung eines Systems mit vielen unbekannten Variablen selten abgefragt. Wenn jedoch viele Variablen vorhanden sind, muss eine Implementierung erstellt werden, in der festgelegt wird, wie viele linear unabhängige Gleichungen formuliert werden sollen. Ich werde. Indem wir den Anschluss von der Stromversorgung als Variable betrachten, können wir die notwendigen und ausreichenden Gleichungen wie folgt formulieren. スライド4.PNG

Sie erhalten die gleiche Anzahl von Ausdrücken wie die Anzahl der definierten Variablen. Der Punkt ist, dass die Formel, die ohne Anschließen der Stromversorgung erhalten wird, nicht linear unabhängig wird und dass die linear unabhängige Formel erhalten werden kann, unabhängig davon, wie viele Knoten, die die Stromversorgung verbinden, erhöht sind (es sei denn, die Stromversorgungen sind kurzgeschlossen). .. (Kann induktiv nachgewiesen werden (sollte))

Beispiel

Zum Beispiel bei einem so einfachen Serienwiderstand スライド6.PNG

Insgesamt 7 können wie folgt formuliert werden. スライド5.PNG

In Python implementiert

Da es einen linearen Löser gibt, der leicht verwendet werden kann, werde ich ihn in Python schreiben. Beim Schreiben eines Lösers gilt die folgende Richtlinie

Die Quelle ist GitHub. Es ist fast so

DCsolver.py


# coding: utf-8
import sys
import time
import numpy as np
from collections import deque

class DCsolver:

    def __init__(self, nodenum):
        #Löser
        self.linear_solver = np.linalg.solve

        #Die Anzahl der Knoten ist festgelegt. Aktualisieren Sie die Anzahl der Widerstände und die Anzahl der Vorspannungsanschlüsse
        self.n_node = nodenum
        self.n_res = 0
        self.n_bias_probe = 0

        #Widerstand: Startpunkt, Endpunkt, Widerstandswert
        self.res_from = []
        self.res_to = []
        self.res_value = []

        #Stromversorgung: Zugriff nach Namen für die virtuelle Verbindung
        self.bias_name = []
        self.bias_level = []
        self.bias_index = dict() # dict<string,int>

        # Bias supplied (-1: not biased)
        self.biased = [-1] * self.n_node
        #Bestimmen Sie bei der Berechnung, auf welchen Knoten jede Vorspannung prüft
        self.bias_from = None
        self.bias_to = None

        #Lineare Gleichung A.*X=Löse V.
        self._A = None
        self._V = None
        self._X = None 

        # result
        self.node_voltage = None
        self.node_current = None
        self.bias_total_current = None
        self.bias_current_per_node = None
        self.res_current = None

    def add_resistance(self, node_from, node_to, res_value):
        # node_von Knoten zu Knoten_Widerstandswert res bis to_Schließen Sie den Wertwiderstand an
        #Definieren Sie die Richtung des Stroms in dieser Richtung
        assert res_value > 0 , "inhibit res_value <= 0"
        self.res_from.append(node_from)
        self.res_to.append(node_to)
        self.res_value.append(res_value)
        self.n_res += 1

    def define_bias(self, bias_name, bias_level=0.0):
        if bias_name in self.bias_index:
            idx = self.bias_index[bias_name]
            self.bias_level[idx] = bias_level
        else :
            idx = len(self.bias_name)
            self.bias_index[bias_name] = idx
            self.bias_name.append(bias_name)
            self.bias_level.append(bias_level)
    
    def supply_bias_to_node(self, bias_name, node_to):
        #Verhindern Sie, dass mehrere Verzerrungen auf einen Knoten zugreifen, um die neuesten Einstellungen widerzuspiegeln
        assert bias_name in self.bias_index, \
            "{0} is not defined, please define before supply".format(bias_name)
        idx = self.bias_index[bias_name]
        #Warnen, wenn bereits eine Vorspannung vorhanden ist.
        if self.biased[node_to] != -1:
            print("bias on node:{0} is changed: {1} --> {2} ".format(
                node_to, self.bias_name[self.bias_index[self.biased[node_to]]], bias_name
            ))
            self.biased[node_to] = idx
        else :
            self.biased[node_to] = idx
            self.n_bias_probe += 1

    def _create_matrix(self):
        # (A, V)Definieren
        nv = self.n_node
        nr = self.n_res
        nb = self.n_bias_probe

        #Listen Sie die endgültigen Einstellungen für die Vorspannungsbedingungen auf und indizieren Sie sie
        self.bias_from = []
        self.bias_to = []
        for i in range(nv):
            if self.biased[i] != -1:
                self.bias_from.append(self.biased[i])
                self.bias_to.append(i)
        assert nb == len(self.bias_from)

        #Matrixgröße=Anzahl der Knoten+Anzahl der Widerstände+Anzahl der Vorspannungspfade
        #Unbekannte Variable
        #  [0...nv-1]:Knoten ich Potenzial
        #  [nv...nv+nr-1] :Widerstandsstrom
        #  [nv+nr...n-1] :Vorspannungspfadstrom
        n = nv + nr + nb
        mat = np.zeros((n,n))
        vec = np.zeros(n)

        # Kirchhoff's Aktuelles Gesetz (Die Summe aus Outgo und Einkommen jedes Knotens ist 0)
        #Linie i([0...nv-1])Gleichung ist die Summe der Ströme für Knoten i
        #Der durch den Widerstand j fließende Strom kommt von[j]Geh raus zu[j]Als Einkommen gezählt
        for j in range(nr):
            mat[self.res_from[j]][nv + j] = 1
            mat[self.res_to[j]][nv + j] = -1

        # Kirchhoff's Spannungsgesetz (Der Spannungsabfall jedes Widerstands ist eine Potentialdifferenz)
        #  nv+Zeile j([nv...nv+nr-1])Gleichung ist die Summe der Ströme für den Widerstand j
        for j in range(nr):
            mat[nv + j][self.res_from[j]] = 1
            mat[nv + j][self.res_to[j]] = -1
            mat[nv + j][nv + j] = -self.res_value[j]
        
        #Bias-Definitionsformel
        #  bias_from[i]Potenzial ist Voreingenommenheit_level['bias']Ist fest auf. (Zum KVL-Teil hinzugefügt)
        #  bias_to[i]Strom aus der Stromversorgung fließt in(Zum KCL-Teil hinzugefügt)
        for j in range(len(self.bias_from)):
            mat[nv + nr + j][self.bias_to[j]] = 1
            vec[nv + nr + j] = self.bias_level[self.bias_from[j]]
            mat[self.bias_to[j]][nv + nr + j] = -1
        
        #Suchen Sie nach Knoten, die nicht mit Bias verbunden sind (schwebende Knoten sind nicht zulässig).
        self.check_connention()
        return mat, vec

    def check_connention(self):
        E = [[] for i in range(self.n_node)]
        for i in range(self.n_res):
            E[self.res_from[i]].append(self.res_to[i])
            E[self.res_to[i]].append(self.res_from[i])
        
        q = deque()
        vis = [False] * self.n_node
        for node in self.bias_to:
            q.append(node)
        while(len(q) > 0):
            now = q.popleft()
            vis[now] = True
            for nxt in E[now]:
                if not vis[nxt]:
                    q.append(nxt)
        
        floating_node = []
        for node in range(len(vis)):
            if not vis[node]:
                floating_node.append(node)
        if len(floating_node) != 0:
            print("Some floating node(s) exist:\nnode(s):\n{0}\n--> Aborted".format(floating_node))
            sys.exit(0)

            

    
    def solve(self):
        A, V = self._create_matrix()
        X = self.linear_solver(A, V)
        X = np.array(X)
        self._A = A
        self._V = V
        self._X = X

        self.node_voltage = X[0:self.n_node]
        self.res_current = X[self.n_node: self.n_node + self.n_res]        
        self.bias_current_per_node = dict()
        self.bias_total_current = dict()

        for bname in self.bias_name:
            self.bias_current_per_node[bname] = []
            self.bias_total_current[bname] = 0.0
        for i in range(self.n_bias_probe):
            bname = self.bias_name[self.bias_from[i]]
            self.bias_current_per_node[bname].append( (self.bias_to[i], X[self.n_node + self.n_res + i]) )
            self.bias_total_current[bname] += X[self.n_node + self.n_res + i]
        
        #Der Knotenstrom berechnet nur den Ausgang ((Σ)|outgo|+Σ|income|)/Berechnen Sie mit 2)
        self.node_current = np.zeros(self.n_node)
        for i in range(self.n_res):
            self.node_current[self.res_from[i]] += np.abs(self.res_current[i])
            self.node_current[self.res_to[i]] += np.abs(self.res_current[i])
        for bname in self.bias_current_per_node:
            for node, cur in self.bias_current_per_node[bname]:
                self.node_current[node] += np.abs(cur)
        self.node_current /= 2.0

    def print_result_summary(self, showCoef = False):
        if showCoef:
            print('A',self._A)
            print('V',self._V)
            print('X',self._X)
        print('node_voltage\n:{0}\n'.format(self.node_voltage))
        print('res_current\n:{0}\n'.format(self.res_current))
        print('node_cur\n:{0}\n'.format(self.node_current))
        print('bias_cur\n:{0}\n'.format(self.bias_total_current))
        print('bias_cur_per_node\n:{0}\n'.format(self.bias_current_per_node))


Der Punkt ist, dass die supply_bias_to_node überprüft wird, damit nicht verschiedene Stromversorgungen an denselben Knoten angeschlossen werden, und die check_connention zum Zeitpunkt der Erzeugung der Matrix eingefügt wird, so dass keine nicht versorgten Knoten (Matrix) vorhanden sind. Der lineare Löser gibt nur einen Fehler aus, wenn er entartet ist. Da der Zeitberechnungsbetrag der Vorverarbeitung $ O (Anzahl der Knoten + Anzahl der Widerstände + Anzahl der Stromversorgungsanschlüsse) $ beträgt, sollte die Berechnung selbst durch den Berechnungsbetrag des linearen Lösers nahezu ratenbestimmend sein.

Eigentlich verwenden

Ich werde es tatsächlich benutzen. Zunächst ein einfaches Beispiel. スライド6.PNG

Dies ist ein Beispiel für die Reihenschaltung von zwei Widerständen mit drei Knoten. Der kombinierte Widerstand ist die Umkehrung des Stroms von Vdd, aber der Vdd-Strom beträgt 0,0333 ... A, was sicherlich 30 Ω ist. screenshot.png

Versuchen Sie als nächstes, den zweiten Widerstand parallel zu schalten. スライド7.PNG Von Knoten 1 bis Knoten 2 sind zwei Widerstände definiert. Der kombinierte Widerstand beträgt 20 Ω, aber der Vdd-Strom beträgt sicherlich 0,05 A. screenshot2.png

Ein etwas kompliziertes Beispiel スライド8.PNG

Ich weiß nicht, wie viele Rundum-Elemente intuitiv vorhanden sind, aber es scheint, dass der Vdd-Strom 4 Ω bei 0,25 A beträgt. Da V1, V3 und V4 das gleiche Potential haben, werden sie effektiv zurückgezogen (1/20 + 1/10 + 1 / (5 + 5)) ^ -1. screenshot3.png

Netzbeständigkeit

Einige Verteilungen von Potentialen und Strömen, wenn den Knoten des Netzes unter Verwendung eines Lösers ein geeignetes Potential zugeführt wird. Es ist ein Netz, das aus 50 x 50 Knoten besteht und durch 1 Ω zwischen benachbarten Knoten verbunden ist.

Obwohl es sich um eine Berechnungszeit handelt, scheint es, dass sie sich selbst bei gleicher Schaltungsstruktur stark unterscheidet, je nachdem, wie die Stromversorgung angeschlossen ist und wie viele. Probe1 und Probe3 konnten in weniger als 1 Sekunde berechnet werden, Probe2 dauerte jedoch fast 200 Sekunden. Die Anzahl der Variablen beträgt (50 × 50) + 2 * (50 * 49) + Die Anzahl der Stromanschlüsse beträgt 7400 oder mehr, daher denke ich, dass es immer noch schnell ist. (Ich habe die spärliche Matrixbibliothek von scipy verwendet) Da es nach der direkten Methode berechnet wird, scheint es etwas schneller zu sein, wenn es nach der indirekten Methode (Iterationsmethode) berechnet wird (diesmal nicht verifiziert). Ich habe den Code usw. in [hier] eingefügt (https://github.com/kuuso/hobby/tree/master/dc_solver).

Am Ende

Viel Spaß beim Leben in Stromkreisen in der Zukunft!

Recommended Posts

Schreiben wir einen einfachen Gleichstromlöser
Schreiben Sie einen supereinfachen TCP-Server
Schreiben wir ein einfaches Simulationsprogramm für das "Monty Hall Problem"
Lassen Sie uns mit PLY 1 eine einfache Sprache erstellen
Schreiben Sie eine einfache Giermethode in Python
Schreiben Sie ein einfaches Vim-Plugin in Python 3
Schreiben wir ein Python-Programm und führen es aus
Schreiben Sie ein super einfaches molekulardynamisches Programm in Python
Ich möchte in Python schreiben! (2) Schreiben wir einen Test
Versuchen Sie, ein einfaches Spiel mit Python 3 und iPhone zu erstellen
Es ist schwer, einen sehr einfachen Algorithmus in PHP zu schreiben