NumPy bei großen Arrays unter macOS extrem langsam

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
Kontrapaganda
User
Beiträge: 7
Registriert: Mittwoch 14. August 2019, 18:25

Hallo, liebe deutschsprachige Python-Gemeinde,
ich habe vor etwa zwei Monaten nach meinem Abitur mit Python, NumPy und Matplotlib angefangen und ein bisschen mit Numerik experimentiert.
Neulich habe ich nach ca. zwei Wochen ein Skript fertiggestellt, um lineare Gleichungssysteme von beliebiger Größe und beliebigem Aufbau zu lösen und einen entsprechenden Lösungsvektor auszugeben. Das ist mir auch gelungen und es läuft wunderbar.
natürlich habe ich das Skript auch mit großen Systemen gestestet, die ich natürlich mittels numpy.random.random_sample erzeuge sowie mit allen möglichen Konstellationen der linearen (Un)Abhängigkeit, Kollinearitäten und Parallelitäten.
Prinzipiell funktioniert alles bestens.
Vielmehr sind es einige Becobachtungen bezüglich der Geschwindigkeit, die mich irritieren.
Ich habe ein älteres Macbook Pro mit Intel Core i5 3210m, 16GB 1600 MHz DDR3 RAM und arbeite hauptsächlich unter macOS 10.13.6.
Bei sehr großen LGS mit mehrerenden hundert Unbekannten sowie Gleichungen ist mir zunächst aufgefallen, dass der Zeitaufwand deutlich zunimmt, was ich aber nunächst einmal einfach so hingenommen habe.
Mir war zuvor schon aufgefallen, dass ein anderes Skript zur Primzahlfindung unter Ubuntu 18.04 nativ auf dem gleichem Laptop etwas schneller lief, etwa um den Faktor 1,6.
Just for Fun wollte ich mal die Performance unter Windows 10 in VirtualBox testen. Bei etwa 100 Unbekannten war das Skript in der VM auch merklich langsamer als nativ unter macOS, was mich nicht verwundert hat.
Bei 300 Unbekannten und Gleichungen aber lief das Skript in der VM plötzlich schneller als nativ unter macOS!
Daraufhin habe es noch mal unter Ubuntu 18.04 nativ getestet und dort liefen alle Skripte mit Abstand am schnellsten.
Unter Ubuntu 18.04 ebenfalls in ViortualBox war die Performance identisch zu Windows 10 in VirtualBox. Ein natives Windows habe ich derzeit nicht auf der Platte.
Besonders die Beobachtungen in der VM, die ja eigentlich zu Performance-Einbußen führt, haben deutlich gemacht, dass das Phänomen bei großen Arrays besonders stark ausfällt und bei ganz kleinen nicht merkbar auffällt.

Interessieren würde mich vor allem, was die Gründe dafür sind. Ist die macOS-Version von MumPy schlechter?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich denke nicht das es an numpy selbst liegt. Das sollte mehr oder minder Den gleichen Code laufen lassen.

Das einzige was mit spontan einfällt ist Wortbreite, und dadurch ggf unterschiedliche Cache Performance.

Welche wortbreiten haben die respektiven Pythons?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Ohne das Skript zu kennen, kann man nicht sagen, ob Du jetzt besonders geschickt oder ungeschickt programmiert hast.
Dann gibt es große Unterschiede, wie numpy compiliert wurde, icc ist meist schneller als gcc, ist schneller als llvm, so grob, je nach Optionen und CPU kann es da aber auch noch Unterschiede geben. Und drittens gibt es noch die mkl-Bibliotheken, die es je nach Problem deutlich schneller oder deutlich langsamer machen. Oder blas-Bibliotheken in unterschiedlichsten Varianten, ...
Grob gesagt, der Compiler muß die verwendete CPU ganz genau kennen, um optimalen Code zu generieren, da ist leicht ein Faktor 2 bis 5 drin, zwischen geht so und optimal.
Für numeriklastige Probleme spielt es fast keine Rolle, ob VM oder nicht.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Wie erzeugst du das zu lösende Gleichungssystem? Füllst die einfach eine Matrix mit Zufallszahlen?

Mit welchen Python und Numpy-Versionen arbeitest du auf den verschiedenen Systemen?

Code: Alles auswählen

import numpy as np
print(np.__version__)
a fool with a tool is still a fool, www.magben.de, YouTube
Kontrapaganda
User
Beiträge: 7
Registriert: Mittwoch 14. August 2019, 18:25

Danke erst mal für die Antworten. Womit NumPy kompiliert worden ist, weiß ich nicht. Ich wusste bis jetzt nicht mal, dass es da Unterschiede gibt. Bin, wie gesagt, absoluter Newbie im Pprogrammieren und habe bis jetzt nur mit NumPy etwas Numerik, Primzahlsachen und halt LGS gemacht.
NumPy habe ich via pip installiert. Wenn es also am Compiler liegt, ist nicht die macOS-Version von NumPy schlechter, sondern die Version, die pip installiert hat, ist nicht optimal für meine Hardware ausgelegt.
Ob ich mich bei dem Skript besonders geschickt angestellt habe, weiß mangels Vergleich ich nicht.
Aber es ist ja vor allem die Performance-Diskrepanz zwischen macOS und Windows/Linux, die müsste doch unabhängig vom Skript sein, oder und die ist mir, wie gesagt, auch schon, wenn auch geringer ausgeprägt, bei einem anderen Skript aufgefallen, bei dem die Arrays aber auch nicht so groß waren, da die mangels Gesamt-Performance gar nicht möglich wäre.

Die Arrays werden mit numpy.random.random_sample erzeugt., aber es ist ja immer das gleiche Skript.

Code: Alles auswählen

system = np.random.random_sample((equation_amount, variable_amount+1))
SoLEQ = (system * (maximum_value - minimum_value) + minimum_value)

Code: Alles auswählen

import sys as sys
try:
    import numpy as np
except ModuleNotFoundError:
    print("Sorry, mssing module 'numpy'.")
    print("Please install module 'numpy' via pip3 or your favourite package manager.")
    sys.exit()

clm_amount = int(sum(SoLEQ[0, :]*0 + 1)) #compute the number of columns
neq = int(sum(SoLEQ[:, 0]*0 + 1)) #compute the number of equations
counter_line = np.linspace(0, clm_amount-1, clm_amount).astype(int) #conpute the number of lines

zero_amount = 0
SoLEQ1 = np.array([counter_line.copy()])
for j in range(0, neq, 1):
    eq_temp = SoLEQ[j, :]
    if sum(abs(eq_temp)) > 0:
        SoLEQ1 = np.vstack((SoLEQ1, eq_temp)) #remove equations without an expression
    else:
        zero_amount += 1


SoLEQ = SoLEQ1.copy()
ln_amount = int(sum(SoLEQ[:, 0]*0 + 1))
DIM = int(sum(SoLEQ[0, :]*0 + 1) - 1) #compute how many dimensions the SoLEQ has
if ln_amount == 1:
    SoLEQ = np.vstack((SoLEQ, np.linspace(0, 0, DIM+1)))

print("DIM =", DIM)
SoLEQ_amount = 1
col_tol = 1e-09


print("This is a cleaned version of the SoLEQ to solve:")
print(SoLEQ)
SoLEQ = [SoLEQ]
itrp_line = False #set the start parameters
print()

