Σημειωματάτιο Τρίτης 8 Δεκεμβρίου 2015

Μια μικρή βιβλιοθήκη συναρτήσεων για πράξεις με διανύσματα

Παρακάτω φτιάχνουμε διάφορες μικρές συναρτήσεις για πράξεις ανάμεσα σε διανύσματα, συναρτήσεις που χρησιμοποιούμε μετά στο σχεδιασμό διαφόρων σχημάτων.

Τα διανύσματα για μας είναι, κατ' αρχήν, ζεύγη αριθμών (λίστες μήκους 2) όσον αφορά διανύσματα στις 2 διαστάσεις ή τριάδες αριθμών (λίστες μήκους 3) όσον αφορά διανύσματα στις 3 διαστάσεις. Μιλάμε δηλ. για λίστες της μορφής [x, y] ή [x, y, z]. Θα καταβάλλουμε προσπάθεια οι συναρτήσεις μας να δουλεύουν και για τις δύο περιπτώσεις (και ακόμη και για διανύσματα σε μεγαλύτερη διάσταση) εκτός φυσικά όπου αυτό δεν έχει νόημα (π.χ. το εξωτερικό γινόμενο διανυσμάτων ορίζεται μόνο για 3-διάστατα διανύσματα και η περιστροφή διανύσματος γύρω από την αρχή των αξόνων κατά γωνία θ ορίζεται μόνο για 2-διάστατα διανύσματα).

Όλες οι συναρτήσεις παρακάτω που το όνομά τους αρχίζει με V είναι συναρτήσεις για πράξεις σε διανύσματα. Στα σχόλια που φαίνονται παρακάτω περιγράφονται λεπτομερώς.

