import de fichiers
This commit is contained in:
BIN
modele/__pycache__/cocosim.cpython-312.pyc
Normal file
BIN
modele/__pycache__/cocosim.cpython-312.pyc
Normal file
Binary file not shown.
BIN
modele/__pycache__/composants.cpython-312.pyc
Normal file
BIN
modele/__pycache__/composants.cpython-312.pyc
Normal file
Binary file not shown.
BIN
modele/__pycache__/donnees.cpython-312.pyc
Normal file
BIN
modele/__pycache__/donnees.cpython-312.pyc
Normal file
Binary file not shown.
BIN
modele/__pycache__/elements.cpython-312.pyc
Normal file
BIN
modele/__pycache__/elements.cpython-312.pyc
Normal file
Binary file not shown.
BIN
modele/__pycache__/exceptions.cpython-312.pyc
Normal file
BIN
modele/__pycache__/exceptions.cpython-312.pyc
Normal file
Binary file not shown.
BIN
modele/__pycache__/graphe.cpython-312.pyc
Normal file
BIN
modele/__pycache__/graphe.cpython-312.pyc
Normal file
Binary file not shown.
BIN
modele/__pycache__/matrice.cpython-312.pyc
Normal file
BIN
modele/__pycache__/matrice.cpython-312.pyc
Normal file
Binary file not shown.
52
modele/cocosim.py
Normal file
52
modele/cocosim.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from modele.matrice import Matrice
|
||||
|
||||
|
||||
class Cocosim(object):
|
||||
def __init__(self, conf, graphe, scenario, observateur):
|
||||
self.conf = conf
|
||||
self.graphe = graphe
|
||||
self.scenario = scenario
|
||||
if 'max' in self.scenario:
|
||||
self.max_cycles = self.scenario['max']
|
||||
else:
|
||||
self.max_cycles = None
|
||||
self.n_cycle = 0
|
||||
self.matrice = Matrice(self.graphe)
|
||||
self.observatoire = {}
|
||||
self.observateur = observateur
|
||||
for k,_,_ in self.observateur:
|
||||
self.observatoire[k] = []
|
||||
|
||||
def cycle(self):
|
||||
"""
|
||||
Un cycle se fait en deux passes plus la mise à jour des composants.
|
||||
"""
|
||||
if self.n_cycle in self.scenario:
|
||||
[f(self.graphe[x]) for x, f in self.scenario[self.n_cycle]]
|
||||
|
||||
self.graphe.init_cycle()
|
||||
|
||||
# Passe 1
|
||||
self.matrice.init_mat()
|
||||
xvect = self.matrice.solve()
|
||||
self.graphe.passe_calcul(xvect)
|
||||
|
||||
# Passe 2 (pour les diodes)
|
||||
self.matrice.init_mat()
|
||||
xvect = self.matrice.solve()
|
||||
self.graphe.passe_calcul(xvect)
|
||||
|
||||
# Mise à jour des états
|
||||
self.graphe.update()
|
||||
self.graphe.coherence()
|
||||
|
||||
for k, obj, fun in self.observateur:
|
||||
self.observatoire[k].append(fun(self.graphe[obj]))
|
||||
|
||||
self.n_cycle += 1
|
||||
|
||||
def run(self):
|
||||
for cycle in range(self.max_cycles):
|
||||
self.cycle()
|
||||
|
||||
|
||||
264
modele/composants.py
Normal file
264
modele/composants.py
Normal file
@@ -0,0 +1,264 @@
|
||||
from modele.exceptions import NonPolariseException, Dir
|
||||
|
||||
|
||||
class Composant(object):
|
||||
def __init__(self, conf, nom):
|
||||
"""
|
||||
Un composant doit a minima savoir donner une résistance
|
||||
"""
|
||||
self.branche = None # Pour récupérer l'intensité dans la branche
|
||||
self.nom = nom
|
||||
self.conf = conf
|
||||
self.r = None
|
||||
|
||||
def init_cycle(self):
|
||||
"""
|
||||
Initialisation du composant avant la 1ere passe de calcul.
|
||||
Pour la plupart des composants (ceux qui se calculent en une seule passe) on ne fait
|
||||
rien. N'est utile que pour les composants se calculant en plusieurs passes (diodes...).
|
||||
"""
|
||||
pass
|
||||
|
||||
def passe_calcul(self):
|
||||
"""
|
||||
Initialisation du composant apres la 1ere passe de calcul.
|
||||
Pour la plupart des composants (ceux qui se calculent en une seule passe) on ne fait
|
||||
rien. N'est utile que pour les composants se calculant en plusieurs passes (diodes...). """
|
||||
pass
|
||||
|
||||
def update(self):
|
||||
# Par défaut ne fait rien
|
||||
pass
|
||||
|
||||
def reverse(self):
|
||||
"""
|
||||
Permet de retourner les composants polarisés (s'ils sont préfixés par "!")
|
||||
"""
|
||||
raise NonPolariseException
|
||||
|
||||
def etat(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Contact(Composant):
|
||||
def __init__(self, conf, nom, contact_type):
|
||||
super().__init__(conf, nom)
|
||||
self.ouvert = None
|
||||
self.ouvre()
|
||||
self.type = contact_type
|
||||
|
||||
def ouvre(self):
|
||||
self.ouvert = True
|
||||
self.r = self.conf['RMAX']
|
||||
|
||||
def ferme(self):
|
||||
self.ouvert = False
|
||||
self.r = self.conf['RMIN']
|
||||
|
||||
def etat(self):
|
||||
return not self.ouvert
|
||||
|
||||
def bascule(self):
|
||||
if self.ouvert:
|
||||
self.ferme()
|
||||
else:
|
||||
self.ouvre()
|
||||
|
||||
|
||||
class Resistance(Composant):
|
||||
def __init__(self, conf, nom, valeur):
|
||||
super().__init__(conf, nom)
|
||||
self.RDEFAULT = self.conf["RES_DEFAULT"]
|
||||
if valeur is None:
|
||||
self.r = self.RDEFAULT
|
||||
else:
|
||||
self.r = valeur
|
||||
|
||||
def set_r(self, valeur):
|
||||
self.r = valeur
|
||||
|
||||
def etat(self):
|
||||
return None
|
||||
|
||||
|
||||
class Lampe(Resistance):
|
||||
def __init__(self, conf, nom):
|
||||
self.RLAMPE = conf["RES_LAMPE"]
|
||||
self.SEUIL = conf["SEUIL_LAMPE"]
|
||||
super().__init__(conf, nom, self.RLAMPE)
|
||||
self.haut = False
|
||||
self.chute()
|
||||
|
||||
def monte(self):
|
||||
self.haut = True
|
||||
|
||||
def chute(self):
|
||||
self.haut = False
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
if abs(self.branche.i()) >= self.SEUIL:
|
||||
self.monte()
|
||||
else:
|
||||
self.chute()
|
||||
|
||||
def etat(self):
|
||||
return self.haut
|
||||
|
||||
|
||||
class Relais(Composant):
|
||||
def __init__(self, conf, nom, graphe):
|
||||
super().__init__(conf, nom)
|
||||
self.graphe = graphe
|
||||
self.r = self.conf["RES_RELAIS"]
|
||||
self.SEUIL = self.conf["SEUIL_RELAIS"]
|
||||
prefix = nom.split('.')
|
||||
self._contacts_travail = []
|
||||
self._contacts_repos = []
|
||||
self._contacts_double = []
|
||||
self.haut = False
|
||||
|
||||
def add_travail(self, nom):
|
||||
self._contacts_travail.append(nom)
|
||||
|
||||
def add_repos(self, nom):
|
||||
self._contacts_repos.append(nom)
|
||||
|
||||
def add_double(self, nom):
|
||||
self._contacts_double.append(nom)
|
||||
|
||||
def monte(self):
|
||||
self.haut = True
|
||||
for nom in self._contacts_repos:
|
||||
self.graphe[nom].ouvre()
|
||||
|
||||
for nom in self._contacts_travail:
|
||||
self.graphe[nom].ferme()
|
||||
|
||||
for nom in self._contacts_double:
|
||||
self.graphe[nom].active()
|
||||
|
||||
def chute(self):
|
||||
self.haut = False
|
||||
for nom in self._contacts_repos:
|
||||
self.graphe[nom].ferme()
|
||||
|
||||
for nom in self._contacts_travail:
|
||||
self.graphe[nom].ouvre()
|
||||
|
||||
for nom in self._contacts_double:
|
||||
self.graphe[nom].desactive()
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
if self.branche.i() >= self.SEUIL:
|
||||
self.monte()
|
||||
else:
|
||||
self.chute()
|
||||
|
||||
def etat(self):
|
||||
return self.haut
|
||||
|
||||
|
||||
class DemiRelaisBasculeur(Composant):
|
||||
def __init__(self, conf, nom, graphe, cote):
|
||||
super().__init__(conf, nom)
|
||||
self.SEUIL = self.conf["SEUIL_RELAIS"]
|
||||
self.r = self.conf["RES_RELAIS"]
|
||||
self._contacts_basc = []
|
||||
self.haut = False
|
||||
self.cote = cote
|
||||
self.graphe = graphe
|
||||
|
||||
def add_contact(self, nom):
|
||||
self._contacts_basc.append(nom)
|
||||
|
||||
def monte(self):
|
||||
self.haut = True
|
||||
if self.cote == Dir.GAUCHE:
|
||||
for nom in self._contacts_basc:
|
||||
self.graphe.elements[nom].bascule_gauche()
|
||||
elif self.cote == Dir.DROITE:
|
||||
for nom in self._contacts_basc:
|
||||
self.graphe.elements[nom].bascule_droite()
|
||||
else:
|
||||
assert False
|
||||
|
||||
def chute(self):
|
||||
self.haut = False
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
if self.branche.i() >= self.SEUIL:
|
||||
self.monte()
|
||||
else:
|
||||
self.chute()
|
||||
|
||||
def etat(self):
|
||||
return self.haut
|
||||
|
||||
|
||||
class Tempo(Composant):
|
||||
"""
|
||||
Tempo type "bilame piloté par le temps"
|
||||
"""
|
||||
def __init__(self, conf, nom, tempo_blocage, tempo_liberation):
|
||||
super().__init__(conf, nom)
|
||||
self.SEUIL = self.conf["SEUIL_TEMPO"]
|
||||
self.h_trans = 0 # heure de la transition
|
||||
self.h = 0 # heure courante
|
||||
self.i_prec = 0.0 # intensité précédente
|
||||
self.tempo_blocage = tempo_blocage / self.conf["DT"] # durée de la tempo de blocage
|
||||
self.tempo_liberation = tempo_liberation / self.conf["DT"] # durée de la tempo de blocage
|
||||
self.blocage = False
|
||||
self.r = self.conf['RMIN']
|
||||
|
||||
def update(self):
|
||||
if self.blocage:
|
||||
if self.h - self.h_trans >= self.tempo_liberation:
|
||||
self.h_trans = self.h
|
||||
self.blocage = False
|
||||
self.r = self.conf["RMIN"]
|
||||
else:
|
||||
if self.branche.i() >= self.SEUIL:
|
||||
if self.i_prec < self.SEUIL:
|
||||
self.h_trans = self.h # On mémorise l'heure de la transition
|
||||
|
||||
if self.h - self.h_trans >= self.tempo_blocage - 1:
|
||||
self.r = self.conf["RMAX"]
|
||||
self.blocage = True
|
||||
self.h_trans = self.h # On mémorise l'heure à laquelle on bloque
|
||||
else:
|
||||
pass # On est au-delà du seuil mais la tempo n'est pas échue
|
||||
self.i_prec = self.branche.i()
|
||||
self.h += 1
|
||||
|
||||
@property
|
||||
def ouvert(self):
|
||||
return self.blocage
|
||||
|
||||
def etat(self):
|
||||
return not self.blocage
|
||||
|
||||
|
||||
class Diode(Composant):
|
||||
def __init__(self, conf, nom):
|
||||
super().__init__(conf, nom)
|
||||
self.r = self.conf["RMAX"]
|
||||
self.reversed = False # La diode est dans le sens opposé à la branche
|
||||
|
||||
def init_cycle(self):
|
||||
self.r = self.conf["RMAX"]
|
||||
|
||||
def passe_calcul(self):
|
||||
if (not self.reversed and (self.branche.amont.u() > self.branche.aval.u())) \
|
||||
or (self.reversed and (self.branche.amont.u() < self.branche.aval.u())):
|
||||
self.r = self.conf["RMIN"]
|
||||
else:
|
||||
self.r = self.conf["RMAX"]
|
||||
|
||||
def reverse(self):
|
||||
self.reversed = True
|
||||
|
||||
def etat(self):
|
||||
return None
|
||||
28
modele/config.json
Normal file
28
modele/config.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"circuit": {
|
||||
"DT": 0.05,
|
||||
"RMIN": 1E-32,
|
||||
"RMAX": 1E32,
|
||||
"UMAX": 24.5,
|
||||
"USEUIL": 5,
|
||||
"ISEUIL": 0.05,
|
||||
"IMAX": 5.0,
|
||||
"RES_DEFAULT": 100,
|
||||
"RES_LAMPE": 100.0,
|
||||
"SEUIL_LAMPE": 0.05,
|
||||
"RES_RELAIS": 100.0,
|
||||
"SEUIL_RELAIS": 0.05,
|
||||
"SEUIL_TEMPO": 0.05,
|
||||
"PULSE_PERIODE": 1.0
|
||||
},
|
||||
"ui": {
|
||||
"DT": 0.13,
|
||||
"BLOCK_H": 50,
|
||||
"BLOCK_W": 40,
|
||||
"RELAISBASC_WIDTH": 1.6,
|
||||
"LINE_W": 1,
|
||||
"WINDOW_W": 1100,
|
||||
"WINDOW_H": 600
|
||||
}
|
||||
}
|
||||
|
||||
356
modele/donnees.py
Normal file
356
modele/donnees.py
Normal file
@@ -0,0 +1,356 @@
|
||||
import logging
|
||||
|
||||
|
||||
class GraphStructException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Noeud(object):
|
||||
def __init__(self, donnees):
|
||||
self.nom = donnees['nom']
|
||||
self.donnees = donnees
|
||||
self.type = donnees['type']
|
||||
self.assoc_connecteurs = {}
|
||||
|
||||
def __str__(self):
|
||||
return f"<Noeud : {self.nom}, {self.type}>"
|
||||
|
||||
def set_branche(self, direction, branche):
|
||||
# Associe la branche correspondant à un connecteur
|
||||
if direction in self.assoc_connecteurs:
|
||||
self.assoc_connecteurs[direction].append(branche)
|
||||
else:
|
||||
self.assoc_connecteurs[direction] = [branche]
|
||||
|
||||
def get_branches(self, direction):
|
||||
return self.assoc_connecteurs[direction]
|
||||
|
||||
def get_branche(self, direction):
|
||||
c = self.get_branches(direction)
|
||||
if len(c) == 1:
|
||||
return c[0]
|
||||
else:
|
||||
raise GraphStructException("Invalid number of connectors")
|
||||
|
||||
def remove_branche(self, branche, direction):
|
||||
self.assoc_connecteurs[direction].remove(branche)
|
||||
|
||||
|
||||
class Arete(object):
|
||||
def __init__(self, amont, amont_dir, aval, aval_dir):
|
||||
self.amont = amont
|
||||
self.amont_dir = amont_dir
|
||||
self.aval = aval
|
||||
self.aval_dir = aval_dir
|
||||
|
||||
def deconnecte(self):
|
||||
self.amont.remove_branche(self, self.amont_dir)
|
||||
self.aval.remove_branche(self, self.aval_dir)
|
||||
self.amont = None
|
||||
self.aval = None
|
||||
self.amont_dir = None
|
||||
self.aval_dir = None
|
||||
|
||||
def reconnecte_noeud(self, nsrc, ndir_src, ndest, ndir_dest):
|
||||
"""
|
||||
Déconnecte le noeud source, et le remplace par le noeud dest
|
||||
"""
|
||||
if nsrc == self.amont and self.amont_dir == ndir_src:
|
||||
self.amont.remove_branche(self, self.amont_dir)
|
||||
self.amont = ndest
|
||||
self.amont_dir = ndir_dest
|
||||
ndest.set_branche(ndir_dest,self)
|
||||
elif nsrc == self.aval and self.aval_dir == ndir_src:
|
||||
self.aval.remove_branche(self, self.aval_dir)
|
||||
self.aval = ndest
|
||||
self.aval_dir = ndir_dest
|
||||
ndest.set_branche(ndir_dest,self)
|
||||
else:
|
||||
raise GraphStructException
|
||||
|
||||
def get_neighbour(self, noeud, direction):
|
||||
if self.amont == noeud and self.amont_dir == direction:
|
||||
return self.aval, self.aval_dir
|
||||
elif self.aval == noeud and self.aval_dir == direction:
|
||||
return self.amont, self.amont_dir
|
||||
else:
|
||||
raise GraphStructException("No such neighbour")
|
||||
|
||||
def get_amont(self):
|
||||
return self.amont, self.amont_dir
|
||||
|
||||
def get_aval (self):
|
||||
return self.aval, self.aval_dir
|
||||
|
||||
|
||||
class Branche(Arete):
|
||||
def __init__(self, amont, amont_dir, aval, aval_dir, composants=None):
|
||||
super().__init__(amont, amont_dir, aval, aval_dir)
|
||||
self.composants = [] if composants is None else composants
|
||||
|
||||
|
||||
class Condensateur(object):
|
||||
"""
|
||||
Un condensateur correspond en fait à l'objet connecteur câblé à deux noeuds amonts et aval
|
||||
C'est donc "à la fois un noeud et deux branches"
|
||||
"""
|
||||
def __init__(self, donnees):
|
||||
self.nom = donnees['nom']
|
||||
self.donnees = donnees
|
||||
self.type = donnees['type']
|
||||
self.amont = None
|
||||
self.aval = None
|
||||
self.amont_dir = None # le connecteur du noeud amont auquel on est raccordé
|
||||
self.aval_dir = None # le connecteur du noeud aval auquel on est raccordé
|
||||
|
||||
def get_amont(self):
|
||||
return self.amont, self.amont_dir
|
||||
|
||||
def get_aval (self):
|
||||
return self.aval, self.aval_dir
|
||||
|
||||
|
||||
class SchemaReader(object):
|
||||
"""
|
||||
A la lecture du graphe, tous les objets sont des noeuds et ils sont raccordés par des branches.
|
||||
La simplification permet de supprimer les coudes.
|
||||
La réduction des branches permet de transformer une série de branches et de composants en une seule branche
|
||||
contentant les composants.
|
||||
"""
|
||||
REDUCTIBLE = ["ContactRepos", "Relais", "Tempo", "ContactTravail", "Bouton", "Resistance",
|
||||
"Diode", "Lampe", "RelaisCote", "Levier"]
|
||||
RIEN_A_FAIRE = ["P24", "P0", "Pulse", "Noeud", "Condensateur", "ContactBasculeur", "ContactDouble"]
|
||||
ID_NOM = -1
|
||||
|
||||
def __init__(self, d):
|
||||
self.noeuds = {}
|
||||
self.condensateurs = {}
|
||||
self.branches = []
|
||||
self.composants = None
|
||||
self.lire_donnees(d)
|
||||
|
||||
@classmethod
|
||||
def nouveau_nom(cls, prefix=""):
|
||||
"""
|
||||
Crée un nouveau nom
|
||||
"""
|
||||
cls.ID_NOM += 1
|
||||
return f"@{prefix}{cls.ID_NOM}"
|
||||
|
||||
def connecte(self, n1, d1, n2, d2, composants=None):
|
||||
"""
|
||||
Crée une branche et connecte deux noeuds avec
|
||||
"""
|
||||
a = Branche(n1, d1, n2, d2, composants=composants)
|
||||
self.branches.append(a)
|
||||
n1.set_branche(d1, a)
|
||||
n2.set_branche(d2, a)
|
||||
|
||||
def connecte_condensateur_amont(self, cond, n1, d1):
|
||||
cond.amont = n1
|
||||
cond.amont_dir = d1
|
||||
|
||||
def connecte_condensateur_aval(self, cond, n1, d1):
|
||||
cond.aval = n1
|
||||
cond.aval_dir = d1
|
||||
|
||||
def remove_branche(self, branche):
|
||||
self.branches.remove(branche)
|
||||
branche.deconnecte()
|
||||
return branche.composants
|
||||
|
||||
def remove_noeud(self, noeud):
|
||||
del self.noeuds[noeud]
|
||||
|
||||
def lire_donnees(self, d):
|
||||
"""
|
||||
Lit le dictionnaire contenant le schéma
|
||||
"""
|
||||
# On commence par lire les noeuds
|
||||
try:
|
||||
for nom, donnees in d['blocs'].items():
|
||||
donnees['nom'] = nom
|
||||
self.noeuds[nom] = Noeud(donnees)
|
||||
|
||||
# On lit (et lie) ensuite les câbles
|
||||
for amont, dir_amont, aval, dir_aval in d['cables']:
|
||||
self.connecte(self.noeuds[amont], dir_amont, self.noeuds[aval], dir_aval)
|
||||
except KeyError:
|
||||
raise GraphStructException
|
||||
|
||||
def traiter_composites(self):
|
||||
"""
|
||||
Traite les noeuds composites (contacts doubles, relais basculeurs, etc.) et les transforme en objets simples.
|
||||
"""
|
||||
noeuds_a_supprimer = []
|
||||
noeuds_a_ajouter = []
|
||||
|
||||
for nom, noeud in self.noeuds.items():
|
||||
# Parcoure les noeuds en cherchant les noeuds composites
|
||||
|
||||
if noeud.type == "RelaisCote":
|
||||
# On transforme en relais simple
|
||||
noeud.type = "Relais"
|
||||
|
||||
if 'orientation' not in noeud.donnees or noeud.donnees['orientation'] == 0:
|
||||
b_hg = noeud.get_branche("hg")
|
||||
b_bg = noeud.get_branche("bg")
|
||||
b_hg.reconnecte_noeud(noeud, "hg", noeud, "g")
|
||||
b_bg.reconnecte_noeud(noeud, "bg", noeud, "d")
|
||||
elif noeud.donnees['orientation'] == 180:
|
||||
b_hd = noeud.get_branche("hd")
|
||||
b_bd = noeud.get_branche("bd")
|
||||
b_hd.reconnecte_noeud(noeud, "hd", noeud, "g")
|
||||
b_bd.reconnecte_noeud(noeud, "bd", noeud, "d")
|
||||
else:
|
||||
assert False, "Rien à faire là"
|
||||
|
||||
elif noeud.type == "RelaisBasculeur":
|
||||
noeuds_a_supprimer.append(nom)
|
||||
rel_g = Noeud({'nom': noeud.nom + ".gauche", 'type': 'Relais',
|
||||
'orientation': noeud.donnees['orientation'], 'basculeur': 'gauche'})
|
||||
rel_d = Noeud({'nom': noeud.nom + ".droite", 'type': 'Relais',
|
||||
'orientation': noeud.donnees['orientation'], 'basculeur': 'droite'})
|
||||
|
||||
noeuds_a_ajouter.append((noeud.nom + ".gauche", rel_g))
|
||||
noeuds_a_ajouter.append((noeud.nom + ".droite", rel_d))
|
||||
|
||||
# On récupère les branches
|
||||
br_hg = noeud.get_branche("hg")
|
||||
br_bg = noeud.get_branche("bg")
|
||||
br_hd = noeud.get_branche("hd")
|
||||
br_bd = noeud.get_branche("bd")
|
||||
|
||||
# Puis les voisins
|
||||
vois_hg, vois_hg_dir = br_hg.get_neighbour(noeud, "hg")
|
||||
vois_bg, vois_bg_dir = br_bg.get_neighbour(noeud, "bg")
|
||||
vois_hd, vois_hd_dir = br_hd.get_neighbour(noeud, "hd")
|
||||
vois_bd, vois_bd_dir = br_bd.get_neighbour(noeud, "bd")
|
||||
|
||||
# On supprime les branches
|
||||
cmp_hg = self.remove_branche(br_hg)
|
||||
cmp_bg = self.remove_branche(br_bg)
|
||||
cmp_hd = self.remove_branche(br_hd)
|
||||
cmp_bd = self.remove_branche(br_bd)
|
||||
|
||||
# On lie les nouveaux noeuds
|
||||
self.connecte(rel_g, "g", vois_hg, vois_hg_dir, cmp_hg)
|
||||
self.connecte(rel_g, "d", vois_bg, vois_bg_dir, cmp_bg)
|
||||
self.connecte(rel_d, "g", vois_hd, vois_hd_dir, cmp_hd)
|
||||
self.connecte(rel_d, "d", vois_bd, vois_bd_dir, cmp_bd)
|
||||
|
||||
[self.remove_noeud(n) for n in noeuds_a_supprimer]
|
||||
[self.noeuds.__setitem__(nom, valeur) for nom, valeur in noeuds_a_ajouter]
|
||||
|
||||
def simplifier(self):
|
||||
"""
|
||||
Simplifie le graphe :
|
||||
- supprime les coudes
|
||||
"""
|
||||
# Supprime les coudes
|
||||
coudes = []
|
||||
for n in self.noeuds.values():
|
||||
if n.type == 'Coude':
|
||||
try:
|
||||
a1, a2 = n.get_branches("m")
|
||||
except ValueError:
|
||||
raise GraphStructException("Un coude doit avoir deux voisins")
|
||||
|
||||
# On récupère les voisins
|
||||
v1, d1 = a1.get_neighbour(n, "m")
|
||||
v2, d2 = a2.get_neighbour(n, "m")
|
||||
|
||||
comp1 = self.remove_branche(a1)
|
||||
comp2 = self.remove_branche(a2)
|
||||
|
||||
self.connecte(v1, d1, v2, d2, comp1 + comp2)
|
||||
coudes.append(n)
|
||||
|
||||
[self.remove_noeud(n.nom) for n in coudes]
|
||||
|
||||
def traiter_condensateurs(self):
|
||||
cond_list = []
|
||||
for nom, noeud in self.noeuds.items():
|
||||
if noeud.type == "Condensateur":
|
||||
branche_g = noeud.get_branche("g")
|
||||
branche_d = noeud.get_branche("d")
|
||||
n1 = self.nouveau_nom()
|
||||
nn_amont = Noeud({'nom': n1, 'type': 'Noeud'})
|
||||
cond_list.append(nn_amont)
|
||||
n2 = self.nouveau_nom()
|
||||
nn_aval = Noeud({'nom': n2, 'type': 'Noeud'})
|
||||
cond_list.append(nn_aval)
|
||||
|
||||
cond = Condensateur(noeud.donnees)
|
||||
self.condensateurs[nom] = cond
|
||||
cond.amont = nn_amont
|
||||
cond.aval = nn_aval
|
||||
# On raccorde le nouveau noeud amont au voisin amont initial
|
||||
voisin_g, dir_g = branche_g.get_neighbour(noeud, "g")
|
||||
composants_g = self.remove_branche(branche_g)
|
||||
self.connecte(voisin_g, dir_g, nn_amont, "m", composants_g)
|
||||
|
||||
# Le noeud nouveau noeud amont au condensateur
|
||||
self.connecte_condensateur_amont(cond, nn_amont, "m")
|
||||
|
||||
# Le nouveau noeud aval au condensateur
|
||||
self.connecte_condensateur_aval(cond, nn_aval, "m")
|
||||
|
||||
# On raccorde le nouveau noeud aval à la branche aval initiale
|
||||
voisin_d, dir_d = branche_d.get_neighbour(noeud, "d")
|
||||
composants_d = self.remove_branche(branche_d)
|
||||
self.connecte(voisin_d, dir_d, nn_aval, "m", composants_d)
|
||||
|
||||
for c in cond_list:
|
||||
self.noeuds[c.nom] = c
|
||||
|
||||
def reduire_branches(self):
|
||||
noeuds_a_supprimer = []
|
||||
for nom, noeud in self.noeuds.items():
|
||||
if noeud.type in self.REDUCTIBLE:
|
||||
# Le composant est stockable dans la branche (composant simple en série)
|
||||
b_gauche = noeud.get_branche("g")
|
||||
b_droite = noeud.get_branche("d")
|
||||
|
||||
voisin_g, voisin_g_dir = b_gauche.get_neighbour(noeud, "g")
|
||||
voisin_d, voisin_d_dir = b_droite.get_neighbour(noeud, "d")
|
||||
|
||||
comp_br_gauche = self.remove_branche(b_gauche)
|
||||
comp_br_droite = self.remove_branche(b_droite)
|
||||
|
||||
self.connecte(voisin_g, voisin_g_dir, voisin_d, voisin_d_dir, comp_br_gauche + [noeud] + comp_br_droite)
|
||||
|
||||
noeuds_a_supprimer.append(nom)
|
||||
|
||||
if noeud.type == "Diode" and noeud.donnees['orientation'] == 180:
|
||||
noeud.nom = "!" + noeud.nom
|
||||
|
||||
elif noeud.type == "Coude":
|
||||
assert False, "Les coudes auraient dûs être éliminés !"
|
||||
elif noeud.type in self.RIEN_A_FAIRE:
|
||||
pass
|
||||
else:
|
||||
raise GraphStructException("Type inconnu : " + noeud.type)
|
||||
|
||||
for n in noeuds_a_supprimer:
|
||||
del self.noeuds[n]
|
||||
|
||||
def make_liste_composants(self):
|
||||
self.composants = {}
|
||||
for b in self.branches:
|
||||
for c in b.composants:
|
||||
self.composants[c.nom] = c
|
||||
|
||||
def process(self):
|
||||
"""
|
||||
Méthode principale de traitement du graphe
|
||||
"""
|
||||
try:
|
||||
self.simplifier()
|
||||
self.traiter_composites()
|
||||
self.traiter_condensateurs()
|
||||
self.reduire_branches()
|
||||
self.make_liste_composants()
|
||||
except KeyError:
|
||||
logging.exception("Erreur de chaînage dans le graphe")
|
||||
raise GraphStructException
|
||||
514
modele/elements.py
Normal file
514
modele/elements.py
Normal file
@@ -0,0 +1,514 @@
|
||||
from modele.exceptions import (WrongElecPropertyException, SurtensionException,
|
||||
SurintensiteException, Dir)
|
||||
|
||||
|
||||
class Element(object):
|
||||
"""
|
||||
Classe mère.
|
||||
Le graphe lui-même est composé de Potentiels fixes (Potentiel), de Noeuds, et de Branches.
|
||||
Les branches contiennent une liste de composants en série.
|
||||
|
||||
Les éléments doivent implémenter quatre méthodes pour le calcul des valeurs :
|
||||
init_cycle est appelée avant chaque cycle, avant l'initialisation de la matrice
|
||||
report_pass_1 est appelée après la première passe pour report des valeurs de la matrice (et avant la construction
|
||||
de la matrice de la deuxième passe)
|
||||
report_pass2 est appelée après la 2e passe pour reporter les valeurs finales calculées
|
||||
update est appelée après le report de la 2e passe, pour pouvoir faire la mise à jour des états des éléments
|
||||
|
||||
"""
|
||||
P_CST, P_NOEUD, P_DLT, P_COND, P_EQVAR, P_BSCLAME = range(6)
|
||||
|
||||
def __init__(self, conf, nom, type_potentiel):
|
||||
self.type_potentiel = type_potentiel
|
||||
self.conf = conf
|
||||
self.nom = nom
|
||||
|
||||
def init_cycle(self):
|
||||
pass
|
||||
|
||||
def passe_calcul(self, solutions, index):
|
||||
pass
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def etat(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def coherence(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def eq_elts(self):
|
||||
"""
|
||||
Retourne les équipotentiels
|
||||
Par défaut, un seul équipotentiel
|
||||
"""
|
||||
return [self]
|
||||
|
||||
def get_potentiel(self, direction):
|
||||
"""
|
||||
Par défaut, un seul potentiel quel que soit la direction
|
||||
"""
|
||||
return self
|
||||
|
||||
|
||||
class Potentiel(Element):
|
||||
"""
|
||||
Potentiel à 24V ou à 0V
|
||||
"""
|
||||
|
||||
def __init__(self, conf, nom, u=0.0, voisins=None):
|
||||
super().__init__(conf, nom, self.P_CST)
|
||||
self._u = u
|
||||
if voisins is None:
|
||||
voisins = []
|
||||
self._voisins = voisins
|
||||
|
||||
def add_voisins(self, voisins):
|
||||
self._voisins += voisins
|
||||
|
||||
def voisins(self):
|
||||
return self._voisins
|
||||
|
||||
def u(self):
|
||||
return self._u
|
||||
|
||||
def i(self):
|
||||
raise WrongElecPropertyException
|
||||
|
||||
def coherence(self):
|
||||
pass
|
||||
|
||||
def etat(self):
|
||||
return self.u() >= self.conf['USEUIL']
|
||||
|
||||
class PotPulse(Potentiel):
|
||||
def __init__(self, conf, nom, u=0.0, periode=None, voisins=None):
|
||||
super().__init__(conf, nom, u, voisins)
|
||||
if periode is None:
|
||||
periode = self.conf['PULSE_PERIODE']
|
||||
self.periode = periode / self.conf['DT']
|
||||
self.u_max = u
|
||||
self.actif = True
|
||||
self.compteur = 0
|
||||
|
||||
def update(self):
|
||||
self.compteur += 1
|
||||
if self.actif:
|
||||
if self.compteur >= self.periode:
|
||||
self.compteur = 0
|
||||
self._u = 0.
|
||||
self.actif = False
|
||||
else:
|
||||
if self.compteur >= self.periode:
|
||||
self.compteur = 0
|
||||
self._u = self.u_max
|
||||
self.actif = True
|
||||
|
||||
|
||||
class Noeud(Element):
|
||||
"""
|
||||
Nœud du graphe
|
||||
|
||||
self._voisins: liste de (Graphe, polarité) avec polarité = +1 ou -1 pour les branches
|
||||
"""
|
||||
def __init__(self, conf, nom, voisins=None):
|
||||
super().__init__(conf, nom, self.P_NOEUD)
|
||||
self._u = None
|
||||
if voisins is None:
|
||||
voisins = []
|
||||
self._voisins = voisins
|
||||
|
||||
def add_voisins(self, voisins):
|
||||
self._voisins += voisins
|
||||
|
||||
def voisins(self):
|
||||
return self._voisins
|
||||
|
||||
def passe_calcul(self, vect, index):
|
||||
super().passe_calcul(vect, index)
|
||||
self._u = vect[index]
|
||||
|
||||
def u(self):
|
||||
return self._u
|
||||
|
||||
def coherence(self):
|
||||
if abs(self.u()) >= self.conf['UMAX']:
|
||||
raise SurtensionException
|
||||
|
||||
def etat(self):
|
||||
return self.u() >= self.conf['USEUIL']
|
||||
|
||||
|
||||
class Branche(Element):
|
||||
"""
|
||||
Branche du graphe.
|
||||
La branche contient un certain nombre de composants en série, qui peuvent être des relais, tempos, résistances
|
||||
ou contacts (équation). N'est pas utilisable pour les condensateurs.
|
||||
"""
|
||||
def __init__(self, conf, nom, composants):
|
||||
super().__init__(conf, nom, self.P_DLT)
|
||||
self.composants = composants
|
||||
self.amont = None
|
||||
self.aval = None
|
||||
self._i = 0.0
|
||||
|
||||
def add_voisins(self, amont, aval):
|
||||
self.amont = amont
|
||||
self.aval = aval
|
||||
|
||||
def r(self):
|
||||
return sum([x.r for x in self.composants]) + self.conf['RMIN']
|
||||
|
||||
def init_cycle(self):
|
||||
super().init_cycle()
|
||||
[x.init_cycle() for x in self.composants]
|
||||
|
||||
def passe_calcul(self, vect, index):
|
||||
super().passe_calcul(vect, index)
|
||||
self._i = vect[index] # Important de mettre à jour i avant de mettre à jour les composants
|
||||
[x.passe_calcul() for x in self.composants]
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
[x.update() for x in self.composants]
|
||||
|
||||
def i(self):
|
||||
return self._i
|
||||
|
||||
def coherence(self):
|
||||
if abs(self.i()) >= self.conf['IMAX']:
|
||||
raise SurintensiteException(f"Dans la branche {self.nom}")
|
||||
|
||||
def etat(self):
|
||||
return self.i() >= self.conf['ISEUIL']
|
||||
|
||||
|
||||
class Condensateur(Element):
|
||||
"""
|
||||
Condensateur.
|
||||
|
||||
La seule truande est que la tension au borne est en réalité calculée avec la charge du cycle précédent.
|
||||
"""
|
||||
def __init__(self, conf, nom, capacite, coef_mul_tension):
|
||||
super().__init__(conf, nom, self.P_COND)
|
||||
self.capacite = capacite
|
||||
self.amont = None
|
||||
self.aval = None
|
||||
self._i = 0.0
|
||||
self.q = 0.0
|
||||
self.q_prec = 0.0
|
||||
self.coef_mul_tension = coef_mul_tension
|
||||
|
||||
def add_voisins(self, amont, aval):
|
||||
self.amont = amont
|
||||
self.aval = aval
|
||||
|
||||
def r(self):
|
||||
raise WrongElecPropertyException
|
||||
|
||||
def init_cycle(self):
|
||||
super().init_cycle()
|
||||
|
||||
def passe_calcul(self, vect, index):
|
||||
super().passe_calcul(vect, index)
|
||||
self._i = vect[index] # Important de mettre à jour i avant de mettre à jour les composants
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
self.q_prec = self.q
|
||||
q = self.q + self.i() * self.conf['DT']
|
||||
self.q = q
|
||||
|
||||
def i(self):
|
||||
return self._i
|
||||
|
||||
def coherence(self):
|
||||
if abs(self.i()) >= self.conf['IMAX']:
|
||||
raise SurintensiteException
|
||||
|
||||
def etat(self):
|
||||
return bool(self.i() >= self.conf['ISEUIL'])
|
||||
|
||||
|
||||
class ContactBasculeur(Noeud):
|
||||
"""
|
||||
Nœud du graphe
|
||||
|
||||
self._voisins: liste de (Graphe, polarité) avec polarité = +1 ou -1 pour les branches
|
||||
"""
|
||||
UMAX = 24.5
|
||||
|
||||
class PotentielLocal(Element):
|
||||
def __init__(self, conf, nom, voisins=None):
|
||||
self.lame = None
|
||||
super().__init__(conf, nom, self.P_NOEUD)
|
||||
self._u = None
|
||||
if voisins is None:
|
||||
voisins = []
|
||||
self._voisins = voisins
|
||||
self.etat = False
|
||||
|
||||
def set_lame(self, lame):
|
||||
self.lame = lame
|
||||
|
||||
def add_voisins(self, voisins):
|
||||
self._voisins += voisins
|
||||
|
||||
def set_etat(self, etat):
|
||||
self.etat = etat
|
||||
|
||||
def voisins(self):
|
||||
return self._voisins + ([(self.lame, +1, "foo")] if self.etat else [])
|
||||
|
||||
def passe_calcul(self, vect, index):
|
||||
super().passe_calcul(vect, index)
|
||||
self._u = vect[index]
|
||||
|
||||
def u(self):
|
||||
return self._u
|
||||
|
||||
def coherence(self):
|
||||
if abs(self.u()) >= self.conf['UMAX']:
|
||||
raise SurtensionException
|
||||
|
||||
class Lame(Element):
|
||||
def __init__(self, conf, nom, contact, gauche, droite):
|
||||
self.amont = contact
|
||||
self.aval = None
|
||||
self.gauche = gauche
|
||||
self.droite = droite
|
||||
self.position = None
|
||||
super().__init__(conf, nom, self.P_DLT)
|
||||
self._i = None
|
||||
|
||||
def bascule(self, position):
|
||||
self.position = position
|
||||
if position == Dir.GAUCHE:
|
||||
self.aval = self.gauche
|
||||
else:
|
||||
self.aval = self.droite
|
||||
|
||||
def i(self):
|
||||
return self._i
|
||||
|
||||
def r(self):
|
||||
return self.conf['RMIN']
|
||||
|
||||
# Classe principale
|
||||
def __init__(self, conf, nom, graphe, voisins=None):
|
||||
self.graphe = graphe
|
||||
self.position = None
|
||||
self.potentiel_gauche = self.PotentielLocal(conf, nom + ".G")
|
||||
self.potentiel_droit = self.PotentielLocal(conf, nom + ".D")
|
||||
self.lame = self.Lame(conf, nom + ".L", self, self.potentiel_gauche, self.potentiel_droit)
|
||||
self.potentiel_gauche.set_lame(self.lame)
|
||||
self.potentiel_droit.set_lame(self.lame)
|
||||
self._voisins = [(self.lame, -1, "foo")] + voisins if voisins is not None else []
|
||||
super().__init__(conf, nom, self.P_NOEUD)
|
||||
self.bascule_gauche()
|
||||
|
||||
def eq_elts(self):
|
||||
return [self, self.potentiel_gauche, self.potentiel_droit, self.lame]
|
||||
|
||||
def add_voisins(self, voisins):
|
||||
for vois_tuple in voisins:
|
||||
_, _, connecteur = vois_tuple
|
||||
if connecteur == 'b':
|
||||
self._voisins = [vois_tuple]
|
||||
else:
|
||||
# Les autres connecteurs doivent être câblés vers les ConnecteursLocaux
|
||||
raise "Invalid connector for RelaisBasculeur"
|
||||
|
||||
def voisins(self):
|
||||
return self._voisins
|
||||
|
||||
def bascule_gauche(self):
|
||||
self.position = Dir.GAUCHE
|
||||
self.lame.bascule(self.position)
|
||||
self.potentiel_gauche.set_etat(True)
|
||||
self.potentiel_droit.set_etat(False)
|
||||
|
||||
def bascule_droite(self):
|
||||
self.position = Dir.DROITE
|
||||
self.lame.bascule(self.position)
|
||||
self.potentiel_gauche.set_etat(False)
|
||||
self.potentiel_droit.set_etat(True)
|
||||
|
||||
def get_potentiel(self, direction):
|
||||
"""
|
||||
Par défaut, un seul potentiel quel que soit la direction
|
||||
"""
|
||||
if direction == "b":
|
||||
return self
|
||||
elif direction == "g":
|
||||
return self.potentiel_gauche
|
||||
elif direction == "d":
|
||||
return self.potentiel_droit
|
||||
else:
|
||||
assert False
|
||||
|
||||
def passe_calcul(self, vect, index):
|
||||
super().passe_calcul(vect, index)
|
||||
self.potentiel_droit.passe_calcul(vect, self.graphe.vecteur.index(self.potentiel_droit))
|
||||
self.potentiel_gauche.passe_calcul(vect, self.graphe.vecteur.index(self.potentiel_gauche))
|
||||
self._u = vect[index]
|
||||
|
||||
def u(self):
|
||||
return self._u
|
||||
|
||||
def etat(self):
|
||||
return self.lame.position
|
||||
|
||||
def coherence(self):
|
||||
if abs(self.u()) >= self.conf['UMAX']:
|
||||
raise SurtensionException
|
||||
self.potentiel_droit.coherence()
|
||||
self.potentiel_gauche.coherence()
|
||||
|
||||
|
||||
class ContactDouble(Noeud):
|
||||
"""
|
||||
Nœud du graphe
|
||||
|
||||
self._voisins: liste de (Graphe, polarité) avec polarité = +1 ou -1 pour les branches
|
||||
"""
|
||||
UMAX = 24.5
|
||||
|
||||
class PotentielLocal(Element):
|
||||
def __init__(self, conf, nom, voisins=None):
|
||||
self.lame = None
|
||||
super().__init__(conf, nom, self.P_NOEUD)
|
||||
self._u = None
|
||||
if voisins is None:
|
||||
voisins = []
|
||||
self._voisins = voisins
|
||||
self.etat = False
|
||||
|
||||
def set_lame(self, lame):
|
||||
self.lame = lame
|
||||
|
||||
def add_voisins(self, voisins):
|
||||
self._voisins += voisins
|
||||
|
||||
def set_etat(self, etat):
|
||||
self.etat = etat
|
||||
|
||||
def voisins(self):
|
||||
return self._voisins + ([(self.lame, +1, "foo")] if self.etat else [])
|
||||
|
||||
def passe_calcul(self, vect, index):
|
||||
super().passe_calcul(vect, index)
|
||||
self._u = vect[index]
|
||||
|
||||
def u(self):
|
||||
return self._u
|
||||
|
||||
def coherence(self):
|
||||
if abs(self.u()) >= self.conf['UMAX']:
|
||||
raise SurtensionException
|
||||
|
||||
class Lame(Element):
|
||||
def __init__(self, conf, nom, contact, gauche, droite):
|
||||
self.amont = contact
|
||||
self.aval = None
|
||||
self.repos = gauche
|
||||
self.travail = droite
|
||||
self.position = False # Travail = True
|
||||
super().__init__(conf, nom, self.P_DLT)
|
||||
self._i = None
|
||||
|
||||
def set_position(self, position):
|
||||
self.position = position
|
||||
if position:
|
||||
self.aval = self.travail
|
||||
else:
|
||||
self.aval = self.repos
|
||||
|
||||
def i(self):
|
||||
return self._i
|
||||
|
||||
def r(self):
|
||||
return self.conf['RMIN']
|
||||
|
||||
# Classe principale
|
||||
def __init__(self, conf, nom, graphe, voisins=None, orientation=0):
|
||||
self.graphe = graphe
|
||||
self.orientation = orientation
|
||||
self.potentiel_repos = self.PotentielLocal(conf, nom + ".repos")
|
||||
self.potentiel_travail = self.PotentielLocal(conf, nom + ".travail")
|
||||
self.lame = self.Lame(conf, nom + ".L", self, self.potentiel_repos, self.potentiel_travail)
|
||||
self.potentiel_repos.set_lame(self.lame)
|
||||
self.potentiel_travail.set_lame(self.lame)
|
||||
self._voisins = [(self.lame, -1, "foo")] + voisins if voisins is not None else []
|
||||
super().__init__(conf, nom, self.P_NOEUD)
|
||||
self.lame.set_position(False)
|
||||
|
||||
def eq_elts(self):
|
||||
return [self, self.potentiel_repos, self.potentiel_travail, self.lame]
|
||||
|
||||
def add_voisins(self, voisins):
|
||||
for vois_tuple in voisins:
|
||||
_, _, connecteur = vois_tuple
|
||||
if (self.orientation == 0 and connecteur == 'd') \
|
||||
or (self.orientation == 180 and connecteur == 'g'):
|
||||
self._voisins = [vois_tuple]
|
||||
else:
|
||||
# Les autres connecteurs doivent être câblés vers les ConnecteursLocaux
|
||||
raise "Invalid connector for ContactDouble"
|
||||
|
||||
def voisins(self):
|
||||
return self._voisins
|
||||
|
||||
def get_potentiel(self, direction):
|
||||
"""
|
||||
Par défaut, un seul potentiel quel que soit la direction
|
||||
"""
|
||||
if self.orientation == 0:
|
||||
if direction == "d":
|
||||
return self
|
||||
elif direction == "bg":
|
||||
return self.potentiel_repos
|
||||
elif direction == "hg":
|
||||
return self.potentiel_travail
|
||||
else:
|
||||
assert False
|
||||
elif self.orientation == 180:
|
||||
if direction == "g":
|
||||
return self
|
||||
elif direction == "bd":
|
||||
return self.potentiel_repos
|
||||
elif direction == "hd":
|
||||
return self.potentiel_travail
|
||||
else:
|
||||
assert False
|
||||
else:
|
||||
assert False
|
||||
|
||||
def passe_calcul(self, vect, index):
|
||||
super().passe_calcul(vect, index)
|
||||
self.potentiel_travail.passe_calcul(vect, self.graphe.vecteur.index(self.potentiel_travail))
|
||||
self.potentiel_repos.passe_calcul(vect, self.graphe.vecteur.index(self.potentiel_repos))
|
||||
self._u = vect[index]
|
||||
|
||||
def u(self):
|
||||
return self._u
|
||||
|
||||
def etat(self):
|
||||
return self.lame.position
|
||||
|
||||
def coherence(self):
|
||||
if abs(self.u()) >= self.conf['UMAX']:
|
||||
raise SurtensionException
|
||||
self.potentiel_travail.coherence()
|
||||
self.potentiel_repos.coherence()
|
||||
|
||||
def active(self):
|
||||
self.lame.set_position(True)
|
||||
self.potentiel_travail.set_etat(True)
|
||||
self.potentiel_repos.set_etat(False)
|
||||
|
||||
def desactive(self):
|
||||
self.lame.set_position(False)
|
||||
self.potentiel_travail.set_etat(False)
|
||||
self.potentiel_repos.set_etat(True)
|
||||
22
modele/exceptions.py
Normal file
22
modele/exceptions.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class WrongElecPropertyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NonPolariseException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SurtensionException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SurintensiteException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Dir(IntEnum):
|
||||
GAUCHE = 1
|
||||
DROITE = 2
|
||||
171
modele/graphe.py
Normal file
171
modele/graphe.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import modele.composants as mc
|
||||
import modele.elements as me
|
||||
from modele.exceptions import Dir
|
||||
from modele.donnees import GraphStructException
|
||||
|
||||
|
||||
class Vecteur(object):
|
||||
"""
|
||||
Associe les éléments du graphe à leur rang dans le vecteur correspondant aux rangs dans la matrice.
|
||||
"""
|
||||
def __init__(self, elements):
|
||||
self._liste = [eq_elt for element in elements.values() for eq_elt in element.eq_elts()]
|
||||
|
||||
self._index = {} # Associe à un élément son index dans la matrice
|
||||
for index, element in enumerate(self._liste):
|
||||
self._index[element] = index
|
||||
|
||||
def __len__(self):
|
||||
return len(self._liste)
|
||||
|
||||
def index(self, element):
|
||||
return self._index[element]
|
||||
|
||||
def __iter__(self):
|
||||
return self._liste.__iter__()
|
||||
|
||||
|
||||
class Graphe(object):
|
||||
ID_BR = -1
|
||||
|
||||
@classmethod
|
||||
def branch_id(cls):
|
||||
cls.ID_BR += 1
|
||||
return f"b{cls.ID_BR}"
|
||||
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
self.elements = None
|
||||
self.composants = None
|
||||
self.vecteur = None
|
||||
|
||||
def load_data_from_schema_reader(self, reader):
|
||||
self.elements = {}
|
||||
self.composants = {}
|
||||
|
||||
# Construction des éléments et composants
|
||||
for nom, valeur in reader.composants.items():
|
||||
if valeur.type == 'Bouton' or valeur.type == 'ContactTravail' or valeur.type == 'ContactRepos' \
|
||||
or valeur.type == 'Levier':
|
||||
self.composants[nom] = mc.Contact(self.conf, nom, valeur.type)
|
||||
elif valeur.type == 'Relais':
|
||||
if valeur.donnees.get('basculeur') == 'gauche':
|
||||
self.composants[nom] = mc.DemiRelaisBasculeur(self.conf, nom, self, Dir.GAUCHE)
|
||||
elif valeur.donnees.get('basculeur') == 'droite':
|
||||
self.composants[nom] = mc.DemiRelaisBasculeur(self.conf, nom, self, Dir.DROITE)
|
||||
else:
|
||||
self.composants[nom] = mc.Relais(self.conf, nom, self)
|
||||
elif valeur.type == 'Tempo':
|
||||
self.composants[nom] = mc.Tempo(self.conf, nom, valeur.donnees['tempo_blocage'],
|
||||
valeur.donnees['tempo_liberation'])
|
||||
elif valeur.type == 'Resistance':
|
||||
if 'valeur' not in valeur.donnees:
|
||||
v = None
|
||||
else:
|
||||
v = valeur.donnees['valeur']
|
||||
self.composants[nom] = mc.Resistance(self.conf, nom, v)
|
||||
del v
|
||||
elif valeur.type == 'Lampe':
|
||||
self.composants[nom] = mc.Lampe(self.conf, nom)
|
||||
elif valeur.type == 'Diode':
|
||||
d = mc.Diode(self.conf, nom)
|
||||
self.composants[nom] = d
|
||||
self.composants["!" + nom] = d
|
||||
del d
|
||||
|
||||
for nom, valeur in reader.noeuds.items():
|
||||
if valeur.type == "Noeud":
|
||||
self.elements[nom] = me.Noeud(self.conf, nom)
|
||||
elif valeur.type == "P24":
|
||||
self.elements[nom] = me.Potentiel(self.conf, nom, 24.)
|
||||
elif valeur.type == "P0":
|
||||
self.elements[nom] = me.Potentiel(self.conf, nom, 0.)
|
||||
elif valeur.type == "Pulse":
|
||||
if "periode" not in valeur.donnees:
|
||||
p = None
|
||||
else:
|
||||
p = valeur.donnees["periode"]
|
||||
self.elements[nom] = me.PotPulse(self.conf, nom, 24., p)
|
||||
elif valeur.type == "ContactBasculeur":
|
||||
self.elements[nom] = me.ContactBasculeur(self.conf, nom, self)
|
||||
elif valeur.type == "ContactDouble":
|
||||
self.elements[nom] = me.ContactDouble(self.conf, nom, self, orientation=valeur.donnees['orientation'])
|
||||
|
||||
for nom, valeur in reader.condensateurs.items():
|
||||
cnd = me.Condensateur(self.conf, nom, valeur.donnees['capacite'], valeur.donnees['coef_mul_tension'])
|
||||
|
||||
v_amont_schm, v_amont_dir = valeur.get_amont()
|
||||
v_aval_schm, v_aval_dir = valeur.get_aval()
|
||||
|
||||
v_amont = self.elements[v_amont_schm.nom]
|
||||
v_aval = self.elements[v_aval_schm.nom]
|
||||
|
||||
cnd.add_voisins(v_amont, v_aval)
|
||||
v_amont.add_voisins([(cnd, -1, v_amont_dir)])
|
||||
v_aval.add_voisins([(cnd, +1, v_aval_dir)])
|
||||
self.elements[nom] = cnd
|
||||
del cnd
|
||||
|
||||
# Ajout des liens entre relais et contacts
|
||||
try:
|
||||
for nom, valeur in reader.composants.items():
|
||||
if valeur.type == "ContactTravail":
|
||||
self.composants[valeur.donnees["relais"]].add_travail(nom)
|
||||
elif valeur.type == "ContactRepos":
|
||||
self.composants[valeur.donnees["relais"]].add_repos(nom)
|
||||
|
||||
for nom, valeur in reader.noeuds.items():
|
||||
if valeur.type == "ContactBasculeur":
|
||||
self.composants[valeur.donnees["relais"] + ".gauche"].add_contact(nom)
|
||||
self.composants[valeur.donnees["relais"] + ".droite"].add_contact(nom)
|
||||
elif valeur.type == "ContactDouble":
|
||||
self.composants[valeur.donnees["relais"]].add_double(nom)
|
||||
except KeyError:
|
||||
raise GraphStructException("Mauvais identificateur de relais dans un contact")
|
||||
|
||||
# Construction des branches
|
||||
for branche in reader.branches:
|
||||
nom = self.branch_id()
|
||||
br = me.Branche(self.conf, nom, [self.composants[x.nom] for x in branche.composants])
|
||||
[self.composants[x.nom].reverse() for x in branche.composants if x.nom[0] == '!'] # Diode
|
||||
for c in br.composants:
|
||||
c.branche = br
|
||||
|
||||
v_amont_schm, v_amont_dir = branche.get_amont()
|
||||
v_aval_schm, v_aval_dir = branche.get_aval()
|
||||
|
||||
v_amont = self.elements[v_amont_schm.nom].get_potentiel(v_amont_dir)
|
||||
v_aval = self.elements[v_aval_schm.nom].get_potentiel(v_aval_dir)
|
||||
|
||||
br.add_voisins(v_amont, v_aval)
|
||||
v_amont.add_voisins([(br, -1, v_amont_dir)])
|
||||
v_aval.add_voisins([(br, +1, v_aval_dir)])
|
||||
self.elements[nom] = br
|
||||
del br
|
||||
|
||||
self.vecteur = Vecteur(self.elements)
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""
|
||||
Retourne un composant
|
||||
"""
|
||||
if item in self.composants:
|
||||
res = self.composants[item]
|
||||
else:
|
||||
res = self.elements[item]
|
||||
return res
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self.composants or item in self.elements
|
||||
|
||||
def init_cycle(self):
|
||||
[elt.init_cycle() for elt in self.elements.values()]
|
||||
|
||||
def passe_calcul(self, solutions):
|
||||
[elt.passe_calcul(solutions, self.vecteur.index(elt)) for elt in self.elements.values()]
|
||||
|
||||
def update(self):
|
||||
[elt.update() for elt in self.elements.values()]
|
||||
|
||||
def coherence(self):
|
||||
[x.coherence() for x in self.elements.values()]
|
||||
57
modele/matrice.py
Normal file
57
modele/matrice.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Matrice(object):
|
||||
"""
|
||||
Le système d'équation est construit sur la base d'une matrice A et un vecteur B modélisant le système, tels que
|
||||
A.X = B
|
||||
|
||||
Chaque ligne de la matrice correspond à une équation, correspondant elle-même à un élément du graphe.
|
||||
Les équations sont dans le même ordre que les éléments du graphe.
|
||||
|
||||
Chaque colonne correspond à une inconnue, également prise dans le même ordre que les éléments du graphe.
|
||||
Si l'élément est un Potentiel ou un noeud, l'inconnue est une tension.
|
||||
Si l'élément est une branche, l'inconnue est l'intensité parcourant la branche.
|
||||
"""
|
||||
def __init__(self, graphe):
|
||||
self.vecteur = graphe.vecteur
|
||||
self.mat_A = None
|
||||
self.mat_B = None # Equation matricielle AX = B
|
||||
self.n = len(self.vecteur)
|
||||
|
||||
def init_mat(self):
|
||||
self.mat_A = np.zeros((self.n, self.n))
|
||||
self.mat_B = np.zeros(self.n)
|
||||
|
||||
for ligne, element in enumerate(self.vecteur): # Potentiel constant
|
||||
# assert ligne == self.vecteur.index(element)
|
||||
if element.type_potentiel == element.P_CST:
|
||||
# Potentiel constant : on ajoute une équation indiquant la valeur de la tension pour cette variable
|
||||
self.mat_A[ligne][ligne] = 1.0 # à noter que par construction, ligne == index
|
||||
self.mat_B[ligne] = element.u()
|
||||
|
||||
elif element.type_potentiel == element.P_NOEUD: # On applique la loi des noeuds
|
||||
# Noeud : on ajoute une équation sur la somme des intensités
|
||||
for (voisin, polarite, _) in element.voisins():
|
||||
self.mat_A[ligne][self.vecteur.index(voisin)] = polarite
|
||||
self.mat_B[ligne] = 0.0
|
||||
|
||||
elif element.type_potentiel == element.P_DLT: # On applique la loi d'Ohm
|
||||
self.mat_A[ligne][ligne] = 1.0
|
||||
self.mat_A[ligne][self.vecteur.index(element.amont)] = -1.0 / element.r()
|
||||
self.mat_A[ligne][self.vecteur.index(element.aval)] = 1.0 / element.r()
|
||||
self.mat_B[ligne] = 0.0
|
||||
|
||||
elif element.type_potentiel == element.P_COND: # DU = q/C
|
||||
self.mat_A[ligne][self.vecteur.index(element.amont)] = 1.0
|
||||
self.mat_A[ligne][self.vecteur.index(element.aval)] = -1.0
|
||||
self.mat_B[ligne] = element.coef_mul_tension * element.q / element.capacite
|
||||
|
||||
else:
|
||||
assert False, "Rien à faire là"
|
||||
|
||||
def solve(self):
|
||||
"""
|
||||
Résout le système d'équation et retourne le vecteur solution.
|
||||
"""
|
||||
return np.linalg.solve(self.mat_A, self.mat_B)
|
||||
Reference in New Issue
Block a user