for dim0 in range(DIM, 0, -1):
    coef_zero = False
    print("dim0 =", dim0)
    #print(SoLEQ)

    ln_amount = int(sum(SoLEQ[0][:, 0]*0 + 1)) #compute the number of equations
    
    SoLEQ_clean = SoLEQ[0][0:2, :].copy().reshape(2, clm_amount) #make new empty SoLEQ
    SoLEQ_clean_num = 0

    for ln0 in range(2, ln_amount, 1):
        indicator = 'singular'

        mat_quot = SoLEQ[0][ln0, :] / SoLEQ[0][ln0-1, :] #examine possble collinearity
        mat_quot0 = np.where(abs(mat_quot) == np.inf, 0.0, mat_quot)
        mat_quot0 = np.where(np.isnan(mat_quot0) == True, 0.0, mat_quot0)
        inf_subst = np.amax(abs(mat_quot0)) * 2 + 1
        mat_quot = np.where(abs(mat_quot) == np.inf, inf_subst, mat_quot) #substitute inf for the double of the maximum of real number

        p = 0
        while np.isnan(mat_quot[p]) == True: #search for comparison value
            p += 1

        mat_exam = abs(mat_quot - mat_quot[p])
        mat_exam = np.where(np.isnan(mat_exam) == True, 0, mat_exam) #if collinear partner has been found mark equation
        mat_exam = col_tol * np.floor(mat_exam / col_tol)
        if sum(mat_exam) != 0:
            SoLEQ_clean = np.vstack((SoLEQ_clean, SoLEQ[0][ln0, :])) #restore only singular equations

    SoLEQ[0] = SoLEQ_clean.copy()

    ln_amount = int(sum(SoLEQ_clean[:, 0]*0 + 1)) #recompute the number of equations
    if ln_amount < 3:
        itrp_line = True
        dim_itrp = dim0
        print("Reduction of variables has been interrupted")
        print("because number of equations to reduce has reached 1.")
        print("Programm will continue with solving the resting equations sequentially.")
        break

    SoLEQ_active = SoLEQ_clean[0, :].copy().reshape(1, clm_amount)
    SoLEQ_passive = np.array([]).reshape(0, clm_amount)
    ac = 1
    
    for q in range(1, ln_amount, 1):
        if SoLEQ_clean[q, dim0] != 0:
            SoLEQ_active = np.vstack((SoLEQ_active, SoLEQ_clean[q, :])) #make a SoLEQ with equation in which the last coefficient is different from zero zero
            ac += 1
        elif SoLEQ_clean[q, dim0] == 0:
            SoLEQ_passive = np.vstack((SoLEQ_passive, SoLEQ_clean[q, :])) #make a SoLEQ in which the last coefficient is zero
        else:
            print("Fatal error occured. Programm has been interrupted.")
            sys.exit()

    SoLEQ0 = np.vstack((SoLEQ_active, SoLEQ_passive)) #combine the two SoLEQs
    SoLEQ[0] = SoLEQ0.copy()
    SoLEQ_new = np.vstack((SoLEQ0[0, :].copy(), SoLEQ_passive.copy())) #make new complete SoLEQ which is sorted
    
    for k in range(1, ac-1, 1):
        eq_temp = ((SoLEQ_active[k, :] / SoLEQ_active[k, dim0]) * SoLEQ_active[k+1, dim0]
                   - SoLEQ_active[k+1, :] # eliminate the unknwon which has the temporily greatest index
                   ) #reduce x equations to (x-1) equations and y variables to (y-1) variables
        SoLEQ_new = np.vstack((SoLEQ_new, eq_temp))

    SoLEQ = [SoLEQ_new] + SoLEQ #add new equation to list
    SoLEQ_amount += 1
        
neq = int(sum(SoLEQ[SoLEQ_amount-1][:, 0]*0 + 1)) - 1 #recompute number of equations
ln_amount = int(sum(SoLEQ[0][:, 0]*0 + 1))

#continue with one equation or one variable

print("Programm will go on analysis data for", DIM ,"dimensions.")

        
if (ln_amount > 2
    or (sum(abs(SoLEQ[0][1, 1:DIM+1])) <= col_tol*0
        and abs(SoLEQ[0][1, 0]) > col_tol*0)): #if there is a contradiction
    ln_counter = np.vstack((np.linspace(0, 0, DIM)))
    clm_counter = np.linspace(0, 0, 1)
    sol_matrix = np.where(clm_counter == ln_counter, np.nan, 0) #make the 'nan' marking for the unknwons
    print("Fatal contradiction occured. SoLEQ has no solution.")
    print()
    solution = np.vstack((np.array([[0.0]]), sol_matrix))
    sol_function = solution.copy()