Έπειτα δίνουμε 3 διαφορετικές μεθόδους υπολογισμού του εμβαδού ενός τριγώνου, όπου οι κορυφές του τριγώνου είναι τα διανύσματα v, w, z. Στην πρώτη συνάρτηση TriangleArea χρησιμοποιούμε τον τύπο βάση επί ύψος διά 2, στη δεύτερη συνάρτηση TriangleAreaNew χρησιμοποιούμε τον τύπο του Ήρωνα και στην τρίτη συνάρτηση TriangleAreaVeryNew χρησιμοποιούμε τον τύπο με το εξωτερικό γινόμενο διανυσμάτων (δείτε τύπο (10) εδώ. Επειδή υλοποιούμε και τις τρεις αυτές συναρτήσεις αμιγώς με τις παραπάνω συναρτήσεις που φτιάξαμε, οι οποίες δουλεύουν και για 2-διάστατα και για 3-διάστατα διανύσματα, έχουμε το πολύ επιθυμητό γεγονός ότι και οι τρεις αυτές συναρτήσεις δουλεύουν ακόμη και στις 3 διαστάσεις (3 μη συνευθειακά σημεία στο χώρο πάντα ορίζουν ένα επίπεδο και ένα τρίγωνο). Στο τέλος του προγράμματος δοκιμάζουμε και τις 3 συναρτήσεις σε ένα τρίγωνο και παρατηρούμε ότι παίρνουμε το ίδιο αποτέλεσμα.

In [11]:
import math
import matplotlib.pyplot as plt

def plot_polygon(L): # Η συνάρτηση αυτή έχει γραφεί σε προηγούμενη διάλεξη.
                     # Εδώ έχουμε μια απλουστευμένη μορφή της.
    """
    L είναι μια λίστα ζευγών (λίστες μήκους δύο, [x, y]) και ζωγραφίζουμε
    το κλειστό πολύγωνο που ορίζουν οι κορυφές αυτές.
    """
    x = [pair[0] for pair in L] + [ L[0][0] ]
    y = [pair[1] for pair in L] + [ L[0][1] ]
    plt.plot(x, y)
 
def Vadd(v, w):
    """
    Επιστρέφει το διάνυσμα v+w
    """
    ret = []
    for i,x in enumerate(v):
        ret.append(x+w[i])
    return ret

def Vneg(v):
    """
    Επιστρέφει το αντίθετο διάνυσμα, -v
    """
    ret = []
    for x in v:
        ret.append(-x)
    return ret

def Vrotate(theta, v):
    """
    Επιστρέφει το v περιστραμμένο κατά theta γύρω από την αρχή των αξόνων, (0,0).
    Εδώ το v πρέπει να είναι διδιάστατο διάνυσμα.
    """
    [x, y] = v # παίρνουμε τις συντεταγμένες του v και πολλαπλασιάζουμε με τον πίνακα περιστροφής
    return [ math.cos(theta)*x-math.sin(theta)*y, math.sin(theta)*x + math.cos(theta)*y ]

def Vmul(t, v):
    """
    Επιστρέφει το διάνυσμα v πολλαπλασιασμένο με τον αριθμό t
    """
    ret = []
    for x in v:
        ret.append(t*x)
    return ret

def Vinner(v, w):
    """
    Επιστρέφει το εσωτερικό γινόμενο των v, w
    """
    s = 0.
    for i,x in enumerate(v):
        s += x*w[i]
    return s

def Vcross(v, w):
    """
    Επιστρέφει το εξωτερικό γινόμενο των 3-διάστατων διανυσμάτων v, w.
    Αν τα v, w είναι 2-διάστατα τους προσθέτει ένα μηδενικό στο τέλος.
    """
    vv = v[:]; ww = w[:] # δημιουργεί αντίγραφα των v, w
    if len(vv)==2:
        vv.append(0.)
    if len(ww)==2:
        ww.append(0.)
    return [ vv[1]*ww[2]-vv[2]*ww[1],
             -( vv[0]*ww[2]-vv[2]*ww[0]),
             vv[0]*ww[1]-vv[1]*ww[0] ]


def TriangleArea(v, w, z):
    """
    Επιστρέφει το εμβαδό του τριγώνου με κορυφές τα διανύσματα v, w, z.
    Χρησιμοποιεί τον τύπο βάση x ύψος / 2, και υπολογίζει τα δύο αυτά ύψη με γεωμετρικές πράξεις
    χρησιμοποιώντας τις παραπάνω πράξεις που υλοποιήσαμε ως συναρτήσεις.
    """
    vw = Vadd(w, Vneg(v)) # vw = w -v
    lvw = math.sqrt(Vinner(vw, vw)) # μήκος του vw.
    u = Vmul(1./lvw, vw) # μοναδιαίο διάνυσμα παράλληλο με το vw
    vz = Vadd(z, Vneg(v)) # vz = z -v
    x = Vadd(v, Vmul( Vinner(vz, u), u )) # το x είναι η ορθή προβολή του z στην πλευρά vw
    xz = Vadd(z, Vneg(x)) # xz είναι το διάνυσμα από την κορυφή z στο ίχνος του ύψους της, x
    lxz = math.sqrt(Vinner(xz,xz)) # το μήκος της xz είναι το ύψος που αντιστοιχεί στην πλευρά vw
    return lvw*lxz*0.5

def TriangleAreaNew(v, w, z):
    """
    Επιστρέφει το εμβαδό του τριγώνου με κορυφές τα διανύσματα v, w, z
    με τον τύπο του Ήρωνα
    """
    vw = Vadd(w, Vneg(v)) # το διάνυσμα vw = w - v
    lvw = math.sqrt(Vinner(vw, vw)) # μήκος του vw
    wz = Vadd(z, Vneg(w)) # το διάνυσμα wz = z - w
    lwz = math.sqrt(Vinner(wz, wz)) # μήκος του wz
    zv = Vadd(v, Vneg(z)) # το διάνυσμα zv = v - z
    lzv = math.sqrt(Vinner(zv, zv)) # μήκος του zv
    s = (lvw+lwz+lzv)/2. # ημιπερίμετρος του τριγώνου
    return math.sqrt(s*(s-lvw)*(s-lwz)*(s-lzv)) #ο τύπος του Ήρωνα

def TriangleAreaVeryNew(v, w, z):
    """
    Επιστρέφει το εμβαδό του τριγώνου με κορυφές τα διανύσματα v, w, z
    με εξωτερικό γινόμενο
    """
    vw = Vadd(w, Vneg(v))
    vz = Vadd(z, Vneg(v))
    cross = Vcross(vz, vw) # cross είναι το εξωτερικό γινόμενο των vz, vw
    l = math.sqrt(Vinner(cross, cross)) # το μήκος του cross
    return 0.5*l

def SquareOnSide(v, w):
    """
    Ζωγραφίζει τετράγωνο με πλευρά vw, δεξιά όπως κοιτάμε από v στο w.
    Τα v, w πρέπει να είναι διδιάστατα διανύσματα.
    """
    vw = Vadd(w, Vneg(v))
    vx = Vrotate(-math.pi/2., vw)
    x = Vadd(vx, v)
    y = Vadd(x, vw)
    plot_polygon([ v, w, y, x])
    
# Παρακάτω δοκιμάζουμε και τις τρεις μεθόδους υπολογισμού εμβαδού που γράψαμε στο τρίγωνο με τις κορυφές v, w, z
# που φαίνονται. Παίρνουμε το ίδιο αποτέλεσμα.
v = [2,1]
w = [3,2]
z = [-1,1]

print "Area is", TriangleArea(v, w, z)
print "Area is", TriangleAreaNew(v, w, z)
print "Area is", TriangleAreaVeryNew(v, w, z)


# Ζωγραφίζουμε το ορθογώνιο τρίγωνο A, B, C και ένα τετράγωνο σε κάθε πλευρά του,
# ένα σχήμα που βλέπουμε πολύ συχνά να συνοδεύει το Πυθαγόρειο θεώρημα.
A = [0, 0]
B = [1, 0]
C = [0, 3]

plot_polygon([A, B, C])
SquareOnSide(A, B)
SquareOnSide(B, C)
SquareOnSide(C, A)

plt.axes().set_aspect('equal')
plt.show()
Area is 1.5
Area is 1.5
Area is 1.5

In []: