534 lines
16 KiB
Python
534 lines
16 KiB
Python
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']
|
|
|
|
|
|
|
|
def verifier_loi_des_noeuds(self):
|
|
|
|
"""Dans cette méthode, self._voisins contient les branches connectées au nœud, et polarite indique si le courant est entrant (-1) ou sortant (+1).
|
|
La variable courant_total accumule la somme des courants, et self.conf['TOLERANCE_COURANT'] est une petite valeur seuil pour tolérer des imprécisions
|
|
numériques (par exemple, 1e-6)."""
|
|
|
|
courant_total = 0
|
|
for voisin, polarite, _ in self._voisins:
|
|
if polarite == -1:
|
|
courant_total -= voisin.i()
|
|
else:
|
|
courant_total += voisin.i()
|
|
|
|
if abs(courant_total) > self.conf['TOLERANCE_COURANT']:
|
|
raise Exception(f"La loi des nœuds n'est pas respectée pour le noeud {self.nom}. Total courant: {courant_total}")
|
|
|
|
|
|
|
|
|
|
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) |