C++Guns – RoboBlog blogging the bot

30.05.2022

C++ Guns: MPI Dataype; send struct

Filed under: Allgemein — Tags: — Thomas @ 10:05

Das Beispiel habe ich von https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node425.htm
Jeder Thread erstellt einen MPI Datentyp welcher die Offsett Addressen der struct Member Variablen hat.
Thread 1 sendet Daten zu Thread 0
Thread 0 empfaenge Daten von Thread1 und seine eigenen Daten, so dass alle in einem Array dann liegen.

// This example is based on https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node425.htm
// Jeder Thread erstellt einen MPI Datentyp welcher die Offsett Addressen der struct Member Variablen hat.
// Thread 1 sendet Daten zu Thread 0
// Thread 0 empfaenge Daten von Thread1 und seine eigenen Daten, so dass alle in einem Array dann liegen.

#include <iostream>
#include <array>

#include <mpi.h>

int my_rank;
int nThreads;

struct basetype_t {
  double volstep = 0;
  double volTot = 0;
};

// Vererbung ist nicht erlaubt
struct type_t  {
  char ID[50];
  int i = 0;
  float x = 0;
  // Ein Container wie std::vector zwischen den Datentypen scheint wohl zu funktionieren, ist in Fortran aber explizit nicht erlaubt.
//   std::vector<int> unused;
  bool l = false;
  double d = 0;
  basetype_t base;
};

// Check MPI Error code
void check(int ierr) {
  if (ierr != MPI_SUCCESS) {
    char err_recvbuffer[MPI_MAX_ERROR_STRING];
    int resultlen;
    MPI_Error_string(ierr, err_recvbuffer, &resultlen);
    std::cerr << err_recvbuffer << "\n";
    MPI_Finalize();
  }
}

// create new MPI datatype based on the addresses of the member variables of the type we want to send
MPI_Datatype createMPItyp() {
  type_t foo;
  MPI_Aint base;
  check(MPI_Get_address(&foo, &base));

  // Fuer jede member Variable die gesendet werden soll, Typ und Addresse bestimmen
  const int nMembervarsToSend = 7;
  std::array<MPI_Datatype, nMembervarsToSend> types;
  std::array<int,nMembervarsToSend> blocklen;
  std::array<MPI_Aint, nMembervarsToSend> disp;

  types[0] = MPI_INT;
  blocklen[0] = 1;
  check(MPI_Get_address(&foo.i, &disp[0]));

  types[1] = MPI_FLOAT;
  blocklen[1] = 1;
  check(MPI_Get_address(&foo.x, &disp[1]));

  types[2] = MPI_LOGICAL;
  blocklen[2] = 1;
  check(MPI_Get_address(&foo.l, &disp[2]));

  types[3] = MPI_DOUBLE;
  blocklen[3] = 1;
  check(MPI_Get_address(&foo.d, &disp[3]));

  types[4] = MPI_CHAR;
  blocklen[4] = sizeof(foo.ID);
  check(MPI_Get_address(&foo.ID, &disp[4]));

  types[5] = MPI_DOUBLE;
  blocklen[5] = 1;
  check(MPI_Get_address(&foo.base.volstep, &disp[5]));

  types[6] = MPI_DOUBLE;
  blocklen[6] = 1;
  check(MPI_Get_address(&foo.base.volTot, &disp[6]));

  if(my_rank == 0) {
      std::cout << "Base Address " << std::hex << base << "\n";
      std::cout << "Addresses   ";
      for(auto& x : disp) {
        std::cout << " " << std::hex << x;
      }
      std::cout << std::dec << "\n";
  }

  // Addresse zu Offset umrechnen
  for(auto& x : disp) {
    x -= base;
  }

  if(my_rank == 0) {
    std::cout << "Displacement";
    for(auto& x : disp) {
      std::cout << " " << x;
    }
    std::cout << "\n";
  }

  MPI_Datatype newMPItype;
  check(MPI_Type_create_struct(nMembervarsToSend, blocklen.data(), disp.data(), types.data(), &newMPItype));
  check(MPI_Type_commit(&newMPItype));

  return newMPItype;
}

void doRank0(MPI_Datatype newMPItype) {
    type_t sendbuffer;
    strcpy(sendbuffer.ID, "Kreis100");
    sendbuffer.i = 10;
    sendbuffer.x = 1.2f;
    sendbuffer.d = 1.23;
    sendbuffer.l = true;
    sendbuffer.base.volstep = 1.34;
    sendbuffer.base.volTot = 1.56;

    int  displacements[nThreads], counts[nThreads];

    std::vector<type_t> recvbuffer(2);
    std::cout << my_rank << " Receiving...\n";

    int root_rank = 0;
    displacements[0] = 0;
    displacements[1] = 1;
    counts[0] = 1;
    counts[1] = 1;
    // MPI_Gatherv(recvbuffer_send,count_send, datatype_send, recvbuffer_recv, counts_recv, displacements, datatype_recv, root,comm)
    check(MPI_Gatherv(&sendbuffer, 1, newMPItype, recvbuffer.data(), counts, displacements, newMPItype, root_rank, MPI_COMM_WORLD));
    std::cout << my_rank << " Done receiving\n";

    std::cout << my_rank << " content of struct:\n";
    for(const type_t& buf : recvbuffer) {
      std::cout << "ID "  << buf.ID << "\n";
      std::cout << "i "  << buf.i << "\n";
      std::cout << "x "  << buf.x << "\n";
      std::cout << "d "  << buf.d << "\n";
      std::cout << "l "  << buf.l << "\n";
      std::cout << "volstep "  << buf.base.volstep << "\n";
      std::cout << "volTot "  << buf.base.volTot << "\n\n";
    }
}

