[Matlab] Dynamische Indizes? → Kopfzerbrechen

Müs Lee

Commodore
Registriert
Feb. 2007
Beiträge
4.902
Moinsen zusammen,

ich schreibe gerade ein Konvertierungsskript, um aus einer XML-Datei mit Stabelementen (Knoten-ID, Knotenkoordinaten, Radius am Knoten sowie Element-ID, Knoten1, Knoten2) eine Inputdatei für den FEM-Solver Optistruct bzw. Hypermesh zu basteln. Das funktioniert jetzt größtenteils sehr gut, allerdings bereitet mir der letzte Teil große Schwierigkeiten. Damit Hypermesh die Elemente vernünftig sortiert, müssen sie mit dem Befehl HMMOVE in die Komponente 1 bis n (ändert sich je nach Modell) geordnet werden. Das sieht bspw. für Komponente 5 so aus:
Code:
$HMMOVE        5
$              1      10      13THRU          14      31THRU          32      36
$       THRU          37      54THRU          55      59THRU          60      77
$       THRU          78      82THRU          83     100THRU         101     105

Sprich es werden in Komponente 5 die Elemente 1, 10, 13 bis 14, 31 bis 32, 36 verschoben. Jedes Feld mit einer Element-ID ist 8 Zeichen lang, jedes nichtbesetzte Zeichen im Feld wird mit Leerzeichen auf der linken Seite der Zahl aufgefüllt. Sprich, aus der Zahl 412 wird der String
Code:
"    412"
Das Feld THRU zwischen den Element-IDs x und y bedeutet "lese alle Elemente von x eingeschlossen bis y eingeschlossen ein", es besteht aus den vier Buchstaben und vier Leerzeichen auf der rechten Seite. Es können 9 Felder à 8 Zeichen pro Zeile gelesen werden, Delimiter wie Punkte oder Kommata werden nicht benötigt. Dies ist die Konvention von Hypermesh/Optistruct und kann nur so gelesen werden.

Nun zum Problem:
Mir liegen die Element-IDs, die es zu verschieben gilt, in einer spärlich besetzten Matrix vor, die auch im Anhang zu finden ist. In Spalte 1 sind alle Element-IDs pro Komponente 1 etc. Diese Matrix ändert sich je nach Modell. Die Anzahl der Elemente pro Komponente ist nicht identisch, sprich man kann sich nicht direkt die Spalten schnappen, die Nullen eliminieren und in einer einzelnen Matrix speichern. Ich habe die Elemente also zuerst mit einer for-Schleife dynamisch in einem Vektor gespeichert, der mit jedem Durchlauf überschrieben wird. Das ist prinzipiell ok, da ich sie später nicht mehr benötige und nur in der Schleife blockweise in eine Datei schreiben muss. Die Schwierigkeit besteht darin, die THRU-Felder zu handhaben. Die Gesamtanzahl der zu schreibenden Felder liegt irgendwo zwischen der Anzahl der Elemente pro Komponente und dem Doppelten davon. Ich habe bereits eine if-else-Abfrage probiert, um anhand der Abstände zwischen den Element-IDs festzustellen ob ein THRU-Feld geschrieben werden muss. Dann fiel mir auf, dass das ja die Indizes komplett durcheinanderwirft und ab dem Punkt weiss ich ehrlich gesagt nicht mehr weiter. Ich folgte kurz der Idee, eine while-Schleife dafür zu benutzen mitsamt zwei Laufvariablen und einer Abbruchbedingung: eine für den Vektor mit den numerischen Werten der Element-IDs und eine für den zu erstellenden Charaktervektor mit den dazugehörigen THRU-Feldern, die mit der ersten Laufvariablen anfängt und pro THRU-Feld um 1 erhöht wird, aber so ganz blicke ich nicht durch. Wäre es vielleicht einfacher, das mit Cells zu handhaben oder doch besser ganz anders? Übersehe ich eine superleichte Lösung für diesen Fall?

Unten gibts ein Codeschnipsel. In diesem Fall ist number_of_radius_values =6, number_of_beams = 4752, die Matrix beams beinhaltet die Elementinformationen in Spaltenvektoren der Form [ElementID, KomponentenID, Knoten1, Knoten2, Referenzknoten] und ist ebenfalls im Anhang.

Code:
%HMMOVE
for component_id=1:number_of_radius_values
    for y=1:number_of_beams
        if beams(y,2)==component_id
            element_ids_in_component(y,component_id)=beams(y,1);
        end
    end

interm=nonzeros(element_ids_in_component(:,component_id)); diff_interm=diff(interm);
str_interm=pad(string(interm),8,'left');

fprintf('Number of elements in component %d = %d\n',component_id,length(interm));
fprintf(fileID,'$HMMOVE %s\n',pad(num2str(component_id),8,'left'));

%     while z<2*length(next_element_diff)
%         if next_element_diff(z)<=0
%             fprintf('ERROR in next_element_id\n');
%             break
%            
%         elseif next_element_diff(z)>1
%            
%            
%         elseif next_element_diff(z)==1 && next_element_diff(z+1)>1
%             %continue
%            
%         end
%     end
end

Edit: ich glaub ich hab was. Über ne if-else über den Vektor abfragen ob ein THRU notwendig ist, die Anzahl davon zählen, den originalen Vektor um diese Anzahl erweitern und die Felder dazwischenklemmen. Sollte passen, oder?
 

Anhänge

  • components.csv
    68,5 KB · Aufrufe: 294
  • beams.csv
    95,1 KB · Aufrufe: 285
Zuletzt bearbeitet:
Jede Matlab-Matrix hat lineare Indizes, das kannst du dir mit A(:) anschauen. Das geht auch mit sparse matrices.
Entsprechend kommst du auch an die non-zero Indizes mit find(A).

Vielleicht kannst du das Problem noch verkürzt und isoliert mit Minimalbeispiel beschreiben. Das ist zu viel Einarbeitung so wie du die Frage formuliert hast. Klingt jedenfalls danach, als wenn du 'einfach' die Daten transformieren musst mit Matlab Hausmitteln. Da gibt es ggf. nicht den super Tipp der dads abkürzt. Außer es gibt ein besseres Interface zwischen den beiden Programmen.
 
Hm stimmt, das scheint für Außenstehende etwas wirr. Lassen wir die Details mal weg und konzentrieren uns auf das Wesentliche.

Angenommen man hat einen Vektor mit willkürlichen, einmalig vorkommenden positiven Integern in strikt aufsteigender Reihenfolge ohne festen Abstand vom Format 1x15
Code:
[ 1, 2, 3, 10, 11, 33, 34, 35, 36, 37, 38, 39, 40, 50, 60 ]
und möchte diesen im oben gezeigten Format in eine Datei schreiben. Sprich es wird daraus der 1x11-Vektor
Code:
[ 1, THRU, 3, 10, THRU, 11, 33, THRU, 40, 50, 60]

Die Bedingungen sind:
1. Wenn die Differenz zwischen zwei aufeinanderfolgenden Zahlen 1 und die Differenz der nachfolgenden dritten Zahl zur zweiten Zahl größer als 1 ist (wie bei 10 11 33), muss zwischen der ersten und zweiten Zahl das Feld THRU gesetzt werden. Die nachfolgende Zahl muss zwingend ausgeschrieben werden.
2. Wenn die Differenz zwischen mehreren jeweils aufeinanderfolgenden Zahlen 1 ist (wie bspw bei 1 2 3), muss zwischen der ersten und letzten Zahl dieser Folge THRU gesetzt werden. Die nachfolgende Zahl wird zwingend ausgeschrieben.
3. Wenn die Differenz einer Zahl zur vorherigen größer als 1 ist und die Differenz zur nachfolgenden kleiner als -1 ist, müssen diese drei Zahlen zwingend ausgeschrieben werden.

Das Hauptproblem ist nun, dass der resultierende Vektor eine Größe von 1x3 haben kann im Falle von strikt aufeinanderfolgenden Zahlen
Code:
[1 , THRU, N ]
oder aber im Falle von [ 1, 2, 4, 5, 7, 8 ] (Dimension 1x6) sogar größer wird (Dimension 1x9):
Code:
[ 1, THRU, 2, 4, THRU, 5, 7, THRU, 8 ]

Sprich ich weiß im Vorhinein gar nicht, wie groß der resultierende Vektor wird und deswegen auch nicht so recht, wie ich das handhaben soll. Wie gehe ich dieses Unterfangen am besten an?

Man klappert also den Vektor entlang seiner Dimension von Index 1 bis Index N ab und prüft welche der drei obigen Bedingungen zutrifft. Wie aber handhabe ich den Transfer des Werts vom Originalvektor in den Resultatvektor mit seinen THRU-Zwischenfeldern? Da stehe ich momentan fett auf dem Schlauch.

Wäre es am sinnvollsten, einen Nullvektor der Dimension 2N zu erstellen, den der obigen Beschreibung nach mit Zahlen und THRU zu füllen bis alles vom Originalvektor abgearbeitet ist, und ihn nachher mit nonzeros() wieder einzudampfen?
 
