[PYTHON] Écrivons un simple solveur de courant continu

introduction

Cet article est le 23e jour du Programmation compétitive (2) Calendrier de l'Avent 2019. J'ai écrit un solveur de courant continu qui calcule le nombre de courants et de résistances combinées qui traverseront chaque résistance dans un circuit dans lequel plusieurs résistances électriques sont connectées en série ou en parallèle. (Bien que cela soit rarement demandé dans la programmation actuelle du concours, c'est correct car il s'agit d'un graphique et d'un calcul)

Solveur de courant continu

Par exemple, supposons que vous souhaitiez connaître la résistance combinée du circuit de résistance suivant réalisé en combinant des résistances avec chaque résistance de 1,0Ω. スライド2.PNG Bien que cela semble simple, il est très difficile de le calculer, donc dans cet article, le programme qui calcule cela sera appelé le solveur de courant CC. Comme prémisse

--Chaque valeur de résistance est une valeur connue

Comment résoudre

En résolvant les équations simultanées formulées selon la loi de Kirchhof de la tension et du courant, le potentiel de chaque nœud et le courant de chaque résistance peuvent être obtenus. Par conséquent, il est nécessaire de définir les variables de manière appropriée. スライド3.PNG

Formulez la pièce en rouge comme une variable. Puisqu'il est finalement réduit à une équation linéaire, l'élément inconnu minimum devient une variable. Dans les cours et les examens de physique des lycées, le calcul actuel pour les systèmes avec de nombreuses variables inconnues est rarement demandé, mais s'il y a beaucoup de variables, il est nécessaire de concevoir une méthode de mise en œuvre en fonction du nombre d'équations linéairement indépendantes à formuler. Je vais. En considérant la connexion de l'alimentation comme une variable, nous pouvons formuler les équations nécessaires et suffisantes comme suit. スライド4.PNG

Vous obtiendrez le même nombre d'expressions que le nombre de variables définies. Le fait est que la formule obtenue sans connecter l'alimentation ne devient pas linéairement indépendante, et que la formule linéairement indépendante peut être obtenue quel que soit le nombre de nœuds qui connectent l'alimentation électrique sont augmentés (sauf si les alimentations sont court-circuitées). .. (Peut être prouvé par induction (devrait))

Exemple

Par exemple, dans le cas d'une résistance série aussi simple スライド6.PNG

Un total de 7 peut être formulé comme suit. スライド5.PNG

Implémenté en Python

Puisqu'il existe un solveur linéaire qui peut être utilisé facilement, je l'écrirai en Python. En écrivant un solveur, la politique suivante

La source est GitHub. C'est presque comme ça

DCsolver.py


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

class DCsolver:

    def __init__(self, nodenum):
        #Solveur
        self.linear_solver = np.linalg.solve

        #Le nombre de nœuds est fixe. Mettre à jour le nombre de résistances et le nombre de connexions de polarisation
        self.n_node = nodenum
        self.n_res = 0
        self.n_bias_probe = 0

        #Résistance: point de départ, point final, valeur de résistance
        self.res_from = []
        self.res_to = []
        self.res_value = []

        #Alimentation: accès par nom pour connexion virtuelle
        self.bias_name = []
        self.bias_level = []
        self.bias_index = dict() # dict<string,int>

        # Bias supplied (-1: not biased)
        self.biased = [-1] * self.n_node
        #Déterminez lors du calcul à quel nœud chaque biais sonde
        self.bias_from = None
        self.bias_to = None

        #Équation linéaire A*X=Résoudre 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_de nœud en nœud_valeur de résistance res à à_Connectez la résistance de valeur
        #Définir la direction du courant dans cette direction
        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):
        #Empêcher plusieurs biais d'accéder à un nœud pour refléter les derniers paramètres
        assert bias_name in self.bias_index, \
            "{0} is not defined, please define before supply".format(bias_name)
        idx = self.bias_index[bias_name]
        #Avertir si le biais est déjà fourni.
        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)Définir
        nv = self.n_node
        nr = self.n_res
        nb = self.n_bias_probe

        #Répertoriez et indexez les paramètres de condition de biais finaux
        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)

        #Taille de la matrice=Nombre de nœuds+Nombre de résistances+Nombre de chemins d'alimentation de biais
        #Variable inconnue
        #  [0...nv-1]:Potentiel du nœud i
        #  [nv...nv+nr-1] :Courant de résistance
        #  [nv+nr...n-1] :Courant de chemin d'alimentation de polarisation
        n = nv + nr + nb
        mat = np.zeros((n,n))
        vec = np.zeros(n)

        # Kirchhoff's Loi actuelle (La somme des dépenses et des revenus de chaque nœud est 0)
        #ligne i([0...nv-1])L'équation est la somme des courants pour le nœud i
        #Le courant traversant la résistance j provient de[j]Aller à[j]Compté comme revenu
        for j in range(nr):
            mat[self.res_from[j]][nv + j] = 1
            mat[self.res_to[j]][nv + j] = -1

        # Kirchhoff's Loi de tension (la chute de tension de chaque résistance est une différence de potentiel)
        #  nv+ligne j([nv...nv+nr-1])L'équation est la somme des courants pour la résistance 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]
        
        #Formule de définition du biais
        #  bias_from[i]Le potentiel est un biais_level['bias']Est fixé à. (Ajouté à la partie KVL)
        #  bias_to[i]Le courant de l'alimentation électrique pénètre dans(Ajouté à la partie KCL)
        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
        
        #Recherchez les nœuds qui ne sont pas connectés à Bias (les nœuds flottants ne sont pas autorisés)
        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]
        
        #Le courant de nœud calcule uniquement la sortie ((Σ)|outgo|+Σ|income|)/Calculer avec 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))