elif (ln_amount == 2 and sum(abs(SoLEQ[0][1, 1:DIM+1])) != 0) or SoLEQ[0][1, 0] == 0: #if there's no contradiction
    print("SoLEQ does probably have a solution set.")
    print()
    var_num_tmp = 0
    var_num_ar = np.array([0]) #set the start values
    eq_fa = 0
    eq_final_list = []
    sol_function = (SoLEQ[0][0, :].copy().astype(int)).reshape(1, DIM+1)
    
    for eq_ind in range(0, SoLEQ_amount, 1): #go through the equations
        try:
            var_num_ind = np.where(SoLEQ[eq_ind][1, :] != 0)[0] #set the numbers of variables to map by functions
            var_num_ind = var_num_ind[np.where(var_num_ind > var_num_tmp)[0]] #fidn highest possible index
            var_num_tmp = np.amax(var_num_ind)# to skip equations which don't contain new variables.
            var_num_ar = np.hstack((var_num_ar, var_num_tmp)) #add it to the list
        except ValueError:
            continue

        eq_tmp = SoLEQ[eq_ind][1, :] #select current equation to reduce sequentially
        for s in range(0, eq_fa, 1): #go through the equations which have already been solved
            if eq_tmp[var_num_ar[s+1]] == 0:
                continue
            
            eq_new = ((eq_tmp / eq_tmp[var_num_ar[s+1]])
                      * eq_final_list[s][var_num_ar[s+1]] #reduce the unknwon with the highest index
                       - eq_final_list[s])
            eq_tmp = eq_new.copy() #update equation to reduce for the next iteration of reduction

        eq_final_list +=  [eq_tmp] #add completely reduced equation to list and count them
        eq_fa += 1
        sol_ar_tmp = np.array([eq_tmp.copy()])
        sol_ar_tmp[:, 0] = sol_ar_tmp[:, 0] * -1 #solve recent equation to unknown with the highest index
        sol_ar_tmp *= -1
        sol_ar_tmp[0, var_num_tmp] = 0
        sol_ar_tmp = sol_ar_tmp / eq_tmp[var_num_tmp]
        sol_function = np.vstack((sol_function, sol_ar_tmp)) #add solved equation to matrix
        sol_array = np.array(eq_final_list)

        

    ln_counter = np.vstack(np.arange(0, DIM+1, 1))
    clm_counter = np.arange(0, DIM+1, 1)
    solution = np.where(clm_counter == ln_counter, 1.0, 0.0) #make argument matrix for resting arguments which aren't mapped
    func_amount = sum(var_num_ar * 0 + 1) - 1
    sol_dim = DIM - func_amount
    print("Solution set of SoLEQ is", sol_dim, "— dimensional.")
    #print("Cardinality of argument set of solution set is", sol_dim, ".")
    print()
    
    for h in range(0, func_amount+1, 1): #go through functions
        ind = var_num_ar[h]
        solution[ind, :] = sol_function[h, :] #overwrite argument functions with mapped functions

    sol_function = solution.copy()
    sol_function_clean = np.vstack(sol_function.copy()[:, 0])#make new empty matrix

    for cc in range(1, clm_amount, 1): #go through all columns
        if sum(abs(sol_function[:, cc])) > cc: #if column still contains an argument
            sol_function_clean = np.hstack((sol_function_clean,
                                    np.vstack(sol_function[:, cc]))) #add it to the new matrix
    sol_function = sol_function_clean.copy()
        
else:
    print("Unknown error occured. SoLEQ can't be solved. Sorry.")
 
'''
print("EXPLANATION OF DATA OUTPUT FORMAT:")
print()
print("The solution of the system of linear equationsto solve")
print("is also displayed by functions mapping the unknowns.")
print("The solution of the SoLEQ has at least one column")
print("and its number of lines except the head line")
print("is equal to the number of unknwons of the start SoLEQ.")
print("Therefore, each line except the head line")
print("maps one unknown via a function of an egument set.")
print("The order the unknwons mapped is equal to the one in the start SoLEQ.")
print("Similar to the order of the unknowns in the start SoLEQ")
print("the arguments are ordered from low indices to high ones.")
print("The order does always prefer low indices to high ones.")
print("Due to the fact that some SoLEQ include column-wise collinearities")
print("mapping the unknwons on eachother can be restricted")
print("and qrgument sets cannot always be transmitted.")
print("As a consequence of that, the sequence of numbers in the head line")
print("to allocate each column to one specific unknwon as an argument")
print("isn't always a linear sequence of integers.")
print("Therefore, the head line does contain these integers")
print("to allocate each column to one specific unknwon as an argument.")
print("Of course, column number zero does not refer to an unknwon as an argument")
print("but to the constant value similarly to column number zero of the start SoLEQ")
print("that conatins the constant values either.")
print("If there is only this first column")
print("the cardinality of the argument set is zero.")
print("Simply expressed, the SoLEQ has a definite solution.")
print("If there are multiple columns")
print("the cardinality of the argument set is greater at least one.")
print("Simply expressed, The SoLEQ doesn't have a definite solution.")
print("All further columns contain the coefficients of the arguments.")
print("Don't wonder if there are lines such as [0, 0, 1, 0] or similar.")
print("That just displays an argument functionally mapping itself.")
print("It's expressed formally congruently to the other unknwon functions")
print("which don't map arguments.")
print("Don't wonder if there're unknwons that have less arguments than others.")
print("It's just because the solution set is orthogonal")
print("to one or multiple of the argument axes.")
print("The coefficient of the argument which is mapped by the argument function")
print("is 1.0 as a neutral element of multiplication. So it maps itself.")
print("All the other coefficients different from the one of the mapped argument")
print("as much as the constant value are 0.0 as neutral elements of summation.")
print("Some arguments only map itself via an argument function")
print("and don't appear in any function different than their own one as an argument.")
print("That just means that the solution set is parallel to one of the argument axes")
print("and that such an unknwon has no reference to any other unknown function.")
print()
print("Solution vector is:")
print(sol_function)
print()
'''