Zuletzt bearbeitet:
Muss es explizit schnell gehen oder sind ein paar Sekunden ok? Das sind ja nur ein paar Tausend Zeilen, das könntest du also 1:1 so machen wie du es oben im Beitrag beschrieben hast. Dein Ergebnisvektor wird dann halt immer größer. Da wird Matlab eine Warnung auswerfen, dass das nicht performant ist, aber wenn es dir taugt kann man die ja ausschalten ;).

Ich würde aber vermutlich zuerst die Intervalle suchen und speichern.
Anfang mit aktuellen Element: 1
Weiter gehen: 2, 3, 10
Ok da war das Intervall zuende, also i1 = [1,3]
10, 11, 33 ok da war zuende also i2 = [10, 11]
...
Einzelne Elemente einfach als [60, 60] speichern
Dann über die Intervalle iterieren und die Länge holen. Dann weißt du, wie groß dein Ergebnisvektor(?) sein wird. Das könntest du dann nochmal bei Bedarf so bauen wie du es vorgeschlagen hast [ 1, THRU, 2, 4, THRU, 5, 7, THRU, 8 ], wobei du statt THRU vermutlich NaN benutzen willst. Das wäre eine ganz nette Zwischenform zur Ansicht.
Aber vielleicht bietet es sich mehr an aus den Intervallen direkt dann schon den Stringvektor / Matrix(?) zu bauen.
 
Man koennte auch die Differenz betrachten (diff(v)) und die entsprechenden Stellen ueber die von dir beschriebenen Bediengungen durch ein Offset vergleichen.
Bspw. fuer die erste Bedindung:
Code:
diff_v = diff(v);
b1 = diff_v == 1 & diff_v(2:end) > 1;

Damit haettest du erst mal die Positionen, an die ein "Thru" soll.
Jetzt muesste man sich nur noch ueberlegen, wie man den Vektor anpasst.
Am einfachsten waere hier jetzt eine Schleife ueber die Anzahl der gefundenen Stellen.
Ist aber auch nicht wirklich performant, weil im jeden Schritt der Vektor bearbeitet wird.

Alternativ koennte man einen den neuen Vektor definieren (man kennt ja jetzt die Laenge).
"Thru" an die entsprechenden Stellen eintragen und in den die restlichen Stellen die Eintraege des alten Vektors
Code:
v = (1:10);
v_thru = [3 8];
v_neu = nan(12,1);

v_neu(v_thru) = "thru"
v_nan = isnan(v_neu);
v_neu(v_nan) = v;
 
Nur zur Sicherheit die Nachfrage: Dass Hypermesh bei einer simplen Auflistung aller Elemente ohne THRU nicht einliest ist gesichert/getestet?
Ansonsten würde ich es in etwa folgendermaßen angehen:
Code:
clear;
elems = [ 1, 2, 3, 10, 11, 33, 34, 35, 36, 37, 38, 39, 40, 50, 60 ];

v = [elems(1), elems, elems(end)];

diff_down = diff(v(1:end-1));
diff_up = diff(v(2:end));

remove = (diff_up==1) & (diff_down==1);
keep = not(remove);

reduced_list = elems(keep);

real_thrus = diff(remove);
real_thrus = real_thrus(keep(1:end-1)) == 1;
mini_thrus = diff(reduced_list) == 1;
all_thrus = real_thrus + mini_thrus;
thru_list(all_thrus==1) = "THRU    ";
thru_list(all_thrus==0) = "";

if length(reduced_list) > 1
    fprintf("%8d", reduced_list(1));
    for i=1:length(thru_list)
        fprintf(thru_list(i));
        fprintf("%8d", reduced_list(i+1));
    end
end
Da alles vektorbasiert bleibt, sollte es performancemäßig mMn auch nicht ganz schlecht sein.
 
Danke an alle, insbesonder an @simpsonsfan! Deine Lösung ist genau das, was ich brauche :)

Darüber hinaus hattest du recht, Hypermesh liest tatsächlich auch alle Elemente mit einer simplen Auflistung, wie ich eben feststellen durfte. Das hatte ich vor diesem Versuch bereits erfolglos getestet. Wahrscheinlich hatte sich ein Formatierungsfehler eingeschlichen, sodass es nicht korrekt funktionierte und ich es missinterpretierte. Damit hat sich mein Anliegen zum Glück erledigt :) Nach einer Weile sieht man den Wald vor lauter Bäumen nicht mehr :hammer_alt:
 
  • Gefällt mir
Reaktionen: simpsonsfan
Zurück
Oben