Waldbrandsimulation/ Druckversion
Hauptteil
BearbeitenDieses Wikibook beschäftigt sich mit der softwaretechnischen Simulation von Waldbränden mit der Hilfe von strukturierten Java-Programmen. Zur Modellierung des räumlich diskreten dynamischen Systems wird ein zellulärer Automat implementiert.
Algorithmus Forest Fire
BearbeitenIn der angewandten Mathematik ist das Waldbrandmodell Forest Fire ein dynamisches System, das eine selbstorganisierte Kritikalität aufweisen kann. Dies ist dann der Fall, wenn das System weitgehend unabhängig von der Wahl der Anfangsparameter mit der Zeit von selbst einen kritischen Punkt erreicht. Hierbei entstehen typischerweise spontan und allein aufgrund einer wohldefinierten Interaktion einzelner Systemelemente komplexe Strukturen. In Bezug auf ein Waldgebiet bedeutet dies, dass je nach Baumdichte und Geometrie der Anordnung durch einen einzigen Blitzschlag, der einen einzelnen Baum entzündet, ein mehr oder weniger großer Waldbrand entstehen und sich ausbreiten kann. Ein einziges initiales Ereignis kann auf diese Weise auch mehrere Brandherde verursachen, oder ein ganzer Wald kann komplett niedergebrannt werden.
Frühe Versionen des entsprechenden Algorithmus gehen auf Christopher L. Henley (1989) sowie auf Barbara Drossel und Franz Schwabl (1992) zurück.
→ Siehe auch englischsprachiger Wikipedia-Artikel Forest-fire model.
-
960 Videoframes in 38,4 Sekunden (25 Frames pro Sekunde).
-
1200 Videoframes in Full HD (1920 x 1080 Bildpunkte).
Parameter:
Anzahl der initialen Blitzschläge im gesamten Gebiet = 8
Feuerausbreitungswahrscheinlichkeit auf Nachbarbaum = 1,0
Baumlöschwahrscheinlichkeit = 0,2
Programmbestandteile
BearbeitenDas hier vorgestellte Java-Programm simuliert ein rechteckiges Waldgebiet. Hierzu werden mehrere Java-Klassen verwendet, die im Folgenden beschrieben werden.
PseudoRandom
BearbeitenFür die Berechnung eine Folge von Pseudozufallszahlen wird ausgehend von einem Startwert (Englisch auch seed für Samen beziehungsweise Saatgut) mit einem primitiven Polynom eine ganzzahlige positive Zahlenfolge berechnet. Hierfür wird eine primitive Polynomfolge verwendet:[1][2]
Für den Divisor ist eine möglichst große Primzahl zu wählen. Für den Faktor kann eine teilerfremde ganze Zahl ausgesucht werden, die in der Nähe von liegt. Durch einen positiven ganzzahligen Startwert und die ganzzahligen Modulo-Divisionen wird sichergestellt, dass für die Zahlen der Polynomfolge stets gilt:
Bei der Multiplikation wird die aktuelle Zahl also stets mit multipliziert, und durch die anschließende Modulo-Division durch ist die nächstfolgende Zahl stets kleiner als . Durch diesen Umstand wird erreicht, dass bei wiederholter Ausführung der Berechnungen jede Zahl von 1 bis genau einmal erzeugt wird, bevor der Wert erneut erreicht wird. Danach wiederholt sich diese Sequenz zyklisch:
- :
Zur Veranschaulichung ist im Folgenden die sehr kurze Polynomfolge mit der Primzahl und dem Koeffizienten sowie dem Startwert angegeben, die aus einer Sequenz mit nur verschiedenen Zahlen besteht:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
1 | 2 | 4 | 8 | 16 | 32 | 27 | 17 | 34 | 31 | 25 | 13 | 26 | 15 | 30 | 23 | 9 | 18 | 36 | 35 | 33 | 29 | 21 | 5 | 10 | 20 | 3 | 6 | 12 | 24 | 11 | 22 | 7 | 14 | 28 | 19 | 1 |
Im praktischen Fall mit einer erheblich größeren Länge der Sequenz können zum Beispiel die beiden Konstanten 2.147.483.647 und 15.015 eingesetzt werden:
mit
In dieser Polynomfolge wird für die Primzahl die achte Mersenne-Primzahl zur Basis Zwei verwendet. Der Koeffizient des Polynoms wird hier als Produkt aus den fünf ersten ungeraden Primzahlen 3, 5, 7, 11 und 13 zusammengesetzt, die in ihrer Eigenschaften als Primzahlen untereinander und zu teilerfremd sind.
Zur Berechnung einer neuen Zahl sind lediglich eine ganzzahlige Multiplikation und eine ganzzahlige Modulo-Division erforderlich, so dass sich ein sehr geringer Rechenaufwand und somit - insbesondere bei Simulationen mit zahlreichen pseudozufälligen Entscheidungen - eine kurze Laufzeit des Programms ergibt. Bei der erforderlichen Multiplikation wird maximal die ganze Zahl erreicht, die kleiner als ist, und deswegen mit 64-Bit-Computersystemen sehr effizient und schnell berechnet werden kann.
Wenn noch mehr verschiedene Pseudozufallszahlen innerhalb einer Sequenz erwünscht sind, können die beiden Konstanten vergrößert werden. Der ganzzahlige Datentyp mit Vorzeichen erlaubt in 64-Bit-Computersystemen in der Regel die Darstellung der größten ganzen Zahl , also gut drei Trillionen.[3] Wenn der Zahlenraum für positive ganze Zahlen besser ausgenutzt werden soll, um noch mehr verschiedene Pseudozufallszahlen in einer Sequenz zu erhalten, können alternativ beispielsweise die beiden Konstanten m' = 9.095.665.192.937 als eine sehr große Primzahl[4] sowie a' = 969.969 als das zu m' teilerfremde Produkt kleiner Primzahlen verwendet werden, deren Produkt mit einem Zahlenwert von knapp drei Trillionen immernoch kleiner als die größte in 64-Bit-Computersystemen effizient zu verarbeitende ganze Zahl ist:
- mit
Java-Programm
BearbeitenDie Java-Klasse PseudoRandom erzeugt pseudozufällige Zahlenfolgen, um die scheinbar zufällige Ausbreitung von Bäumen und Flammen simulieren zu können.
Mit dem Aufruf der Methode setStartValue (long startwert) kann ein beliebiger ganzzahliger Startwert gesetzt werden, von welchem ausgehend eine definierte Pseudozufallszahlenfolge generiert wird. Falls eine scheinbar zufällige Zahlenfolge gewünscht ist, kann der Startwert mit Hilfe der parameterlosen Methode setStartValueToSystemTime () auf die aktuelle Systemzeit in Millisekunden gesetzt werden, die vom 1. Januar 1970 an gemessen wird.
Die Methode long nextInteger (long max) gibt eine ganze Zahl z mit der Eigenschaft zurück. Die ganzen Zahlen der Folge können mit Hilfe der kleinsten nicht auftretenden ganzen Zahl als Gleitkommazahl auf das Intervall zwischen Null und Eins normiert werden, so dass gilt:
Eine solche reelle Zahl wird durch die Methode double nextProbability () zurückgegeben.
Mit dem folgenden Java-Programm mit der Java-Klasse PseudoRandom können entsprechende Pseudozufallszahlen erzeugt werden:
→ Java-Programm "PseudoRandom"
SimForestModel
BearbeitenDie Java-Klasse SimForestModel dient der Implementierung eines Modells für die Umsetzung einer Simulation von Waldbränden in einer rechteckigen Fläche.
Die Erdoberfläche in einem rechteckigen Waldgebiet aus mal Zellen kann aus vier verschiedenen Elementen zusammengesetzt werden:
- Unbewachsener Waldboden (SimForestModel.SOIL)
- Baum (SimForestModel.TREE)
- Wasser (SimForestModel.WATER)
- Brennender Baum (SimForestModel.FIRE)
Die entsprechenden öffentlichen Konstanten für diese Aufzählung sind in der Java-Klasse "SimForestModel" definiert:
// Klassenkonstanten fuer die Klassenvariablen currentGround und nextGround
public final static long SOIL = 0;
public final static long TREE = 1;
public final static long WATER = 2;
public final static long FIRE = 3;
Das Modell verwendet zwei zweidimensionale Datenfelder (Arrays), um aus einem bestehenden Waldgebiet, das in der Instanzvariablen SimForestModel.currentGround gespeichert ist, ein nächstes, simuliertes Waldgebiet zu berechnen, das in der Instanzvariable SimForestModel.nextGround zwischengespeichert wird. Jedes Waldelement enthält einen Zahlenwert des Datentyps "long", in dem die vier oben genannten und aufgezählten Werte gespeichert werden können. Mit dem Konstruktor SimForestModel (int x, int y) der Klasse werden die Breite x und die Höhe h des rechteckigen Waldgebiets festgelegt, und die beiden zweidimensionalen Datenfelder SimForestModel.currentGround und SimForestModel.nextGround werden erzeugt:
/* Konstruktor fuer die Initialisierung von Instanzen der Klasse SimForestModel.
* @param x: Fuer die horizontale Groesse in Pixeln
* @param y: Fuer die vertikale Groesse in Pixeln
*/
public SimForestModel (int x, int y)
{
this.sizeX = x;
this.sizeY = y;
this.currentGround = new long [x] [y];
this.nextGround = new long [x] [y];
}
Für das hier implementierte Modell gelten die folgenden Regeln:
- Auf unbewachsenem Waldboden wächst mit der Wahrscheinlichkeit ein neuer Baum.
- Ein Baum entzündet sich mit der Wahrscheinlichkeit durch einen Blitzschlag, weil auf dem Waldgebiet mit Bäumen in jedem Jahr Blitze an zufälligen Orten einschlagen.
- Ein Baum fängt mit der Wahrscheinlichkeit Feuer, wenn ein Nachbarbaum brennt.
- Nach dem Erlöschen ist in einer Zelle unbewachsener Waldboden vorhanden.
- Eine Wasserzelle kann nicht mit Bäumen bewachsen sein.
Ablauf einer Simulation mit einer ständig variierenden Anzahl von Bäumen :
- In der im Zeitraffer ablaufenden Simulation wird für jedes unbewachsene Feld Waldboden anhand der Wahrscheinlichkeit für den jährlichen Baumbewuchs , ob auf einem Stück unbewachsenen Waldboden im neuen Jahr ein neuer Baum wächst. Dieser Wert wird mit dem Methodenaufruf model.setTreeGrowthIndex (pBaum) festlegt.
- Anhand der jährlichen Häufigkeit wird für das gesamte Gebiet simuliert, dass auf zufälligen Punkten des Gebiets insgesamt Gewitterblitze einschlagen. Dieser Wert wird mit dem Methodenaufruf model.setAnnualNumberOfLightnings (hBlitz) festgelegt.
- Wenn ein Blitz einen Baum trifft, dann fängt dieser Feuer (rot = SimForestModel.FIRE) und überträgt die Flammen anhand der Ausbreitungswahrscheinlichkeit , auf benachbarten Bäume, die dann ebenfalls abbrennen. Dieser Wert wird mit dem Methodenaufruf model.setProbabilitySpreadOfForestFire (pFeuer) festgelegt. Abgebrannte Bäume hinterlassen unbewachsenen Waldboden.
- Die Wahrscheinlichkeit bestimmt, wie lange ein brennender Baum im Mittel noch in Flammen steht, nachdem er Feuer gefangen hat. Dieser Wert wird mit dem Methodenaufruf model.setFireExtinctionIndex (pLoesch) festgelegt. Erst nachdem alle Flammen eines Jahres erloschen sind und somit nicht mehr auf andere Bäume übertragen werden können, wird die jährliche Simulation für den neuen Baumbewuchs fortgesetzt (siehe oben, erster Punkt).
Der Baumwuchs in einem Jahr wird mit der Methode boolean simulateYear () simuliert:
/*
* Methode fuer die Simulation eines Jahres.
* Gibt zurueck, ob der Wald brennt.
*/
public boolean simulateYear ()
{
simulateTreeGrowth ();
copyNextToCurrentGround ();
boolean forestIsBurning = simulateLightnings ();
return forestIsBurning;
}
Die Bäume, die innerhalb eines Jahres auf unbewachsenem Waldboden (SOIL) neu wachsen, werden mit dem Methodenaufruf simulateTreeGrowth () simuliert:
/*
* Methode fuer die Simulation des jaehrlichen Baumwuchses.
*/
private void simulateTreeGrowth ()
{
for (int y = 0; y < sizeY; y++)
{
for (int x = 0; x < sizeX; x++)
{
long currentState = currentGround [x] [y];
nextGround [x] [y] = currentState;
if (currentState == SOIL)
{
newTree (x, y);
}
}
}
}
Das Wachstum eines Baumes (TREE) in einer bestimmten Zelle (x, y) des Waldgebiets wird mit dem Methodenaufruf newTree (int x, int y) simuliert:
/*
* Methode zum Simulieren des Wachstums eines neuen Baumes.
*/
private void newTree (int x, int y)
{
double probability = PseudoRandom.nextProbability ();
if (probability < treeGrowthIndex)
{
nextGround [x] [y] = TREE;
}
}
Die Blitzschläge, die innerhalb eines Jahres erfolgen, werden mit dem Methodenaufruf boolean simulateLightnings () simuliert:
/*
* Methode fuer die Simulation eines Jahres.
* Gibt zurueck, ob der Wald brennt.
*/
private boolean simulateLightnings ()
{
boolean forestIsBurning = false;
for (long i = 0; i < annualNumberOfLightnings; i++)
{
boolean forestIsIgnited = simulateLightning ();
forestIsBurning = forestIsBurning || forestIsIgnited;
}
return forestIsBurning;
}
Ein einzelner Blitzschlag wird mit dem Methodenaufruf boolean simulateLightning () simuliert:
/*
* Methode fuer die Simulation eines Blitzschlags.
* Gibt zurueck, ob ein Baum entzuendet wurde.
*/
private boolean simulateLightning ()
{
int locationX = (int) PseudoRandom.nextInteger (sizeX);
int locationY = (int) PseudoRandom.nextInteger (sizeY);
boolean forestIsBurning = false;
if (currentGround [locationX] [locationY] == TREE)
{
currentGround [locationX] [locationY] = FIRE;
forestIsBurning = true;
}
return forestIsBurning;
}
Die Simulation eines Waldbrands während eines Jahres erfolgt mit der Methode long simulateBurning ():
/*
* Methode fuer die Simulation eines Waldbrands
* Gibt zurueck, wie viele Baeume des Waldes brennen
*/
public long simulateBurning ()
{
long numberOfTreesBurning = 0;
for (int y = 0; y < sizeY; y++)
{
for (int x = 0; x < sizeX; x++)
{
long currentState = currentGround [x] [y];
nextGround [x] [y] = currentState;
if (currentState == TREE)
{
if (fireNextToTree (x, y))
{
nextGround [x] [y] = FIRE;
numberOfTreesBurning++;
}
}
else if (currentState == FIRE)
{
double probability = PseudoRandom.nextProbability ();
if (probability < fireExtinctionIndex)
{
nextGround [x] [y] = SOIL;
}
else
{
numberOfTreesBurning++;
}
}
}
}
copyNextToCurrentGround ();
return numberOfTreesBurning;
}
Hierbei wird mit der Methode boolean fireNextToTree (int x, int y) für jeden Baum in einer bestimmten Zelle (x, y) des Waldgebiets untersucht, ob einer der benachbarten acht Bäume brennt und seine Flammen mit der Wahrscheinlichkeit probabilitySpreadOfForestFire auf den untersuchten Baum überträgt. Diese Wahrscheinlichkeit gilt unmittelbar für die direkt benachbarten vier Waldelemente (oben, rechts, unten und links). Für die um den Faktor weiter entfernten diagonal benachbarten Waldelemente wird diese Wahrscheinlichkeit um diesen Faktor reduziert:
/*
* Methode fuer die Ausbreitung des Waldbrands
* Gibt zurueck, ob ein benachbarter Baum brennt
*/
private boolean fireNextToTree (int x, int y)
{
boolean fireNextToTree = false;
double probability = PseudoRandom.nextProbability ();
if (probability < probabilitySpreadOfForestFire)
{
fireNextToTree = ((x > 0) && (currentGround [x - 1] [y] == FIRE))
|| ((x < (sizeX - 1)) && (currentGround [x + 1] [y] == FIRE))
|| ((y > 0) && (currentGround [x] [y - 1] == FIRE))
|| ((y < (sizeY - 1)) && (currentGround [x] [y + 1] == FIRE));
}
if (!fireNextToTree && (probability < (probabilitySpreadOfForestFire / 4)))
{
fireNextToTree = ((x > 0) && (y > 0) && (currentGround [x - 1] [y - 1] == FIRE))
|| ((x < (sizeX - 1)) && (y > 0) && (currentGround [x + 1] [y - 1] == FIRE))
|| ((x > 0) && (y < (sizeY - 1)) && (currentGround [x - 1] [y + 1] == FIRE))
|| ((x < (sizeX - 1)) && (y < (sizeY - 1)) && (currentGround [x + 1] [y + 1] == FIRE));
}
return fireNextToTree;
}
Mit dem folgenden Java-Programm mit der Java-Klasse SimForestModel können Modelle zur Simulation von Waldbränden erzeugt und verwaltet werden:
→ Java-Programm "SimForestModel"
SimForestFrame
BearbeitenDie Java-Klasse SimForestFrame realisiert als Erweiterung der Java-Standardklasse javax.swing.JFrame (Basisklasse ist java.awt.Component, und der Name des Software-Pakets "awt" steht für "advanced windows toolkit", was ungefähr als "fortgeschrittener Fenster-Werkzeugsatz" übersetzt werden kann) eine stetig aktualisierte graphische Ausgabe von Inhalten der Klasse SimForestModel:
- java.awt.Component
- java.awt.Container
- java.awt.Window
- java.awt.Frame
- javax.swing.JFrame
- SimForestFrame
- javax.swing.JFrame
- java.awt.Frame
- java.awt.Window
- java.awt.Container
Für die graphische Ausgabe werden Instanzen der Java-Klassen java.awt.image.BufferedImage und java.awt.image.WritableRaster verwendet. Eine Instanz der Klasse SimForestFrame wird mit der überschriebenen parameterlosen Java-Standardmethode init () initialisiert und mit der überschriebenen, öffentlichen und typengebundenen Java-Standardmethode paint () ausgegeben. Die Methode paint () benötigt dafür als Parameter eine Instanz der Java-Klasse java.awt.Graphics.
Zur Synchronisation der graphischen Ausgabe wird eine Instanz der Java-Standardklasse java.awt.event.ActionListener implementiert. Hierbei handelt es sich um ein Java-Interface, das Ereignisse der Klasse java.awt.event.ActionEvent empfängt: Ereignisse zur aktualisierten graphischen Ausgabe werden mit Hilfe einer Instanz der Java-Klasse javax.swing.Timer ausgelöst. Dieser Zeitgeber wird durch den Aufruf seines Konstruktors javax.swing.Timer (1, this) mit einem Verzögerungsparameter (hier eine Millisekunde) als ActionListener erstellt, und die Instanz der Klasse javax.swing.JFrame wird von diesem Zeitgeber durch den Aufruf seiner Methode addActionListener () automatisch intern registriert. Der Verzögerungsparameter wird verwendet, um die Verzögerung der Ereignisse in Millisekunden festlegen zu können.
public final class SimForestFrame extends javax.swing.JFrame implements java.awt.event.ActionListener
{
final private static int fireDelay = 1; // Verzögerung während eines Wandbrands in Millisekunden
final private static int annualDelay = 1000; // Verzoegerung nach einem simulierten Jahr in Millisekunden
private javax.swing.Timer timer = new javax.swing.Timer (annualDelay, this); // Fuer die Anzeige des JFrames mit einer Millisekunde Verzoegerung
}
Der Konstruktor SimForestFrame (int x, int y, long count) zur Initialisierung der Ausgabe der Simulation ist folgendermaßen gestaltet, um ein Waldgebiet mit der Breite x und der Höhe y für count Jahre zu erzeugen:
/* Konstruktor fuer die Initialisierung von Instanzen der Klasse SimForestFrame
* @param x: Fuer die horizontale Groesse in Pixeln
* @param y: Fuer die vertikale Groesse in Pixeln
* @param count: Fuer die Anzahl der Simulationsdurchlaeufe in Jahren
*/
public SimForestFrame (int x, int y, long count)
{
super (title);
this.model = new SimForestModel (x, y);
this.sizeX = x;
this.sizeY = y;
this.numberOfYears = count;
// Rahmengroesse, Hintergrund und das Layout werden gesetzt
this.setSize (this.sizeX + 2 * offset, this.sizeY + 2 * offset + offsetTop);
this.setBackground (java.awt.Color.BLACK);
this.bufferedImage = new java.awt.image.BufferedImage (this.sizeX, this.sizeY, java.awt.image.BufferedImage.TYPE_INT_RGB);
this.imageRaster = bufferedImage.getWritableTile (0, 0);
this.setDefaultCloseOperation (javax.swing.WindowConstants.EXIT_ON_CLOSE);
}
Die Simulation wird mit dem Aufruf der Methode init () gestartet. Bevor innerhalb der Methode init () die graphische Ausgabe mit dem Aufruf der Methode paint () zum ersten Mal erfolgt, wird der bereits registrierte javax.swing.Timer durch den Aufruf seiner Methode start () gestartet:
/*
* Methode fuer die Initialisierung einer Instanz von SimForestFrame
*/
public void init ()
{
java.awt.Graphics2D graphics2D = bufferedImage.createGraphics ();
this.timer.start ();
this.paint (graphics2D);
this.setVisible (true);
this.setAlwaysOnTop (true);
}
Sobald der Zeitgeber mit der gebundenen Methode start () aus der Klasse javax.swing.Timer in der Methode init () der Klasse SimForestFrame gestartet wurde, wartet dieser jeweils, bevor ein Ereignis der Klasse java.awt.event.ActionEvent an den registrierten java.awt.event.ActionListener auslöst werden kann. Wenn das Ereignis ausgelöst wurde, wird die Methode actionPerformed (java.awt.event.ActionEvent event) des Objekts mit einem Parameter event der Klasse java.awt.event.ActionEvent aufgerufen, die schließlich wiederum die Java-Standardmethode java.awt.Component.repaint () aufruft, die intern dann wiederum die Standard-Methode paint () aufruft. Am Ende der Simulation wird der Zeitgeber durch den Aufruf der gebundenen Methode stop () aus der Klasse javax.swing.Timer angehalten. Die aus der Basisklasse java.awt.event.ActionListener überschriebene Standardmethode actionPerformed (java.awt.event.ActionEvent event) für die synchronisierte Aktualisierung einer Instanz der Klasse SimForestFrame sieht folgendermaßen aus:
public void actionPerformed (java.awt.event.ActionEvent event)
{
long numberOfTreesBurning = 0;
if (forestIsBurning)
{
numberOfTreesBurning = model.simulateBurning ();
forestIsBurning = (numberOfTreesBurning > 0);
if (forestIsBurning)
{
timer.setDelay (fireDelay);
}
else
{
timer.setDelay (annualDelay);
}
}
else if (currentYear < numberOfYears)
{
currentYear++;
forestIsBurning = model.simulateYear ();
}
else
{
timer.stop ();
}
java.lang.String currentTitel = title + " / year " + currentYear + " / " + " burning trees = " + numberOfTreesBurning;
setTitle (currentTitel);
repaint ();
}
In der privaten Instanzvariable forestIsBurning wird der boolesche Wert "true" gespeichert, wenn es noch mindestens einen brennenden Baum im Waldgebiet gibt. Falls dies der Fall ist, wird das entsprechende Jahr so lange simuliert, bis kein Baum mehr brennt. Ansonsten wird mit der Laufvariable SimForestFrame.currentYear das aktuelle Jahr bis zur Anzahl der gewünschten zu simulierenden Jahre SimForestFrame.numberOfYears hochgezählt.
Mit dem Aufruf der Methode java.awt.Toolkit.getDefaultToolkit ().sync () am Ende der Standardmethode paint (java.awt.Graphics graphics) zur graphischen Darstellung der Instanz graphics wird sichergestellt, dass die graphische Anzeige vollständig aktualisiert ist, bevor durch den Zeitgeber ein neues Ereignis (java.awt.event.ActionEvent) ausgelöst wird:
public void paint (java.awt.Graphics graphics)
{
for (int y = 0; y < sizeY; y++)
{
for (int x = 0; x < sizeX; x++)
{
long groundState = model.getGroundState (x, y);
if (groundState == SimForestModel.SOIL)
{
imageRaster.setPixel (x, y, SoilColour);
}
else if (groundState == SimForestModel.TREE)
{
imageRaster.setPixel (x, y, TreeColour);
}
else if (groundState == SimForestModel.WATER)
{
imageRaster.setPixel (x, y, WaterColour);
}
else if (groundState == SimForestModel.FIRE)
{
imageRaster.setPixel (x, y, FireColour);
}
}
}
graphics.drawImage (bufferedImage, offset, offset + offsetTop, null);
java.awt.Toolkit.getDefaultToolkit ().sync (); // Dieser Methodenaufruf stellt sicher, dass die Ausgabe vollständig aktualisiert ist
}
Mit dem folgenden Java-Programm mit der Java-Klasse SimForestFrame kann die Bildschirmanzeige von Waldbrandsimulationen realisiert werden:
→ Java-Programm "SimForestFrame"
Aufruf des Programms
BearbeitenMit dem Aufruf der folgenden öffentlichen Hauptmethode main (java.lang.String [] arguments) kann eine 50-jährige Simulation mit 800 Waldbodenfeldern in der Breite und 600 Waldbodenfeldern in der Höhe berechnet und graphisch dargestellt werden. Der Aufruf des Konstruktors SimForestFrame (int x, int y, long count) erzeugt eine entsprechende Instanz frame der Klasse SimForestFrame, wobei dieser automatisch den Konstruktor SimForestModel (int x, int y) der Java-Klasse SimForestModel aufruft und somit eine Instanz model dieser Klasse erzeugt. Mit dem Unterprogramm init (SimForestModel model) kann die Instanz model von SimForestModel initialisiert werden.
Mit den beiden gebundenen Methoden getWidth () und getHeight () können die Abmessungen des simulierten rechteckigen Waldgebiets abgerufen werden. Von der Methode model.getGroundState (x, y) wird der Zustand des Waldbodenelements an der Stelle (x, y) zurückgegeben, und mit dem Aufruf der Methode model.setGroundState (x, y, status) wird der gewünschte ganzzahlige Zustand status des Waldbodenelements an der Stelle (x, y) gesetzt. Die vier gültigen Zustände sind in den Konstanten SOIL, TREE, WATER und FIRE der Klasse SimForestModel definiert.
// Methode init zum Initialisieren einer Instanz von SimForestModel mit einem Waldgebiet
private static void init (SimForestModel model)
{
model.setTreeGrowthIndex (0.1); // Wahrscheinlichkeit des jährlichen Baumwuchses in einem Waldbodenelement
model.setAnnualNumberOfLightnings (8); // Anzahl der zufällig verteilten Gewitterblitzschlaege im gesamten Waldgebiet
model.setProbabilitySpreadOfForestFire (0.5); // Wahrscheinlichkeit des Uebertretens von Feuer von einem brennenden Baum auf einen benachbarten Baum
model.setFireExtinctionIndex (0.01); // Wahrscheinlichkeit des Erloeschens eines brennenden Baumes
int sizeX = model.getWidth (); // Breite des simulierten Waldgebiets in Pixel
int sizeY = model.getHeight (); // Hoehe des simulierten Waldgebiets in Pixel
long status = model.getGroundState (0, 0); // Abfrage des Zustands an der linken oberen Stelle (0, 0) des Waldgebiets
model.setGroundState (0, 0, SimForestModel.TREE); // Setzen eines Baums an der linken oberen Stelle (0, 0) des Waldgebiets
}
// Hauptprogramm main
public static void main (java.lang.String [] arguments)
{
PseudoRandom.setStartValueToSystemTime ();
final int width = 800;
final int height = 600;
final int numberOfYears = 50;
SimForestFrame frame = new SimForestFrame (width, height, numberOfYears);
SimForestModel model = frame.getModel ();
init (model);
frame.init ();
}
Nachwort
BearbeitenDie hier angegebenen strukturierten Programmbeispiele können ohne große Umstände in jede andere strukturierte Programmiersprache übertragen werden. Durch die Modifikation und Ergänzung der angegebenen Algorithmen können ohne große Umstände anders gestaltete Erdoberflächen berechnet werden.
Es wäre erfreulich, wenn dieses Wikibook zu einem besseren und tieferen Verständnis der strukturierten Programmierung und als Einstieg in die Technik der Programmierung von Simulationsrechnungen mit zellulären Automaten beitragen kann.
Die folgende Bildergalerie zeigt einige mit dem hier vorgestellten Algorithmus erzeugte Waldbrände:
Danksagung
BearbeitenDer Hauptautor dankt seinem ehemaligen Kollegen und Nutzer eines Gepard-Computers Dr.-Ing. Thomas Wittich, der ihm 1988 die Programmiersprache Modula-2 vorgestellt und nähergebracht hat. Dies hat dazu geführt, dass der Hauptautor auch den Nachfolger Oberon und das damit programmierte multitasking-fähige Betriebssystem Oberon System mit graphischer Oberfläche kennengelernt hat, mit welchem er Anfang der 1990er Jahre nicht nur seine Dissertation erstellt, sondern für das er auch die Simulation "ForestFire" als Demonstrationsprojekt für Hintergrundprozesse programmiert hatte.
Literatur
Bearbeiten- Lars Dingeldein: A Cellular Automata Based Forest-Fire Model, Universität Frankfurt, 2020
- Richard D. Zinck, Volker Grimm, Benjamin M. Bolker, Donald L. DeAngelis: Unifying Wildfire Models from Ecology and Statistical Physics, Helmholtz Centre for Environmental Research–UFZ, Department of Ecological Modelling, in: The American Naturalist, Volume 174, Number 5, University of Chicago, 2009
- Barbara Drossel, Franz Schwabl: "Self-organized critical forest-fire model". Physical Review Letters. American Physical Society (APS). 69 (11): 1629–1632, 1992
- Christopher L. Henley: Self-organized percolation: a simpler model, Bulletin of the American Physical Society, 34, 838, 1989
Einzelnachweise
Bearbeiten- ↑ Derrick Henry Lehmer, Mathematical Methods in Large-scale Computing Units, in: Proceedings of a Second Symposium on Large-Scale Digital Calculating Machinery, 14. September 1949, Seiten 141 bis 146, Harvard University Press, Cambridge, Massachusetts, 1951
- ↑ Donald Ervin Knuth: 3.2.1. The Linear Congruential Method, in: The Art of Computer Programming, 3. Auflage, Volume 2, Seminumerical Algorithms, Addison-Wesley, 1997, ISBN 0-201-89684-2
- ↑ C.5 Specifications for integer and floating point datatypes and operations, in: ISO/IEC 10967-1 Information technology — Language independent arithmetic — Part 1: Integer and floating point arithmetic, second edition, 2012-07-15
- ↑ Chris K. Caldwell: 9095665192937, Prime Curios!, 2023, abgerufen am 11. November 2023
Quelldateien
BearbeitenPseudoRandom
Bearbeiten/*
Source file: Zufall.java
Author: Markus Bautsch
Licence: public domain
Date: 31 October 2022
Version: 1.1 (17. November 2023), longer pseudo random sequence / + public static long nextIntegerInterval (long zmin, long zmax)
Programming language: Java
*/
/*
Klasse fuer die Berechnung pseudozufaelliger Zahlen
*/
public class PseudoRandom
{
// Klassenvariable fuer die aktuelle Zufallszahl
private static long currentRandomnumber = 1; // wird mit 1 initialisiert
// Klassenkonstanten
final private static long m = 9095665192937L; // grosse Primzahl, ungefaehr 2 hoch 43
final private static long a = 3 * 7 * 11 * 13 * 17 * 19; // Produkt sechs kleiner und teilerfremder ungerader Primzahlen, Zahlenwert = 969969, knapp 2 hoch 20
// Setzt aktuelle Zufallszahl auf einen ganzzahligen Startwert
public static void setStartValue (long startwert)
{
currentRandomnumber = startwert;
}
// Setzt aktuelle Zufallszahl auf die aktuelle Systemzeit in Millisekunden
public static void setStartValueToSystemTime ()
{
setStartValue (java.lang.System.currentTimeMillis ());
}
// Berechnet die naechste pseudozufaellige aktuelleZufallszahl im Intervall [1..m-1] (einschliesslich)
private static void nextRandomnumber ()
{
currentRandomnumber = a * currentRandomnumber % m;
}
// Berechnet eine pseudozufaellige Zahl im Intervall [0..max-1] (einschliesslich)
public static long nextInteger (long max)
{
nextRandomnumber ();
long randomNumber = currentRandomnumber % max;
return randomNumber;
}
// Berechnet eine pseudozufaellige Zahl im Intervall [zmin..max] (einschliesslich)
public static long nextIntegerInterval (long zmin, long zmax)
{
long max = zmax - zmin + 1;
long randomNumber = nextInteger (max) + zmin;
return randomNumber;
}
// Berechnet eine pseudozufaellige Zahl im Intervall [0..1] (ausschliesslich 0 und 1)
public static double nextProbability ()
{
nextRandomnumber ();
double probability = (double) currentRandomnumber / m;
return probability;
}
}
SimForestModel
Bearbeiten/*
Source file: SimForestModel.java
Author: Markus Bautsch
Licence: public domain
Date: 31 October 2022
Version: 1.0
Programming language: Java
*/
/*
Modell fuer die Simulation von Waldbraenden in einer rechteckigen Flaeche
*/
public class SimForestModel
{
// Klassenkonstanten fuer die Klassenvariablen currentGround und nextGround
public final static long SOIL = 0;
public final static long TREE = 1;
public final static long WATER = 2;
public final static long FIRE = 3;
// Instanzvariablen
private int sizeX = 200;
private int sizeY = 200;
private long annualNumberOfLightnings = 3; // Anzahl der Blitzschlaege innerhalb des Gebiets in einem Jahr
private double probabilitySpreadOfForestFire = 0.7; // nach Blitzschlag
private double fireExtinctionIndex = 0.9; // Wahrscheinlichkeit, dass ein Feuer erlischt
private double treeGrowthIndex = 0.02; // Wahrscheinlichkeit, dass ein Baum waechst
private long [] [] currentGround;
private long [] [] nextGround;
/* Konstruktor fuer die Initialisierung von Instanzen der Klasse SimForestModel
* @param x: Fuer die horizontale Groesse in Pixeln
* @param y: Fuer die vertikale Groesse in Pixeln
*/
public SimForestModel (int x, int y)
{
this.sizeX = x;
this.sizeY = y;
this.currentGround = new long [x] [y];
this.nextGround = new long [x] [y];
}
/*
* Gibt die Breite des Waldbodenmodells zurueck
*/
public int getWidth ()
{
return sizeX;
}
/*
* Gibt die Hoehe des Waldbodenmodells zurueck
*/
public int getHeight ()
{
return sizeY;
}
/*
* Gibt den Status des Waldbodenmodells an der Stelle (x,y) zurueck
*/
public long getGroundState (int x, int y)
{
return currentGround [x] [y];
}
/*
* Setzt den Status des Waldbodenmodells an der Stelle (x,y)
*/
public void setGroundState (int x, int y, long state)
{
currentGround [x] [y] = state;
}
/*
* Setzt die Anzahl der Blitzschlaege in einem Jahr
*/
public void setAnnualNumberOfLightnings (long number)
{
if (number < 0)
{
number = 0;
}
annualNumberOfLightnings = number;
}
/*
* Prueft den Wertebereich fuer eine Wahrscheinlichkeit zwischen Null und Eins
*/
private double checkProbability (double probability)
{
probability = java.lang.Math.abs (probability);
if (probability > 1)
{
probability = 1;
}
return probability;
}
/*
* Setzt die Wahrscheinlichkeit fuer die Ausbreitung eines Brandes nach einem Blitzschlag
*/
public void setProbabilitySpreadOfForestFire (double probability)
{
probabilitySpreadOfForestFire = checkProbability (probability);
}
/*
* Setzt die Wahrscheinlichkeit fuer das Erloeschen eines Feuers an einer Stelle des Waldbodens
*/
public void setFireExtinctionIndex (double probability)
{
fireExtinctionIndex = checkProbability (probability);
}
/*
* Setzt die Wahrscheinlichkeit fuer das Wachsen eines neuen Baumes an einer Stelle des Waldbodens
*/
public void setTreeGrowthIndex (double probability)
{
treeGrowthIndex = checkProbability (probability);
}
/*
* Methode fuer die Simulation eines Blitzschlags
* Gibt zurueck, ob ein Baum entzuendet wurde
*/
private boolean simulateLightning ()
{
int locationX = (int) PseudoRandom.nextInteger (sizeX);
int locationY = (int) PseudoRandom.nextInteger (sizeY);
boolean forestIsBurning = false;
if (currentGround [locationX] [locationY] == TREE)
{
currentGround [locationX] [locationY] = FIRE;
forestIsBurning = true;
}
return forestIsBurning;
}
/*
* Methode fuer die Ausbreitung des Waldbrands
* Gibt zurueck, ob ein benachbarter Baum brennt
*/
private boolean fireNextToTree (int x, int y)
{
boolean fireNextToTree = false;
double probability = PseudoRandom.nextProbability ();
if (probability < probabilitySpreadOfForestFire) // fuer rechts/links, oben/unten
{
fireNextToTree = ((x > 0) && (currentGround [x - 1] [y] == FIRE))
|| ((x < (sizeX - 1)) && (currentGround [x + 1] [y] == FIRE))
|| ((y > 0) && (currentGround [x] [y - 1] == FIRE))
|| ((y < (sizeY - 1)) && (currentGround [x] [y + 1] == FIRE));
}
if (!fireNextToTree && (probability < (probabilitySpreadOfForestFire / 4))) // fuer die vier Diagonalen
{
fireNextToTree = ((x > 0) && (y > 0) && (currentGround [x - 1] [y - 1] == FIRE))
|| ((x < (sizeX - 1)) && (y > 0) && (currentGround [x + 1] [y - 1] == FIRE))
|| ((x > 0) && (y < (sizeY - 1)) && (currentGround [x - 1] [y + 1] == FIRE))
|| ((x < (sizeX - 1)) && (y < (sizeY - 1)) && (currentGround [x + 1] [y + 1] == FIRE));
}
return fireNextToTree;
}
/*
* Methode zum Kopieren des neu berechneten Waldbodens zum aktuellen Wandboden
*/
private void copyNextToCurrentGround ()
{
for (int y = 0; y < sizeY; y++)
{
for (int x = 0; x < sizeX; x++)
{
currentGround [x] [y] = nextGround [x] [y];
}
}
}
/*
* Methode fuer die Simulation eines Waldbrands
* Gibt zurueck, wie viele Baeume des Waldes brennen
*/
public long simulateBurning ()
{
long numberOfTreesBurning = 0;
for (int y = 0; y < sizeY; y++)
{
for (int x = 0; x < sizeX; x++)
{
long currentState = currentGround [x] [y];
nextGround [x] [y] = currentState;
if (currentState == TREE)
{
if (fireNextToTree (x, y))
{
nextGround [x] [y] = FIRE;
numberOfTreesBurning++;
}
}
else if (currentState == FIRE)
{
double probability = PseudoRandom.nextProbability ();
if (probability < fireExtinctionIndex)
{
nextGround [x] [y] = SOIL;
}
else
{
numberOfTreesBurning++;
}
}
}
}
copyNextToCurrentGround ();
return numberOfTreesBurning;
}
/*
* Methode zum Simulieren des Wachstums eines neuen Baumes
*/
private void newTree (int x, int y)
{
double probability = PseudoRandom.nextProbability ();
if (probability < treeGrowthIndex)
{
nextGround [x] [y] = TREE;
}
}
/*
* Methode fuer die Simulation des jaehrlichen Baumwuchses
*/
private void simulateTreeGrowth ()
{
for (int y = 0; y < sizeY; y++)
{
for (int x = 0; x < sizeX; x++)
{
long currentState = currentGround [x] [y];
nextGround [x] [y] = currentState;
if (currentState == SOIL)
{
newTree (x, y);
}
}
}
}
/*
* Methode fuer die Simulation eines Jahres
* Gibt zurueck, ob der Wald brennt
*/
private boolean simulateLightnings ()
{
boolean forestIsBurning = false;
for (long i = 0; i < annualNumberOfLightnings; i++)
{
boolean forestIsIgnited = simulateLightning ();
forestIsBurning = forestIsBurning || forestIsIgnited;
}
return forestIsBurning;
}
/*
* Methode fuer die Simulation eines Jahres
* Gibt zurueck, ob der Wald brennt
*/
public boolean simulateYear ()
{
simulateTreeGrowth ();
copyNextToCurrentGround ();
boolean forestIsBurning = simulateLightnings ();
return forestIsBurning;
}
}
SimForestFrame
Bearbeiten/*
Source file: SimForestFrame.java
Author: Markus Bautsch
Licence: public domain
Date: 31 October 2022
Version: 1.1
Programming language: Java
*/
/*
Graphische aktualisierte Ausgabe von Rechtecken
*/
public final class SimForestFrame extends javax.swing.JFrame implements java.awt.event.ActionListener
{
// serialVersionUID fuer die Serialisation, die in der Klasse javax.swing.JFrame implementiert ist
private final static long serialVersionUID = 1L;
// Klassenkonstanten
final private static int [] SoilColour = {64, 32, 0};
final private static int [] TreeColour = {0, 255, 0};
final private static int [] FireColour = {255, 0, 0};
final private static int [] WaterColour = {0, 0, 255};
final private static java.lang.String title = "Forest Fire";
final private static int offset = 10;
final private static int offsetTop = 30;
final private static int fireDelay = 1; // Verzögerung während eines Wandbrands in Millisekunden
final private static int annualDelay = 500; // Verzoegerung nach einem simulierten Jahr in Millisekunden
// Instanzvariablen
private int sizeX = 200;
private int sizeY = 200;
private long currentYear = 0;
private long numberOfYears = 1;
private boolean forestIsBurning = false;
private SimForestModel model;
private java.awt.image.BufferedImage bufferedImage;
private java.awt.image.WritableRaster imageRaster;
private javax.swing.Timer timer = new javax.swing.Timer (annualDelay, this); // Fuer die Anzeige des JFrames mit Verzoegerung
/* Konstruktor fuer die Initialisierung von Instanzen der Klasse SimForestFrame
* @param x: Fuer die horizontale Groesse in Pixeln
* @param y: Fuer die vertikale Groesse in Pixeln
* @param count: Fuer die Anzahl der Simulationsdurchlaeufe in Jahren
*/
public SimForestFrame (int x, int y, long count)
{
super (title);
this.model = new SimForestModel (x, y);
this.sizeX = x;
this.sizeY = y;
this.numberOfYears = count;
// Rahmengroesse, Hintergrund und das Layout werden gesetzt
this.setSize (this.sizeX + 2 * offset, this.sizeY + 2 * offset + offsetTop);
this.setBackground (java.awt.Color.BLACK);
this.bufferedImage = new java.awt.image.BufferedImage (this.sizeX, this.sizeY, java.awt.image.BufferedImage.TYPE_INT_RGB);
this.imageRaster = bufferedImage.getWritableTile (0, 0);
this.setDefaultCloseOperation (javax.swing.WindowConstants.EXIT_ON_CLOSE);
}
/*
* Gibt das Simulationsmodell der Klasse SimForestModel vom SimForestFrame zurueck
*/
public SimForestModel getModel ()
{
return model;
}
/*
* Standard-Methode paint zum Zeichnen des Rechtecks mit dem Waldboden
*/
public void paint (java.awt.Graphics graphics)
{
for (int y = 0; y < sizeY; y++)
{
for (int x = 0; x < sizeX; x++)
{
long groundState = model.getGroundState (x, y);
if (groundState == SimForestModel.SOIL)
{
imageRaster.setPixel (x, y, SoilColour);
}
else if (groundState == SimForestModel.TREE)
{
imageRaster.setPixel (x, y, TreeColour);
}
else if (groundState == SimForestModel.WATER)
{
imageRaster.setPixel (x, y, WaterColour);
}
else if (groundState == SimForestModel.FIRE)
{
imageRaster.setPixel (x, y, FireColour);
}
}
}
graphics.drawImage (bufferedImage, offset, offset + offsetTop, null);
java.awt.Toolkit.getDefaultToolkit ().sync (); // Dieser Methodenaufruf stellt sicher, dass die Ausgabe vollständig aktualisiert ist
}
/*
* Methode fuer die Initialisierung einer Instanz von SimForestFrame
*/
public void init ()
{
java.awt.Graphics2D graphics2D = bufferedImage.createGraphics ();
this.timer.start ();
this.paint (graphics2D);
this.setVisible (true);
this.setAlwaysOnTop (true);
}
/*
* Ueberschriebene Standard-Methode actionPerformed fuer die synchronisierte Aktualisierung einer Instanz SimForestFrame
*/
public void actionPerformed (java.awt.event.ActionEvent event)
{
long numberOfTreesBurning = 0;
if (forestIsBurning)
{
numberOfTreesBurning = model.simulateBurning ();
forestIsBurning = (numberOfTreesBurning > 0);
if (forestIsBurning)
{
timer.setDelay (fireDelay);
}
else
{
timer.setDelay (annualDelay);
}
}
else if (currentYear < numberOfYears)
{
currentYear++;
forestIsBurning = model.simulateYear ();
}
else
{
timer.stop ();
}
java.lang.String currentTitel = title + " / year " + currentYear + " / " + " burning trees = " + numberOfTreesBurning;
setTitle (currentTitel);
repaint ();
}
}