Diskrete Modellierung Wintersemester 2016/17 Martin Mundhenk Uni Jena, Institut f¨ ur Informatik

24. Oktober 2016

3.2 Erzeugen von Datentypen Der Datentyp Charge

Charge ist eine Datentyp f¨ur geladene Teilchen auf einer Fl¨ache (Punktladung). Das elektrische Potential V im Punkt px, y q Potential in px, y q q0 bezgl. p ist k ¨ r einer Punktladung in px0 , y0 q wird beschrieben durch px, y q q V “k¨ . r r px0 , y0 q Dabei ist q die Ladung im Punkt px , y q, 0

0

r ist der Abstand zwischen px, y q und px0 , y0 q 2 und k “ 8.99 ¨ 109 N m ist die Coulombsche C2 Konstante. Unser Ziel ist, das Potential an verschiedenen Punkten der Ebene bezgl. mehrerer Punktladungen zu berechnen. Punktladung p mit Ladung q0

Die API fu ¨r Charge Unser Datentyp Charge soll Folgendes beschreiben: §

eine Ladung an einem Punkt in der Ebene, d.h. den Wert der Ladung q0 und die Koordinaten des Punktes px0, y 0q

§

das Potential, das die Punktladung an bel. Punkt bewirkt px, y q, q0 d.h. k ¨ Abstand von px0, y 0q zu px, y q

Operation Charge(x0,y0,q0)

Beschreibung eine neue Punktladung an Punkt px 0, y 0q mit Ladung q 0

c.potentialAt(x, y) das Potential der Punktladung c an Punkt px , y q str(c)

q0 in (x0,y0)“ zur Darstellung von c als String ” 3.2.2

In Python beschreibt man Datentypen durch Klassen. Die Klasse Charge muss §

die Wert q0, x0, y 0 f¨ur bel. Punktladungen speichern k¨onnen (Konstruktur)

§

f¨ur jeden Punkt px, y q das Potential, das von der Punktladung im Punkt px0, y 0q erzeugt wird, bestimmen k¨onnen (Methoden)

§

eine Punktladung als string darstellen k¨onnen (spezielle Funktionen)

import sys, stdio, math class Charge: def __init__(self, x0, y0, q0): self._xkoord = x0 self._ykoord = y0 self._ladung = q0 def potentialAt(self, x, y): COULOMB = 8.99e9 dx = x - self._xkoord dy = y - self._ykoord r = math.sqrt(dx*dx + dy*dy) if r==0.0: return float('inf') return COULOMB * self._ladung / r def __str__(self): text = str(self._ladung) + ' in (' + str(self._xkoord) + ', ' + str(self._ykoord) + ')' return text def test(): x = float(sys.argv[1]) y = float(sys.argv[2]) c = Charge(0.51, 0.63, 21.3) stdio.writeln(c) stdio.writeln(c.potentialAt(x,y)) if __name__=='__main__': test() 3.2.4

Der Konstruktor spezielle Parameter-Variable Signatur

gew¨ ohnliche Parameter-Variablen def

init ( self, x0, y0, q0 ):

self. xkoord = x0 Instanz-Variablen

self. ykoord = y0

Funktions-Rumpf

self. ladung = q0

3.2.5

Konstruktor-Abarbeitung auf dem Objekt-Level Charge c = Charge(0.1,0.2,3.0) [ init (self,0.1,0.2,3.0)]

self x0 y0 q0

0.1 0.2 3.0

3.2.6

Konstruktor-Abarbeitung auf dem Objekt-Level Charge c = Charge(0.1,0.2,3.0) [ init (self,0.1,0.2,3.0)]

self x0 y0 q0

0.1 0.2 3.0 Charge xkoord

self. xkoord = x0

self x0 y0 q0

0.1 0.2 3.0

3.2.6

Konstruktor-Abarbeitung auf dem Objekt-Level Charge c = Charge(0.1,0.2,3.0) [ init (self,0.1,0.2,3.0)]

self x0 y0 q0

0.1 0.2 3.0

self. xkoord = x0 self. ykoord = y0

self x0 y0 q0

Charge xkoord ykoord 0.1 0.2 3.0

3.2.6

Konstruktor-Abarbeitung auf dem Objekt-Level Charge c = Charge(0.1,0.2,3.0) [ init (self,0.1,0.2,3.0)]

self x0 y0 q0

0.1 0.2 3.0

self. xkoord = x0 self. ykoord = y0 self. ladung = q

self x0 y0 q0

Charge xkoord ykoord ladung 0.1 0.2 3.0

3.2.6

Konstruktor-Abarbeitung auf dem Objekt-Level Charge c = Charge(0.1,0.2,3.0) [ init (self,0.1,0.2,3.0)]

self x0 y0 q0

0.1 0.2 3.0

self. xkoord = x0 self. ykoord = y0 self. ladung = q

self x0 y0 q0

Charge xkoord ykoord ladung 0.1 0.2 3.0

[return self] c

Charge xkoord ykoord ladung

0.1 0.2 3.0 3.2.6

Methoden Signatur

spezielle Parameter-Variable gew¨ ohnliche Parameter-Variablen def potentialAt( self, x, y ): COULOMB = 8.99e9

lokale Variablen

dx = x - self. xkoord Instanz-Variablen dy = y - self. ykoord r = math.sqrt(dx*dx, dy*dy) Funktions-Aufruf if r==0: return float(’inf’)

return Anweisung

return COULOMB * self. ladung / r 3.2.7

Variablen in Methoden §

Parameter-Variablen Bsp.: self, x, y ¨ Aufgabe: Ubergabe von Argumenten beim Aufruf der Methode G¨ultigkeitsbereich (scope): Methode

§

Instanz-Variablen Bsp.: xkoord, ykoord Aufgabe: Speicherung von Werten des Datentyps G¨ultigkeitsbereich (scope): Klasse

§

lokale Variablen Bsp.: dx, dy Aufgabe: Speicherung von tempor¨aren Werten beim Rechnen G¨ultigkeitsbereich (scope): Methode 3.2.8

Spezielle Funktionen und Konventionen Die Funktion str(c) in der API: nach Konvention wird beim Aufruf str(c) die Methode c. str () angewendet. Beim Aufruf von stdio.writeln(c) wird von Python stdio.writeln(str(c)) ausgef¨uhrt (etc.). Die Schreibweise mit Unterl¨ange wird dann benutzt, wenn ein Variablen- oder Methodenname nur innerhalb der Klasse benutzt werden soll. In Python gibt es keine M¨ oglichkeit, die Benutzung dieser Variablen- und Methodennamen zu verbieten. Andere Programmiersprachen bieten Konzepte daf¨ ur.

3.2.9

Erzeugen eines Datentyps Kurze Zusammenfassung

1. Angabe der API § §

Ziel: einfache Benutzung des Datentyps von Klienten Klarheit u ¨ber Struktur des Datentyps: welche Daten und welche Methoden?

2. Implementierung einer Klasse in Python §

§

§

Konstruktur init mit self als erster Parameter-Variable gefolgt von den gew¨ohnlichen Parameter-Variablen Methoden gem¨aß der API self als erster Parameter-Variable gefolgt von den gew¨ ohnlichen Parameter-Variablen spezielle Funktionen, deren Namen von doppelte Unterl¨angen eingeschlossen sind mit self als erster Parameter-Variable

3. Testen der Implementierung 3.2.10

Bsp.: Visualisierung von Potentialen

Wir wollen Punktladungen aus einer Datei einlesen (alle Punkte haben Koordinaten 0 ď x , y ď 1) und die Potentiale aller Punkte mit Koordinaten 0 ď x , y ď 1 graphisch darstellen, z.B. verschiedene Graustufen – hohes Potential ist hell und niedriges Potential ist dunkel.

% more charges.txt 9 .51 .63 -100 .50 .50 40 .50 .72 10 .33 .33 5 .20 .20 -10 .70 .70 10 .82 .72 20 .85 .23 30 .90 .12 -50

Das Problem“ ist die Darstellung von ” Potentialen aus einem unbekannten großen Bereich durch 256 Graustufen.

3.2.11

Welche Datenstruturen wollen wir benutzen?

§

Charge

§

Picture zur Darstellung des Ergebnis-Bildes. Der Zugriff auf die einzelnen Punkte des Bildes ist dabei sehr einfach.

§

Color

§

InStream zum Einlesen der Datei.

3.2.12

Die API von InStream

3.2.13

potential.py # potential.py import sys, stddraw, stdarray from from from from

charge import Charge color import Color picture import Picture instream import InStream

# Einlesen der Eingabedatei und # Erzeugen eines Arrays mit den Punktladungen dateiname = sys.argv[1] eingabe = InStream(dateiname) n = eingabe.readInt() charges = stdarray.create1D(n) for i in range(n): x = eingabe.readFloat() y = eingabe.readFloat() q = eingabe.readFloat() charges[i] = Charge(x,y,q)

# Berechnung des Potentials in jedem # Punkt des Ergebnis-Bildes # und Darstellung des Potentials durch eine Farbe for s in range(aufloesung): for z in range(aufloesung): x = 1.0 * s / aufloesung y = 1.0 * z / aufloesung v = 0.0 for i in range(n): v += charges[i].potentialAt(x,y) v = (255/2.0) + (v/4.0e10) if v255: gray = 255 else: gray = int(v) color = Color(gray,255-gray,255-gray) pic.set(s,pic.height()-1-z, color) stddraw.setCanvasSize(pic.width(),pic.height()) stddraw.picture(pic) stddraw.show()

# Vorbereitung des Ergebnis-Bildes aufloesung = 400 pic = Picture(aufloesung,aufloesung) 3.2.14

Die Umwandlung von Potentialen in Farben kann auf verschiedene Weisen berechnet werden . . .

3.2.15

Turtle-Graphik Der Datentyp Turtle

Idee: Bilder lassen sich mit einem Bleistift malen, indem man wiederholt §

eine Richtung 0˝ ´ 360˝ einnimmt

§

in diese Richtung einen geraden Strich einer bestimmten L¨ange zeichnet

Bsp.: ein gleichseitiges Dreieck malt man durch: nimm Richtung 0˝ ein male einen 10cm langen Strich drehe die Richtung 120˝ nach links male einen 10cm langen Strich drehe die Richtung 120˝ nach links male einen 10cm langen Strich

Die API fu ¨r Turtle Ein Datentyp zum Malen auf diese Art muss § die Stelle und die Richtung speichern, § eine Methode zum Ziehen eines Strichs und ¨ § eine Methode zum Andern der Richtung haben. Operation

Beschreibung

Turtle(x0,y0,a0) eine neue Turtle an Punkt px 0, y 0q mit Richtung a0˝ t.goForward(s)

Turtle t geht s Schritte vorw¨arts und malt dabei einen Strich

t.turnLeft(d)

Turtle t dreht d Grad nach links

str(t)

(x0,y0) mit Richtung a0“ zur Darstellung von t ” als String 3.2.17

Was brauchen wir? Mittels stddraw.line(a,b,x,y) kann man einen Strich von Punkt pa, bq nach Punkt px, y q malen. Wir m¨ussen also aus Punkt pa, bq, Winkel α und Strichl¨ange s den von der Turtle beim Malen erreichten Punkt ausrechnen. pa ` s ¨ cos α, b ` s ¨ sin αq

s

s ¨ sin α

α pa, bq

s ¨ cos α 3.2.18

