#Jcourtin 08/2016
"""
Ce TD a pour but d'implémenter une classe d'objets vecteur en python pour se familiariser avec la notion de classe sur un exemple simple, mais la démarche , très générale, sera toujours la même. Nous corrigerons cet exemple en classe.


L'enjeu d'une classe est l'encapsulation d'un ensemble de propriétés qui sont spécifiques à un objet : 
ex : - obtenir des attributs de l'objet, dimension & composantes 
     - calculer une norme pour un objet vecteur
     - fabriquer un objet vecteur par le produit vectoriel de deux objets
       vecteurs
L'encapsulation signifie que ces informations ou opérations ne seront possibles que pour ce type d'objet ce qui permettra facilement de garantir
qu'une action effectuée est bien autorisée.
On ne risque pas de tenter de faire le produit vectoriel à partir d'un entier ou même d'un tuple.


#############################################################################
Structure de données :
----------------------
On choisit de représenter un vecteur par la donnée de sa dimension et de ses coordonnées sous la forme d'un tuple.
-On pourrait faire plus simple se dispenser de la dimension.
-On pourrait décider de garder la norme en attibut.


CONSTRUCTEUR :
--------------
Le constructeur et l'affichage [càd __str__  --> print(objet)] sont donnés.
Comme son nom l'indique le constructeur construit l'objet :
Cette méthode nous garanti donc que l'objet construit est conforme à l'idée abstraite de sa modélisation (structure de données) mais il doit aussi vérifier que les données fournies pour le faire sont viables.
exemple   : 
    - Le constructeur renseigne la dimension, les composantes.
    - On ne peut pas construire le vecteur si on donne un entier en argument
    
contre-ex :
    Vous pourrez dans cette version construire un "vecteur" en passant 
    une liste ou même une chaine de caractère à la place du tuple.
    Essayez ainsi de mettre en évidence les défauts de cette implémentation.
    


IMPORTANT : toutes les méthodes définies (fonction def) prennent en premier argument "self" qui désigne l'instance de l'objet :  le "soi"

def methode(self, argument):   --devient-->      >>> objet.methode(argument)

En effet on ne peut pas se dispenser de la donnée de l'objet lui même.



----------------------------------------------------------------------------
############################################################################
----------------------------------------------------------------------------
TRAVAIL A FAIRE :     On demande d'implémenter toutes les opérations !!!
----------------------------------------------------------------------------
############################################################################
----------------------------------------------------------------------------



Dans chaque cas il s'agit de coder le corps de fonction des méthodes incompletes, mais aussi de retourner l'objet approprié.
Pensez à réutiliser dans une méthode les méthodes déjà crées 
- pour éviter la duplication de code.
- pour garantir que l'opération est autorisée.

opérations scalaires :
----------------------  
    -> action d'un scalaire sur le vecteur  [norme  3*U  U/4  -U]
    Ces opérations prennent en argument self, et le scalaire


opérations vectorielles :
-------------------------
    -> action de notre instance "self" avec une autre instance 
    que l'on nommera "other".
    Ces opérations prennent en argument self, et un autre objet vecteur
    "other" qui devra être fourni par le client de la méthode.
    
    ex : def prodScalaire(self, other):
    donnera des choses comme
    >>> puissance = vecteurForce.prodScalaire(vecteurVitesse)
    où vecteurForce désigne "self" et vecteurVitesse "other".
    
    
    
VALIDATION : Vous pouvez vérifier la cohérence de vos opérations directement dans la console ou à l'aide des appels tests dans le MAIN tout en bas du programme.

############################################################################
"""



class vecteur():
    
    ##Attributs de la class
    dim=None
    coord=()
    
    
    
    ##Méthodes de la class
    
    #constructeur : mange un tuple des coordonnées et renseigne les attributs
    def __init__(self, tup):

        self.dim=len(tup)      #fixe la dimension
        for c in tup:
            self.coord+=(c,)   #On reConstruit le tuple
            
        #aTTENTION : l n'y a jamais de return dans le constructeur.
    
    
    #Affichage de l'objet :  >>> print(monVecteur)
    def __str__(self):
        myString ="vecteur en "+str(self.dim)+" dimension"
        if (self.dim>1):
            myString+="s\n"
        else:
            myString+="\n"
        myString+="Coordonnées : "+str(self.coord)+"\n"
        return myString

    ##OPERATIONS SCALAIRES
    #norme euclidienne
    def norme(self):
        #
        # Calculer la norme
        #
        return #un scalaire
     
        
    #Opération de signe *    
    def __mul__(self, scalaire):
        tmp=()
        #
        # Calculer les composantes
        #
        return #renvoie un objet vecteur            


    #la multiplication prédédente ne se fait que sous la forme: scalaire*vecteur
    __rmul__=__mul__ #permet la multiplication par la droite:   vecteur*scalaire
    
    
    #negation :            permet de comprendre que -U est en fait -1*U
    def __neg__(self):
        return self.__mul__(-1)
    
    #Opération de signe /
    def __truediv__(self, scalaire):
        #
        # même chose division 
        #
        return #attention il y a une astuce !
    
            
    ##OPERATIONS VECTORIELLES
    
    #Opération de signe +
    def __add__(self, other):   #other est un autre objet vecteur
        #
        #  Calculer les composantes
        #
        return #renvoie un objet vecteur 
    
    #soustraction
    def __sub__(self, other):
        return #renvoie un objet vecteur: attention il y a une astuce !
    
    
    #Produit Scalaire    
    def prodScalaire(self, other):
        #
        #
        #
        return #renvoie un scalaire
      
        
    #produit vectoriel en dimension 3 seulement ....
    def prodVectoriel(self, other):
        #
        #
        #
        #
        return #renvoie un objet vecteur 


############# MAIN #############################################################
if (__name__=='__main__'):
    
    #vecteurs de base
    eX = vecteur((1,0,0)); eY = vecteur((0,1,0)); eZ = vecteur((0,0,1))
    print("*** Affichage des vecteurs de base :")
    print("eX :\n",eX); print("eY :\n",eY); print("eZ :\n",eZ)
    
    
    #addition / normalisation
    print("\n"); print("*** test addition & normalisation : vecteur u")
    u = (eX+eY+eZ)
    print(u)
    print("normalisation de u :")
    print(u/u.norme())
    
    #produit vectoriel de base
    print("\n"); print("*** Test de permutation circulaire :")
    a=eX.prodVectoriel(eY); b=eY.prodVectoriel(eZ); c=eZ.prodVectoriel(eX)
    print("eX ^ eY :\n",a); print("eY ^ eZ :\n",b); print("eZ ^ eX :\n",c)
    
    #produit mixte
    print("\n"); print("*** Test de produit miste : u.(eX ^ eY) =?= eY.(u ^ eX)\n")
    volume1=u.prodScalaire(eX.prodVectoriel(eY))
    volume2=eY.prodScalaire(u.prodVectoriel(eX))
    print("volume : ", volume1," =?= ",volume2)
    
    #opération combinées
    print("\n"); print("*** test opérations combinées :")
    v=(6*eX - 4*eY).prodVectoriel(-eZ/2)
    print(v)
    
    
    