6.17. פותרים ומספרים אקראיים

כאשר הפונקציה הנחקרת מוגדרת על ידי קוד Python ולא על ידי חוצץ של דגימות, משפחת כלים שונה היא הבחירה הנכונה: היכן נמצא השורש של הפונקציה, המינימום שלה, האינטגרל שלה על פני קטע נתון? תת-המודולים scipy.integrate ו-scipy.optimize מכסים את העבודה הזו. כל אלגוריתם קורא חזרה אל פונקציית ה-Python שסיפק המשתמש, ולכן העלות לכל איטרציה גבוהה יותר מצמצום של חוצץ; הנוחות היא באי-הצורך לכתוב את הפותר.

תת-המודול scipy.special מכסה את הפונקציות הסטטיסטיות המיוחדות (פונקציית השגיאה, גמא) שעולות בעת חישוב פונקציית ההתפלגות המצטברת של התפלגות הסתברות (CDF, ההסתברות שדגימה היא לכל היותר ערך נתון) או פונקציית צפיפות ההסתברות (PDF, הסבירות היחסית בערך נתון). numpy.random מכסה את מחולל המספרים הפסבדו-אקראיים עבור ערפול (dithering), סימולציה ונתוני בדיקה סינתטיים.

6.17.1. אינטגרציה נומרית של פונקציה הניתנת לקריאה

כאשר האינטגרנד הוא פונקציית Python ולא חוצץ של דגימות, scipy.integrate חושף ארבעה אלגוריתמי קוודרטורה:

  • quad() – גאוס-קרונרוד מסתגל. ברירת המחדל הנכונה לאינטגרנדים חלקים. מחזיר (value, error).

  • romberg() – רומברג / ניוטון-קוטס קלאסי. מחזיר מספר יחיד בנקודה צפה. הוצא משימוש במעלה הזרם; נכלל לשם תאימות.

  • simpson() – כלל סימפסון מסתגל. מחזיר מספר יחיד בנקודה צפה.

  • tanhsinh() – קוודרטורה דו-מעריכית. השתמשו בה כאשר לאינטגרנד יש סינגולריות בקצוות או גבול אינסופי. מחזירה (value, error).

האינטגרל הגאוסי המחושב באמצעות הכלל הדו-מעריכי (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))

פלט:

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

6.17.2. מציאת שורשים ומזעור

scipy.optimize מכסה שלושה פותרים קלאסיים של משתנה יחיד. כל איטרציה קוראת חזרה אל פונקציית ה-Python שסיפק המשתמש, ולכן ההאצה לעומת פותר ב-Python טהור היא צנועה (בערך פי 2); הנוחות היא באי-הצורך לכתוב את הפותר.

  • bisect() – מציאת שורש של f בקטע [a, b] על ידי חציית הקטע. ל-f(a) ו-f(b) חייבים להיות סימנים מנוגדים:

    def f(x):
        return x * x - 1
    
    sp.optimize.bisect(f, 0, 4)        # ~1.0
    
  • newton() – מציאת שורש באמצעות איטרציית סקאנט / ניוטון-רפסון:

    def f(x):
        return x * x * x - 2.0
    
    sp.optimize.newton(f, 3., tol=0.001, rtol=0.01)
    # ~1.260
    
  • fmin() – מציאת מינימום מקומי באמצעות שיטת הסימפלקס היורד (נלדר-מיד):

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

היקף המשתנה היחיד מספיק עבור רוב האופטימיזציות בצד המצלמה – קבוע כיול של חיישן, ההגבר (gain) הממקסם מדידת ניגודיות, הסף שבו דו-מודאליות של היסטוגרמה חדה ביותר. עבור בעיות רב-משתניות, התשובה הנכונה היא בדרך כלל לנסח מחדש את הבעיה כפתרון אלגברה לינארית קטן ולא להושיט יד לאופטימייזר לא-לינארי כללי.

6.17.3. פונקציות מיוחדות

scipy.special חושף קומץ פונקציות סטטיסטיות והסתברותיות שמתנהגות כפונקציות אוניברסליות – הן מקבלות סקלר, איטרבל או ndarray ומחזירות ndarray של מספרים בנקודה צפה:

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

פונקציית השגיאה והמשלים שלה מופיעות ב-CDF של גאוסיאן – היישום המועדף להמרה בין z-score נמדד להסתברות או לחישוב אינטגרל הזנב של התפלגות נורמלית. פונקציות הגמא ולוג-הגמא מופיעות בחישובי בטא / חי-בריבוע / סטודנט-t; gammaln היא הצורה היציבה נומרית עבור ארגומנטים גדולים שבהם gamma עצמה הייתה גולשת.

6.17.4. מספרים אקראיים

numpy.random מספק מחלקת Generator הדוגמת דגימות מהתפלגויות נפוצות. המחולל הוא בעל מצב: כל קריאה מקדמת את מצבו הפנימי, ולכן קריאות עוקבות מחזירות דגימות בלתי תלויות:

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))

ה-dtype של הפלט הוא תמיד float. size= מקבל מספר שלם (פלט חד-ממדי) או tuple (פלט n-ממדי); כאשר הוא מושמט, מוחזר מספר Python יחיד בנקודה צפה.

המחולל מתאים לסימולציה, ערפול (dithering), נתוני בדיקה סינתטיים וכל יישום אחר שבו אין צורך בחוזק קריפטוגרפי. הוא אינו מתאים למפתחות או אסימונים; עבור אלה השתמשו במקור האקראי של המערכת דרך os.

6.17.5. זמינות בזמן בנייה

האם כל תת-מודול נוכח בפועל תלוי באופן שבו נבנתה המצלמה. scipy.optimize ו-scipy.special אינם מופעלים בכל מצלמה; קריאה לפונקציה שהמצלמה אינה כוללת גורמת לשגיאת AttributeError. dir(sp), dir(sp.optimize), dir(np.random) וחבריהם מדווחים מה זמין במצלמה הנדונה.