turtle.py import sys, stddraw, math class Turtle: def __init__(self,x0,y0,a0): self._x = x0 self._y = y0 self._angle = a0 def turnLeft(self,delta): self._angle += delta % 360 def goForward(self,stepnumber): oldx = self._x oldy = self._y self._x += stepnumber * math.cos(math.radians(self._angle)) self._y += stepnumber * math.sin(math.radians(self._angle)) stddraw.line(oldx, oldy, self._x, self._y) def __str__(): return '(' + x0 + ',' + y0 + ') mit Richtung ' + a0

3.2.19

# Der Test-Client liest einen int-Wert n von der Konsole ein # und zeichnet ein n-Eck def test(): n = int(sys.argv[1]) step = math.sin(math.radians(180.0/n)) t1 = Turtle(0, 0, 0) stddraw.setCanvasSize(800,800) stddraw.setXscale(-0.2,1.2) stddraw.setYscale(-0.2,1.2) for i in range(n): t1.goForward(step) t1.turnLeft(360.0/n) stddraw.show() if __name__ == '__main__': test()

3.2.20

Erweiterung der API f¨ ur Turtle Wir wollen der Turtle beim Malen zuschauen k¨onnen und erlauben ihr, zuf¨allige Schritte zu machen (und in anderen Farben zu malen). Operation

Beschreibung

Turtle(x0,y0,a0,steps,delay) eine neue Turtle an Punkt px 0, y 0q mit Richtung a0˝ ; das Malen eines Strichs wird in steps (default 1) Etappen zerteilt und nach jeder Etappe wird das bisher gemalte f¨ ur delay ms (default 10) angezeigt t.goForward(s)

Turtle t geht s Schritte vorw¨arts und malt dabei einen Strich

t.turnLeft(d)

Turtle t dreht d Grad nach links

t.randomStep(s)

Turtle t ¨andert die Richtung zuf¨allig und macht einen Schritt der L¨ange s 3.2.21

Die neue Methode randomStep(s) in turtle.py

import stdrandom Class Turtle: . . . def randomStep(self,steplength): angle = stdrandom.uniformFloat(0.0,360.0) self.turnLeft(angle) self.goForward(steplength)

3.2.22

Brown’sche Bewegung Wir lassen die Turtle eine zuf¨allige Wanderung u¨ber die Ebene machen. # drunk1.py import sys, stddraw python drunk1.py 10000 0.01 import sys, stddraw, stdrandom from turtle import Turtle trials = int(sys.argv[1]) step = float(sys.argv[2]) stddraw.setPenRadius(0.0) t = Turtle(0.5,0.5,0.0,1,0) for x in range(trials): angle = stdrandom.uniformFloat(0.0,360.0) t.turnLeft(angle) t.goForward(step) stddraw.show() 3.2.23

Wir k¨onnen jetzt auch viele Turtles gleichzeitig malen lassen. # drunkn.py import sys, stddraw, stdrandom from color import Color from turtle import Turtle

python drunkn.py 10 10000 0.01 n = int (sys.argv[1]) trials = int(sys.argv[2]) step = float(sys.argv[3]) stddraw.setPenRadius(0.0) # Erzeuge ein Array aus n turtles mit # zuf¨ alligen Startpositionen. turtles = stdarray.create1D(n) for t in range(n): x = stdrandom.uniformFloat(0.1,0.9) y = stdrandom.uniformFloat(0.1,0.9) turtles[t] = Turtle(x,y,0.0,1,1) # Lasse sie alle f¨ ur trials Schritte laufen. for t in range(trials): for t in turtles: t.randomStep(step) stddraw.show()

Zusammenfassung

Wir haben die ersten Schritte dazu gesehen, wie man §

einen Datentyp entwirft,

§

eine API daf¨ur angibt und

§

den Datentyp in Python als Klasse implementiert.

Wir werden noch mehr Schritte dazu sehen.