Le fait est que le supply_bias_to_node est vérifié afin que différentes alimentations ne soient pas connectées au même nœud, et que check_connention est inséré au moment de générer la matrice afin qu'il n'y ait pas de nœuds non alimentés (matrice). Le solveur linéaire lève juste une erreur lorsqu'il est dégénéré) Étant donné que la quantité de temps de calcul du prétraitement est $ O (nombre de nœuds + nombre de résistances + nombre de connexions d'alimentation) $, le calcul lui-même devrait être presque déterminant par la quantité de calcul du solveur linéaire.

En fait utiliser

Je vais vraiment l'utiliser. Tout d'abord, un exemple simple. スライド6.PNG

Voici un exemple de connexion de deux résistances en série à trois nœuds. La résistance combinée est l'inverse du courant de sortie de Vdd, mais le courant Vdd est de 0,0333 ... A, ce qui est certainement 30Ω. screenshot.png

Ensuite, essayez de connecter la deuxième résistance en parallèle. スライド7.PNG Deux résistances sont définies du nœud 1 au nœud 2. La résistance combinée est de 20Ω, mais le courant Vdd est sûrement de 0,05A. screenshot2.png

Un exemple un peu compliqué スライド8.PNG

Je ne sais pas combien d'éléments enveloppants il y a intuitivement, mais il semble que le courant Vdd est de 4Ω à 0,25A. Puisque V1, V3 et V4 ont le même potentiel, ils sont effectivement rétractés (1/20 + 1/10 + 1 / (5 + 5)) ^ -1. screenshot3.png

Résistance au maillage

Une partie de la distribution du potentiel et de la distribution du courant lorsqu'un potentiel approprié est fourni aux nœuds du maillage à l'aide d'un solveur. C'est un maillage composé de 50 x 50 nœuds, et est connecté par 1Ω entre des nœuds adjacents.

Bien qu'il s'agisse d'un temps de calcul, il semble que même avec la même structure de circuit, il diffère considérablement selon la façon de connecter l'alimentation et le nombre, et sample1 et sample3 pourraient être calculés en moins d'une seconde, mais sample2 a pris près de 200 secondes. Le nombre de variables est (50 × 50) + 2 * (50 * 49) + le nombre de connexions électriques est de 7400 ou plus, donc j'ai l'impression que c'est toujours rapide. (J'ai utilisé la bibliothèque de matrices éparses de scipy) Puisqu'il est calculé par la méthode directe, il semble qu'il sera un peu plus rapide s'il est calculé par la méthode indirecte (méthode répétitive) (non vérifiée cette fois). J'ai mis le code etc. dans ici.

À la fin

Ayez une vie de circuit électrique amusante dans le futur!

Recommended Posts

Écrivons un simple solveur de courant continu
Ecrire un serveur TCP super simple
Écrivons un programme de simulation simple pour le "problème de Monty Hall"
Faisons un langage simple avec PLY 1
Ecrire une méthode de cupidité simple en Python
Ecrire un plugin Vim simple en Python 3
Écrivons un programme Python et exécutons-le
Ecrire un programme de dynamique moléculaire super simple en python
Je veux écrire en Python! (2) Écrivons un test
Essayez de créer un jeu simple avec Python 3 et iPhone
Il est difficile d'écrire un algorithme très simple en php