|
7.1 Datenaustausch und Automatisierung von Berechnungs- und Visualisierungsprozessen
ASCII-Dateien - Die einfachste Schnittstelle der Welt
- Am einfachsten lassen sich Methoden für den Datenaustausch entwickeln, die auf die Erzeugung und Benutzung von Dateien im ASII-Format basieren.
- Hierzu gibt es eine Reihe unterstützender Befehle auf der DOS-Ebene (Systembefehle / Eingabeaufforderung unter Windows), bzw. in den diversen Linux-Shells als Shellbefehle.
- Unsere Betrachtungen beschränlken sich zwar auf Windows, lassen sich aber ohne weiteres auch auf Linux übertragen.
|
Exportieren und Importieren von Daten mit C++ - pipen
- Es besteht die Möglichkeit Daten, die ein Programm normalerweise auf der Eingabeaufforderung ausgeben würde, in eine Datei umzuleiten.
- Dieser Vorgang ist unter Linux/Unix als "pipen" bekannt und auch unter Windows unter Verwendung des Zeichens ">" möglich.
- Das folgende C/C++ Programm gibt eine 3x3-Matrix in der DOS-Shell aus:
|
#include<iostream>
using namespace std;
int main()
{
double matrix[9] = {1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9};
for(int i=0;i<9;i++)
{
if(i%3==0 && i>0)
cout<<endl;
cout<<matrix[i]<<" ";
}
}
Code 7.1-1: 01_dreimaldreimatrix.cpp - Erstes Testprogramm zur Entwicklung von Datenaustauschskonzepten.
- Wenn man eine Eingabeaufforderung öffnet (Start->Ausführen...->cmd), in das Verzeichnis geht, wo der Quelltext liegt (cd <Pfad>), ihn kompiliert (g++ -o <Zielname ohne Endung> <Name der Datei mit Quellcode>) und das entstandene Executable dort startet, erscheint die 3x3-Matrix in der Eingabeaufforderung.
- Hier die entsprechende Befehlsfolge in der Eingabeaufforderung (Beispiel):
|
C:\Dokumente und Einstellungen\guido>cd ..
C:\Dokumente und Einstellungen>cd ..
C:\>cd lokal
C:\lokal>cd visualisierung
C:\lokal\visualisierung>g++ -o 01_dreimaldreimatrix 01_dreimaldreimatrix.cpp
C:\lokal\visualisierung>01_dreimaldreimatrix.exe
1.1 2.2 3.3
4.4 5.5 6.6
7.7 8.8 9.9
C:\lokal\visualisierung>
Code 7.1-2: Befehlsfolge zum Kompilieren und Starten eines C/C++ - Programms.
- Wir nehmen an, die ausgegebene Matrix sei ein Berechnungsergebnis und wir wollen es in eine Datei umleiten.
- Ohne Änderung des Programms kann dies durch "pipen" geschehen.
- Durch folgenden Befehl wird die Ausgabe in die Datei "matrix.txt" umgeleitet:
|
C:\lokal\visualisierung>01_dreimaldreimatrix.exe > matrix.txt
Code 7.1-3: Umleiten der Ausgabe in die Datei matrix.txt
- Um nicht immer diese Umleitung von Hand vornehmen zu müssen, lassen sich auch die Abfolge der notwendigen Befehle in ein Shellscript schreiben.
- Unter Linux kann jede beliebige Datei ausführbar gemacht werden, unter Windows sind Textdateien mit der Endung .bat ausführbar, so genannte Batch-Dateien.
- Für unser Beispiel erzeugen wir eine Textdatei und ändern deren Endung in .bat
- Die Datei heißt dann "ausfuehren.bat", kann durch Doppelklicken ausgeführt werden.
- Sie beinhaltet folgende Befehlszeilen (das Programm wird auch neu kompiliert und matrix.txt nach Ausführung im Notepad angezeigt.):
|
g++ -o 01_dreimaldreimatrix 01_dreimaldreimatrix.cpp
01_dreimaldreimatrix.exe > matrix.txt
notepad matrix.txt
Code 7.1-4: ausfuehren.bat - Befehlszeilen in der Batchdatei.
Exportieren und Importieren von Daten mit C++ - fstream
- C++ bietet die Möglichkeit, eine Umleitung der Standardausgabe auf eine Datei innerhalb des Programms vorzunehmen.
- Dies gelingt mit Hilfe der Befehle der Library fstream
- Das folgende Programm integriert einen linearen Einmasseschwinger mittels Euler-Integration und speichert die Größen t,x und v spaltenweise in der Datei schwinger.txt:
|
#include<iostream>
#include<vector>
#include<fstream>
using namespace std;
int main()
{
double m=1.0; //Masse m=1kg
double C=1.0; //Federkonstante C=1N/m
double D=0.1; //Dämpfungskonstante D=1Ns/m
double t0=0.0,x0=1.0,v0=0.0; //Anfangsbedingungen
double t,x,v,xneu,vneu; //Zeit und die Zustandsgrößen
double dt=0.01; //Zeitschrittweite
double f0,f1; //Variablen zur Berechnung der aktuellen Steigung (rechte Seite des DGLS)
double tend = 30.0; //Endzeitpunkt der Simulation
vector<double> t_merker,x_merker,v_merker; //Vektorelemente, um den Verlauf der Simulation zwischenzuspeichern.
//Anfangsbedingungen einstellen:
x=x0;
v=v0;
t=t0;
//***********************
//Simulation durchführen:
//***********************
while(t<=tend)
{
//aktuellen Zustand speichern:
t_merker.push_back(t);
x_merker.push_back(x);
v_merker.push_back(v);
//Aktuelle Steigung berechnen:
//DGLS:
//x'=v
//v'=-(C/m)*x-(D/m)*v
f0=v;
f1=-(C/m)*x-(D/m)*v;
//Integrationsschritt durchführen:
xneu = x + f0*dt;
vneu = v + f1*dt;
//Ergebnis zurückschreiben:
x = xneu;
v = vneu;
t+=dt; //Delta t hinzufügen
}
//*********************************
//Simulationsergebnisse abspeichern
//*********************************
ofstream dateiausgabe("schwinger.txt"); //Stream zur Ausgabe in Datei öffnen.
//Daten in Datei schreiben:
for(int i=0;i<t_merker.size();i++)
dateiausgabe<<t_merker[i]<<" "<<x_merker[i]<<" "<<v_merker[i]<<endl;
//Stream schließen:
dateiausgabe.close();
}
Code 7.1-5: 02_einmasseschwinger.cpp - Einmasseschwinger, Programm speichert die Größen t,x und v spaltenweise in der Datei schwinger.txt.
- Die Daten können leicht mit Scilab eingelesen und geplottet werden.
- Die folgenden Befehle befinden sich in dem Scilab-Skript mit dem Namen 02_schwinger.sce.
- Es kann durch "exec 02_schwinger.sce" aus Scilab heraus gestartet werden, nachdem man durch cd <aktuelles Verzeichnis> in Scilab in das Verzeichnis gegangen ist, wo das Skript liegt:
|
m=fscanfMat('schwinger.txt');
plot(m(:,1),m(:,2),m(:,1),m(:,3));
Code 7.1-6: 02_schwinger.sce - Scilab-Skript zum laden der Daten in schwinger.txt als Matrix und zur Visualisierung der Zeitverläufe der Zustzandsgrößen x und v.
- Wird nur diese eine Kurve oder wenige erzeugt, so bietet es sich an, die notwendigen Achsbeschriftungen interaktiv durchzuführen.
- Dies geht direkt im Plot-Fenster über "edit->current axis properties" (vergl. nachfolgenden Screenshot):
- Wie dies auch im Skript geschehen kann, wird in einem nachfolgenden Unterkapitel behandelt.
|
Bild 7.1-1: Screenshot des Plot-Fensters.
Laden von ASCII-Daten in C++
- Nachdem nun aufgezeigt wurde, wie man Daten in C++ in eine Datei schreiben kann, sollen nun noch zwei einfache Möglichkeiten aufgezeigt werden, sie in ein C++-Programm zu laden.
- Einerseits könnte ein vom Simulationsprogramm getrenntes C++ Programm die Simulationsdaten laden und wie zuvor das Scilab-Programm anzeigen.
- Andererseits könnte man auch die Simulationsparameter in einer Extradatei als Zahlenliste ablegen und für die Simulation verwenden.
- Der Übersichtlichkeit wegen wird hier nur der Ladevorgang gezeigt, ohne die nachfolgende Verarbeitung.
- Ein Ansatz zur Visualisierung von Daten direkt in C++ erfolgt an späterer Stelle.
|
- Zunächst wird aufgezeigt, wie man Parameter laden kann.
- Hier gibt es wiederum zwei Möglichkeiten.
|
Erste Möglichkeit Parameter zu laden: direkte Übergabe an das verarbeitende Programm in der Eingabeaufforderung
- Nachfolgendes Programm hat die Anzahl der übergebenen Parameter in der Variablen panz zur Verfügung.
- Die Parameter selbst liegen zunächst als Array von Zeichenarray (also als Text) in der Variablen param vor.
- Das Programm prüft zunächst, ob die Anzahl der übergebenen Parameter 3+1 ist.
- In param befindet sich nämlich als erster Eintrag der Programmname selbst, also "03_paramlesen.exe".
- Erst dann folgen die Parameter Masse m, Federsteifigkeit C und Dämpfung D.
- Entsprechend muß das Programm in der Eingabeaufforderung aufgerufen werden, Beispiel:
- 03_paramlesen.exe 2.0 1.0 0.1
- Die drei Parameter werden also direkt hinter dem Programmnamen geschrieben und stehen im Anschluß im Programm zur Verfügung.
- "panz" und "param" sind übringens frei gewählte Namen:
|
#include<iostream>
using namespace std;
int main(int panz, char** param)
{
double m,C,D;
if(panz!=3+1)
{
cout<<"So benutzt man dieses Programm:"<<endl;
cout<<"03_paramlesen.exe <Masse m> <Federsteifigkeit C> <Dämpfung D>"<<endl;
cout<<"Beispiel:"<<endl;
cout<<"03_paramlesen.exe 2.0 1.0 0.1"<<endl;
}
else
{
m = atof(param[1]);
C = atof(param[2]);
D = atof(param[3]);
cout<<"Masse m="<<m<<endl;
cout<<"Federsteifigkeit C="<<C<<endl;
cout<<"Daempfung D="<<D<<endl;
}
}
Code 7.1-7: 03_paramlesen.cpp - Diesem Programm werden drei Parameter direkt durch die Eingabeaufforderung übergeben.
- Beispielsitzung in der Eingabeaufforderung:
|
C:\lokal\visualisierung>03_paramlesen.exe
So benutzt man dieses Programm:
03_paramlesen.exe <Masse m> <Federsteifigkeit C> <Dõmpfung D>
Beispiel:
03_paramlesen.exe 2.0 1.0 0.1
C:\lokal\visualisierung>03_paramlesen.exe 2.0 1.0 0.2
Masse m=2
Federsteifigkeit C=1
Daempfung D=0.2
C:\lokal\visualisierung>
Code 7.1-8: Beispielsitzung in der Eingabeaufforderung.
Zweite Möglichkeit Parameter zu laden: Übergabe über eine Datei.
- Als Vorbereitung zur Verwendung des nachfolgenden Programms, werden die Parameter in der Textdatei parameter.txt in einer Spalte gespeichert.
- Der Inhalt dieser Datei sieht dann einfach so aus:
|
2.0
1.0
0.1
Code 7.1-9: parameter.txt - Datei mit Parameterliste (m,C und D).
- Folgendes Programm öffnet nun die Datei als Stream und liest die nacheinander auftauchenden Elemente als double-Werte ein:
|
#include<iostream>
#include<fstream>
#include<vector>
using namespace std;
int main()
{
double m,C,D;
ifstream datenlesen("parameter.txt");
double wert;
vector<double> werte; //Stream öffnen (Stream-Objekt anlegen)
//Alle Zahlen in Datei einlesen:
while(datenlesen>>wert)
werte.push_back(wert);
//Stream schließen:
datenlesen.close();
m = werte[0];
C = werte[1];
D = werte[2];
cout<<"Masse m="<<m<<endl;
cout<<"Federsteifigkeit C="<<C<<endl;
cout<<"Daempfung D="<<D<<endl;
}
Code 7.1-10: 04_paramdateilesen.cpp - Programm, dass die Zahlen in der Datei parameter.txt einliest.
Einlesen von Matrizen in C++
- Würde man mit obiger Methode die Matrix mit den Simulationsdaten lesen, so ginge die Information der Zeilen und Spaltenanzahl verloren und alle Daten wären in einem einzigen Vektor gespeichert.
- Statt dessen werden im folgenden erst die Zeilen der Datei einzeln eingelesen und im Anschluß die Zeilen in einzelne Zahlen zerlegt.
- Durch Verwendung eines eigenen Streams für die Zeilen (stringstream), wird auch der zweite Zerlegungsschritt einfach.
- Um eine gewisse Resistenz gegen Leerzeilen oder Beschriftungen im Kopf der Matrix zu erlangen, wird vor dem Hinzufügen einer Zeile beim Zusammenbau der Matrix geprüft, ob überhaupt Zahlen darin gefunden wurden.
|
#include<iostream>
#include<vector>
#include<fstream>
#include<sstream>
using namespace std;
int main()
{
vector<vector<double>*> matrix;
vector<string> zeilen;
ifstream dateilesen("schwinger.txt");
//Alle Zeilen einlesen
char puffer[3000];
while(dateilesen.getline(puffer,3000))
zeilen.push_back(puffer);
dateilesen.close();
for(int i=0;i<zeilen.size();i++)//alle Zeilen zerlegen, wenn möglich
{
vector<double>* werte = new vector<double>();
double wert;
stringstream zeilenstream(zeilen[i]); //Zeile in Stream-Objekt verwandeln
while(zeilenstream>>wert) //solange in Zeile eine double-Zahl gefunden wird...
werte->push_back(wert);
//Nur wenn werte nicht leer in matrix merken, sonst löschen:
if(werte->size()>0)
matrix.push_back(werte);
}
//Das ganze in eine echte double-Matrix umwandeln:
int anzahl_zeilen = matrix.size();
int anzahl_spalten = matrix[0]->size();
double** mat = new double*[anzahl_zeilen];
for(int i=0;i<anzahl_zeilen;i++)
mat[i] = new double[anzahl_spalten];
for(int i=0;i<anzahl_zeilen;i++)
for(int k=0;k<anzahl_spalten;k++)
mat[i][k] = matrix[i]->at(k);
//Kontrollausgabe
cout<<"eingelesene Matrix hat "<<anzahl_zeilen<<" Zeilen und "<<anzahl_spalten<<" Spalten, Inhalt:"<<endl;
for(int i=0;i<anzahl_zeilen;i++)
{
for(int k=0;k<anzahl_spalten;k++)
{
cout<<mat[i][k]<<" ";
}
cout<<endl;
}
}
Code 7.1-11: 05_matrizenlesen.cpp - Einlesen einer Matrix aus double-Werten variabler Größe in C++.
Starten von Scilab im Batch-Modus von einem C++ Programm aus
- Anstatt die Steuerung von Scilab und einem C++ - Programm von Hand oder durch Batch-Dateien vorzunehmen, besteht auch die Möglichkeit durch die C-Funktion system() externe Programme von einem C/C++-Programm aus zu starten.
- Zudem ist es möglich, Scilab per Konsolenbefehl zu starten.
- Dies eröffnet die Möglichkeit alle Programm abläufe auch innerhalb eines C/C++-Programms zu organisieren, auch wenn ein, oder mehrere Scilab-Programme beteiligt sind.
- Das folgende C++-Programm erzeugt zwei Scilab-Skripte, startet sie und verwendet auch die von den Skripten erzeugten Daten weiter.
- Der absolute Pfad nach WScilex.exe muß eventuell bei eigenen Anwendungen angepaßt werden.
|
#include<iostream>
#include<vector>
#include<fstream>
#include<sstream>
using namespace std;
int main()
{
//Simulationsprogramm als Scilab-Skript erzeugen:
string skript1 = " \
m = 1.0; \n \
C = 1.0; \n \
\n \
function f = rechteSeite(t,y) \n \
A = [[0,-C/m];[1,0]]; \n \
f = A*y; \n \
endfunction \n \
\n \
t = 0:0.01:30; \n \
y0 = [0,1]\'; \n \
t0 = 0; \n \
y = ode(y0,t0,t,rechteSeite); \n";
skript1.append("fprintfMat(\'schwinger2.txt\',[t\',y\']);\n");
ofstream data("skript1.sce");
data<<skript1;
data.close();
//Scilab ohne GUI im Batchmodus mit vorgegebenem Skript ausführen:
system("C:\\Programme\\scilab-4.1\\bin\\Scilex.exe -nouserstartup -ns -nb -nw -nwni -nogui -e exec(\'skript1.sce\');quit");
//Visualisierungsprogramm als Scilab-Skript erzeugen:
string skript2 = "m=fscanfMat('schwinger.txt');\nplot(m(:,1),m(:,2),m(:,1),m(:,3));\n";
ofstream data2("skript2.sce");
data2<<skript2;
data2.close();
system("C:\\Programme\\scilab-4.1\\bin\\Scilex.exe -nouserstartup -nb -nw -e exec(\'skript2.sce\');");
}
Code 7.1-12: 06_scilabsteuerung.cpp - In C++ zwei Scilab-Skripte erzeugen, speichern und ablaufen lassen.
|