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)