print("The solution is expressed as a solution vector.")
print("Each line except the head line stands for one dimensional parameter.")
print("The first column contains the positional vector.")
print("All further columns contain positional vectors.")
print("The head line contains integers")
print("to allocate the variables correctly to the directional vectors.")
print()
print(sol_function)
print()

sol_matrix = solution[1:DIM+1, :]
if np.isnan(np.sum(sol_matrix)) == False: #if SoLEQ has a definite solution
    arg_subst = 1.0 #substitute arguments for constant values
    mtpd = np.vstack(np.linspace(1.0, 1.0, DIM+1))
    arg_matrix = np.hstack((np.array([1.0]),
                            np.linspace(arg_subst, arg_subst, DIM))) 
    sol_mat_def = np.matrix(arg_matrix * sol_matrix) * mtpd #multiply the arguments with their coefficients and sum them
    sol_mat_def = np.array(np.hstack(sol_mat_def))

    sum0_ar = np.array([]).reshape(0, 1)
    for i in range(1, neq+1, 1): #go through all start equations
        sum0 = (sum(SoLEQ[SoLEQ_amount-1][i, :]
                    * np.hstack((np.array([-1.0]), sol_mat_def[0, :])))) #multiply the coefficients with the unknwons and sum
        sum0_ar = np.vstack((sum0_ar, sum0))

    average_result_abs = sum(abs(sol_function[1:DIM+1, 0])) / DIM #compute average absolute result value
    max_sum0_abs = np.amax(abs(sum0)) #collect maximum discrepancy absolute
    print("Maximum of absolute numeric discrepancy is", max_sum0_abs, ".")
    print()
    
else:
    print()
    print("Cumulated discrepancies of each equation can't be computed")
    print("because SoLEQ does not have a solution.")


if max_sum0_abs > DIM*1e-06:
    print("ATTENTION!!!")
    print("Proof algorithm has determined an exceptional numerical discrepancy.")
    print("Probably, floating point arithmetics")
    print("has disturbed determination of contardictionary collinearities")
    print("what makes the SoLEQ have no solution.")
    print("Probably, the computed solution set is a pseudo-solution.")


Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Kontrapaganda hat geschrieben: Donnerstag 15. August 2019, 14:44 Die Arrays werden mit numpy.random.random_sample erzeugt., aber es ist ja immer das gleiche Skript.
Es ist aber jedes Mal eine anderes Gleichungssystem, dass du löst. Je nachdem wie du das Gleichungssystem löst, kann das Einfluss auf die Anzahl der Schritte zum Lösen haben.
Mit numpy.random.seed kannst du reproduzierbare Gleichungssysteme erzeugen.
a fool with a tool is still a fool, www.magben.de, YouTube
Kontrapaganda
User
Beiträge: 7
Registriert: Mittwoch 14. August 2019, 18:25

Jein. Natürlich ist das System jedes Mal ein anderes, weil sich die Zahlen ja zufällig unterscheiden.
Die entscheidenden Parameter, die die Größe des Systems bestimmen, nämlich die Anzahl der Gleichungen und die Anzahl der Unbekannten, gebe ich immer manuell ein und habe die natürlich miteinbezogen. Die obere sowie die untere Grenze waren dabei immer +-100. Ganz doof bin ich auch nicht.

Natürlich kann es dabei je nach exakter Komposition zu leichten Abweichungen bezüglich des Kalkülaufwandes kommen, aber gerade bei derart großen Arrays mit mehr als 10.000 skalaren Werten dürfte allein aufgrund der Menge der Werte die Standardabweichung des Kalülaufwandes bedingt durch die Zufälligkeit der Einzelwerte sehr klein sein. Zudem habe ich es einige Male ausprobiert und der Zeitaufwand hat sich allein durch die Zufälligkeit der einzelnen Werte nicht wahrnehmbar unterschieden, lediglich, wie gesagt, durch die Größe des Systems und die Bedingungen, worum es ja eigentlich geht(macOS/Linux/Windows, nativ/VM).

