cocosim_python/modele/elements.py
2024-06-27 11:12:33 +02:00

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)