void doRank1(MPI_Datatype newMPItype) {
    type_t sendbuffer;
    int  displacements[nThreads], counts[nThreads];

    strcpy(sendbuffer.ID, "Kreis200");
    sendbuffer.i = 20;
    sendbuffer.x = 2.2;
    sendbuffer.d = 2.23;
    sendbuffer.l = true;
    sendbuffer.base.volstep = 2.34;
    sendbuffer.base.volTot = 2.56;

    std::cout << my_rank << " Sending...\n";
    // MPI_Gatherv(recvbuffer_send,count_send, datatype_send, recvbuffer_recv, counts_recv, displacements, datatype_recv, root,comm)
    int root_rank = 0;
    check(MPI_Gatherv(&sendbuffer, 1, newMPItype, NULL, counts, displacements, newMPItype, root_rank, MPI_COMM_WORLD));
    std::cout << my_rank << " Done sending\n";
}

int main(int argc, char* argv[]) {
    MPI_Init(&argc, &argv);

    // Get number of processes and check only 2 processes are used
    MPI_Comm_size(MPI_COMM_WORLD, &nThreads);
    if(nThreads != 2) {
        std::cout << "Start with 2 threads.\n";
        MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
    }

    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Datatype newMPItype = createMPItyp();

    switch(my_rank) {
      case 0: doRank0(newMPItype); break;
      case 1: doRank1(newMPItype); break;
    };

    MPI_Finalize();

    return EXIT_SUCCESS;
}

$ mpic++ -g -ggdb -Wall test_MPI_struct.cpp

$ mpiexec -n 2 ./a.out
1 Sending...
1 Done sending
Base Address 7ffd83f7e180
Addresses 7ffd83f7e1b4 7ffd83f7e1b8 7ffd83f7e1bc 7ffd83f7e1c0 7ffd83f7e180 7ffd83f7e1c8 7ffd83f7e1d0
Displacement 52 56 60 64 0 72 80
0 Receiving...
0 Done receiving
0 content of struct:
ID Kreis100
i 10
x 1.2
d 1.23
l 1
volstep 1.34
volTot 1.56

ID Kreis200
i 20
x 2.2
d 2.23
l 1
volstep 2.34
volTot 2.56

14.05.2022

Kaffeetassenwärmer Optimierungsaufgabe

Filed under: Allgemein — Tags: , — Thomas @ 07:05

Niemand mag kalten Kaffee! Darum muss ein Kaffeetassenwärmer her! Aber das Zeug was man kaufen kann taugt alles nichts! USB2/1 liefert nicht genügend Leistung und USB3 ist eine Vergewaltigung der kleine Kabelchen. Noch dazu habe ich keinen USB3 Port am Laptop. Eine simple Heizplatte mit einem EIN/AUS Schalter langt doch vollkommen. So wie die in meiner Kaffeemaschine....

In der Bastelkiste finden sich noch ein paar Hochlast Widerstände. Und ein Spielzeug Transformator, welcher ca 16W Leistung liefert, ist auch noch vorhanden. Da müsste sich doch eine passende Kombination von Widerständen finden lassen?! Meine Versuche von Hand zeigen ein mögliches, aber nicht optimales Ergebnis: kein Widerstand wird überlastet, die Leistung in der Summe ist auch okay, aber die Widerstände werden sehr ungleich belastet. Einige bleiben fast kalt, andere werden sehr heiß.

Ein Computerprogramm soll es lösen!

Jede Kombination aus Widerstand, parallel und seriell Schaltung sollte von der Laufzeit her kein Problem sein. Aber da ich momentan keine Idee habe, wie man da alle Möglichkeiten durchgeht, will ich es erst mal als Optimierungsproblem beschreiben.

Die constraints sind, dass kein Widerstand überlastet wird. Also, dass die elektrische Leistung gebildet aus Spannung und Strom nicht über die maximale Leistung des Widerstandes liegt.
Die fitness Funktion berechnet die elektrische Leistung aller Widerstände, diese soll maximal sein.

Da die zufällig erstellte Schaltung durchaus komplex werden kann, langen die einfachen Rechenregeln für parallel und seriell Schaltung nicht. Da wird sich ein elektrisches Netzwerk bilden mit Knoten und Schleifen Regeln. Kirchhoffsche Regeln ... die werden in eine Matrix übersetzt die man dann löschen kann. Ich glaube so war das.

Mein erster Versuch sieht so aus:

--+--| 47 |--+----+--| 68 |------------+---
  +--| 47 |--+    |                    |
  +--| 47 |--+    +--| 33 |--+--| 1 |--+
  +--| 47 |--+    +--| 12 |--+

Eine parallel Schaltung aus vier 47 Ohm Widerständen. Das ergibt 47/4=11.75 Ohm. Plus eine parallel Schaltung von 68 Ohm mit einer Kombination einer parallel Schaltung von 33 mit 12 Ohm in Reihe zu 1 Ohm. Das ergibt in der Summe (gemessen) 20.9 Ohm. Bei einer Spannung von 16V ergibt das ein Strom von 16/20.9=0.77A und einer Leistung von 16*0.77=12.3W Ob das langt, den Kaffee warm zu halten, muss noch ermittelt werden ;)

Hier ein Bild des fliegenden Aufbaus:
kaffeetasse1

Powered by WordPress