6.17. Solver und Zufallszahlen

Wenn die untersuchte Funktion durch Python-Code statt durch einen Puffer von Abtastwerten definiert ist, ist eine andere Werkzeugfamilie die richtige Wahl: Wo liegt die Nullstelle der Funktion, ihr Minimum, ihr Integral über ein gegebenes Intervall? Die Submodule scipy.integrate und scipy.optimize decken diese Arbeit ab. Jeder Algorithmus ruft in die vom Benutzer bereitgestellte Python-Funktion zurück, sodass die Kosten pro Iteration höher sind als bei einer Pufferreduktion; der Komfort liegt darin, den Solver nicht selbst schreiben zu müssen.

Das Submodul scipy.special deckt die statistischen speziellen Funktionen (Fehlerfunktion, Gamma) ab, die beim Berechnen der kumulativen Verteilungsfunktion (CDF, die Wahrscheinlichkeit, dass ein Abtastwert höchstens einen gegebenen Wert erreicht) oder der Wahrscheinlichkeitsdichtefunktion (PDF, die relative Wahrscheinlichkeit an einem gegebenen Wert) einer Wahrscheinlichkeitsverteilung auftreten. numpy.random deckt den Pseudozufallsgenerator für Dithering, Simulation und synthetische Testdaten ab.

6.17.1. Numerische Integration einer aufrufbaren Funktion

Wenn der Integrand eine Python-Funktion statt eines Puffers von Abtastwerten ist, stellt scipy.integrate vier Quadraturalgorithmen bereit:

  • quad() – adaptives Gauss-Kronrod. Der richtige Standard für glatte Integranden. Gibt (value, error) zurück.

  • romberg() – klassisches Romberg / Newton-Cotes. Gibt einen einzelnen Float zurück. Upstream veraltet; aus Kompatibilitätsgründen enthalten.

  • simpson() – adaptive Simpson-Regel. Gibt einen einzelnen Float zurück.

  • tanhsinh() – doppelt-exponentielle Quadratur. Verwende sie, wenn der Integrand Endpunkt-Singularitäten oder eine unendliche Grenze hat. Gibt (value, error) zurück.

Das Gauß-Integral, ausgewertet mit der doppelt-exponentiellen Regel (tanhsinh):

from math import exp
from math import pi
from math import sqrt
from ulab import numpy as np
from ulab import scipy as sp

f = lambda x: exp(-x * x)
value, err = sp.integrate.tanhsinh(f, -np.inf, np.inf)
print("approx:", value, "   exact:", sqrt(pi))

Ausgabe:

approx: 1.7724538...   exact: 1.7724538...

6.17.2. Nullstellensuche und Minimierung

scipy.optimize deckt drei klassische Einzelvariablen-Solver ab. Jede Iteration ruft in die vom Benutzer bereitgestellte Python-Funktion zurück, sodass die Beschleunigung gegenüber einem reinen Python-Solver bescheiden ist (etwa 2x); der Komfort liegt darin, den Solver nicht selbst schreiben zu müssen.

  • bisect() – findet eine Nullstelle von f auf [a, b] durch Halbieren des Intervalls. f(a) und f(b) müssen entgegengesetzte Vorzeichen haben:

    def f(x):
        return x * x - 1
    
    sp.optimize.bisect(f, 0, 4)        # ~1.0
    
  • newton() – findet eine Nullstelle mittels Sekanten- / Newton-Raphson-Iteration:

    def f(x):
        return x * x * x - 2.0
    
    sp.optimize.newton(f, 3., tol=0.001, rtol=0.01)
    # ~1.260
    
  • fmin() – findet ein lokales Minimum mit der Downhill-Simplex-Methode (Nelder-Mead):

    def f(x):
        return (x - 1) ** 2 - 1
    
    sp.optimize.fmin(f, 3.0)           # ~1.0
    

Der Umfang einer einzelnen Variablen reicht für die meisten kameraseitigen Optimierungen aus – die Kalibrierungskonstante eines Sensors, die Verstärkung, die eine Kontrastmessung maximiert, der Schwellenwert, bei dem eine Histogramm-Bimodalität am schärfsten ist. Für Probleme mit mehreren Variablen ist die richtige Antwort meist, das Problem als kleine lineare Algebra-Lösung neu zu formulieren, statt zu einem allgemeinen nichtlinearen Optimierer zu greifen.

6.17.3. Spezielle Funktionen

scipy.special stellt eine Handvoll statistischer und probabilistischer Funktionen bereit, die sich wie universelle Funktionen verhalten – sie akzeptieren einen Skalar, ein Iterable oder ein ndarray und geben ein Float-ndarray zurück:

x = np.linspace(0, 4, num=8)

sp.special.erf(x)         # error function
sp.special.erfc(x)        # complementary error function
sp.special.gamma(x + 1)   # gamma function
sp.special.gammaln(x + 1) # log-gamma function

Die Fehlerfunktion und ihr Komplement tauchen in der CDF einer Gaußverteilung auf – die Anwendung der Wahl für die Umrechnung zwischen einem gemessenen z-Wert und einer Wahrscheinlichkeit oder für die Berechnung des Schwanzintegrals einer Normalverteilung. Die Gamma- und Log-Gamma-Funktionen tauchen in Beta- / Chi-Quadrat- / Student-t-Berechnungen auf; gammaln ist die numerisch stabile Form für große Argumente, bei denen gamma selbst überlaufen würde.

6.17.4. Zufallszahlen

numpy.random stellt eine Klasse Generator bereit, die Abtastwerte aus gängigen Verteilungen zieht. Der Generator ist zustandsbehaftet: Jeder Aufruf rückt seinen internen Zustand vor, sodass aufeinanderfolgende Aufrufe unabhängige Abtastwerte zurückgeben:

from ulab import numpy as np

rng = np.random.Generator(seed=42)

rng.random(size=5)             # 5 uniform [0.0, 1.0) samples
rng.uniform(low=-1.0, high=1.0, size=10)
rng.normal(loc=0.0, scale=1.0, size=(2, 4))

Der Ausgabe-dtype ist immer float. size= akzeptiert eine Ganzzahl (1-D-Ausgabe) oder ein Tupel (n-D-Ausgabe); wird es weggelassen, wird ein einzelner Python-Float zurückgegeben.

Der Generator ist für Simulation, Dithering, synthetische Testdaten und jede andere Anwendung geeignet, bei der keine kryptografische Stärke erforderlich ist. Er ist nicht für Schlüssel oder Tokens geeignet; verwende dafür die System-Zufallsquelle über os.

6.17.5. Verfügbarkeit zur Build-Zeit

Ob jedes Submodul tatsächlich vorhanden ist, hängt davon ab, wie die Kamera gebaut wurde. scipy.optimize und scipy.special sind nicht auf jeder Kamera aktiviert; der Aufruf einer Funktion, die die Kamera nicht enthält, löst AttributeError aus. dir(sp), dir(sp.optimize), dir(np.random) und Verwandte melden, was auf der jeweils angesprochenen Kamera verfügbar ist.