Daraus resultiert ja meine Beobachtung. Bei 100 Gleichungen und 100 Variablen ist ja, wie geschrieben, das nativ in macOS ausgeführte etwas schneller, bei 300 Gleichungen und 300 Unbekannten läuft es dann unter Windows 10 beziehungsweise Ubuntu 18.04 deutlich schneller, derart stark sogar, dass es selbst in der VM schneller ist. Bei 200 Gleichungen und 200 Unbekannten läuft das Skript in der VM mit Windows/Linux etwa gleich schnell wie nativ unter macOS, anhand der Print -Ausgaben sicht man jedoch, dass es in dem Bereich mit sehr großen Arrays schneller läuft, während es in dem Bereich mit kleinen Arrays langsamer läuft.

Natürlich habe ich es auch schon mit nicht-quadratischen Systemen durchgeführt. Die grundsätzliche Beobachtung ist dabei die gleiche.
Lediglich die Lösungsmenge unterscheidet sich dahingehend, dass es bei überbestimmten LGS natürlich keine Lösung gibt und bei unterbestimmten einen Lösungsvektor mit einer bestimmten Anzahl an Richtungsvektoren.
Aber das ist ja auch korrekt so und hat mit meiner Fragestellung nichts zu tun.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kontrapaganda: ich bin jetzt nicht den ganzen Algorithmus durchgegangen, aber es muß wohl an den Compileroptionen liegen.
Wie heißen denn die Numpy-Pakete auf den einzelnen Systemen exakt mit vollem Namen?

Zum Code:
Bei ›import sys as sys‹ braucht es das ›as‹ nicht, denn das wird nur gebraucht, wenn man das Modul umbenennen will (numpy -> np).

Gewöhn Dir gleich an, sprechende Namen zu verwenden. Was soll denn clm heißen, und warum ist neq, was ich als not-Equal lese, gar kein Wahrheitswert?
Mit sprechenden Namen sind dann auch die Kommentare zu den Variablennamen nicht mehr nötig. Variablennamen werden wie Funktionsnamen (und Funktionen braucht man bei 281 Zeilen definitiv!) klein_mit_unterstrich geschrieben.

Um die Größe eines Arrays zu ermitteln, muß man nicht eine Zeile mit 0 multiplizieren, 1 draufaddieren und darüber die Summe bilden (was bei Werten wie inf oder nan auch schief geht), sondern einfach nur array.shape abfragen.

SoLEQ kommt aus dem nichts, an der Stelle bricht also das Programm bereits mit einem NameError ab.

Zeile 11: statt linspace ist hier arange besser.
Zeile 14: np.array macht schon eine Kopie, ein weiteres Copy ist nicht nötig. `counter_line` wird aber nie wieder verwendet, wodurch auch schon eine Kopie eine zu viel ist.
Für mich sieht es auch komisch aus, dass in einer Matrix die erste Zeile nur aus aufsteigenden Zahlen besteht. Ich kenn den Algorithmus nicht, ob es dafür notwendig ist.
Zeile 15: Eine Schleife über einen Index ist immer ein Zeichen dafür, dass man zu explizit programmiert. Gerade bei Numpy verwendet man, wo es gebt, Operationen, die auf dem gesamten Array arbeiten. Beispiel folgt.
Zeile 16: `eq_temp`: dass eine Variablen temporär ist, also nur in dieser Umgebung verwendet wird, zeigt sich normalerweise darin, dass sie in einer, am besten kurzen, Funktion, als lokale Variable vorkommt. Da ist das temporär klar, und muß nicht in den Namen geschrieben werden.
Zeile 17: Die Summe der Absolutwerte zu bilden, um zu prüfen, ob alles 0 ist, ist umständlich und schwer lesbar, weil man erst die Operation verstehen muß, was da genau passiert und dann noch interpretieren muß. numpy.any ist genau die Funktion, die Du brauchst.
`SoLEQ1` ist wieder ein schlechter Name, denn was soll die 1 bedeuten? In wie weit unterscheidet er sich von `SoLEQ`?
Das löschen der „leeren” Zeilen läßt sich schreiben als:

Code: Alles auswählen

non_empty_lines = SoLEQ.any(axis=1)
SoLEQ = SoLEQ[non_empty_lines]
Zeile 29: warum wird hier eine 0-Zeile wieder angefügt? Um Nullen zu erzeugen, gibt es numpy.zeros.
Zeile 38/106: ich sehe, Du willst hier alle Zwischenergebnis in eine Liste speichern, aber so macht man das nicht. Man fügt keine neuen Werte an den Anfang einer Liste, weil das ineffizient ist und man rechnet auch nicht mit dem Listenelement, sondern, da Du es ja sowieso schon außerhalb der Liste hättest, wenn Du nicht den selben Namen für das Array und die Liste mit den Arrays benutzen würdest, was man übrigens auch nicht macht, weil man dann immer sich daran erinnern muß, ob man in einem Teil des Codes ist, wo SoLEQ nun ein Array ist , oder eine Liste.
Den Rest der for-Schleife verstehe ich nicht, kommt mir aber reichlich kompliziert für ein Gaussches Eliminierungsverfahren vor. Die Kolinearität fällt doch automatisch heraus, wenn man Zeilen bekommt, die nur aus 0 bestehen. Ich habe nirgends etwas gefunden, wo die erste Zeile mit den aufszeigenden Zahlen nützlich wäre, die kann also wirklich weg. Die Benutzung von `copy` ist soweit ich sehe, überall überflüssig. `vstack` sollte, wenn man nie innerhalb einer Schleife ständig aufrufen (weil langsam), sondern alles in einer Liste sammeln und am Ende einmal vstack aufrufen.

Nachdem ich jetzt genauer weiß, was Du machst, sieht es so aus, als ob der Algorithmus die meiste Zeit mit Kopieren beschäftigt wäre; die numpy-Version gewinnt, die das auf Deinem Rechner am effizientesten kann.

Als nächste Schritte solltest Du lernen, was Funktionen sind, und wie man sie einsetzt. Funktionen helfen, Code zu strukturieren, kleine Funktionen, die man einzeln testen kann, helfen, gut lesbaren und funktionierenden Code zu schreiben, weil man immer nur wenige Zeilen überblicken muß. Mach Dich auch mit der Funktionsweise von numpy besser vertraut. Der Code wird dadurch nicht nur schneller und kürzer, man versteht ihn auch leichter.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kontrapaganda: Noch mal eine andere Zahl neben den 281 Zeilen, die dringend für Funktionen spricht sind die 68 Variablennamen: DIM, SoLEQ, SoLEQ0, SoLEQ1, SoLEQ_active, SoLEQ_amount, SoLEQ_clean, SoLEQ_clean_num, SoLEQ_new, SoLEQ_passive, ac, arg_matrix, arg_subst, average_result_abs, cc, clm_amount, clm_counter, coef_zero, col_tol, counter_line, dim0, dim_itrp, eq_fa, eq_final_list, eq_ind, eq_new, eq_temp, eq_tmp, equation_amount, func_amount, h, i, ind, indicator, inf_subst, itrp_line, j, k, ln0, ln_amount, ln_counter, mat_exam, mat_quot, mat_quot0, max_sum0_abs, maximum_value, minimum_value, mtpd, neq, p, q, s, sol_ar_tmp, sol_array, sol_dim, sol_function, sol_function_clean, sol_mat_def, sol_matrix, solution, sum0, sum0_ar, system, var_num_ar, var_num_ind, var_num_tmp, variable_amount, zero_amount.

Das ist ein bisschen arg viel um den Überblick zu behalten. Die meisten Empfehlungen was die Anzahl von Dingen angeht mit denen man im Kopf klarkommen kann sind *deutlich* kleiner.

Wobei man sieben Namen auch gleich mal streichen kann, weil die offensichtlich definiert, aber nirgends verwendet werden: average_result_abs, coef_zero, dim_itrp, indicator, itrp_line, sol_array, SoLEQ_clean_num.

Wobei man bei `sol_array` mal schauen müsste ob da am Ende nicht noch mehr dran hängt was nur für `sol_array` gemacht wird und damit auch weg könnte.

Zeichenkettenliterale sind keine Kommentare und insbesondere nicht zum ”auskommentieren” von Codeblöcken gedacht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten