10. NumPy#

“بیایید صریح باشیم: کار علم هیچ ربطی به اجماع ندارد. اجماع کار سیاست است. برعکس، علم فقط به یک محقق نیاز دارد که اتفاقاً درست بگوید، که به این معنی است که او نتایجی دارد که با ارجاع به دنیای واقعی قابل تأیید هستند. در علم، اجماع بی‌ربط است. آنچه مرتبط است، نتایج تکرارپذیر است.” – مایکل کرایتون

علاوه بر آنچه در Anaconda موجود است، این درس به کتابخانه‌های زیر نیاز دارد:

!pip install quantecon

Hide code cell output

Requirement already satisfied: quantecon in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (0.11.4)
Requirement already satisfied: numba>=0.49.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (0.65.1)
Requirement already satisfied: numpy>=1.17.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (2.4.6)
Requirement already satisfied: requests in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (2.34.2)
Requirement already satisfied: scipy>=1.5.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (1.18.0)
Requirement already satisfied: sympy in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (1.14.0)
Requirement already satisfied: llvmlite<0.48,>=0.47.0dev0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from numba>=0.49.0->quantecon) (0.47.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (3.4.7)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (3.18)
Requirement already satisfied: urllib3<3,>=1.26 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (2.7.0)
Requirement already satisfied: certifi>=2023.5.7 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (2026.6.17)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from sympy->quantecon) (1.3.0)

10.1. مروری کلی#

NumPy یک کتابخانه درجه یک برای برنامه‌نویسی عددی است

  • به طور گسترده در دانشگاه‌ها، امور مالی و صنعت استفاده می‌شود.

  • بالغ، سریع، پایدار و تحت توسعه مستمر است.

ما قبلاً در درس‌های قبلی کدهایی شامل NumPy دیده‌ایم.

در این درس، بحث سیستماتیک‌تری را در مورد موارد زیر آغاز خواهیم کرد:

  1. آرایه‌های NumPy و

  2. عملیات پردازش آرایه اساسی که توسط NumPy ارائه می‌شوند.

(برای یک مرجع جایگزین، به مستندات رسمی NumPy مراجعه کنید.)

ما از import های زیر استفاده خواهیم کرد.

import numpy as np
import random
import quantecon as qe
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm

10.2. آرایه‌های NumPy#

مشکل اساسی که NumPy حل می‌کند، پردازش سریع آرایه است.

مهم‌ترین ساختاری که NumPy تعریف می‌کند، نوع داده آرایه است که به صورت رسمی numpy.ndarray نامیده می‌شود.

آرایه‌های NumPy بخش بسیار بزرگی از اکوسیستم علمی Python را پشتیبانی می‌کنند.

10.2.1. مبانی#

برای ایجاد یک آرایه NumPy که فقط شامل صفر است از np.zeros استفاده می‌کنیم

a = np.zeros(3)
a
array([0., 0., 0.])
type(a)
numpy.ndarray

آرایه‌های NumPy تا حدی شبیه لیست‌های بومی Python هستند، به جز اینکه

  • داده‌ها باید همگن باشند (همه عناصر از یک نوع).

  • این انواع باید یکی از انواع داده (dtypes) ارائه شده توسط NumPy باشند.

مهم‌ترین این dtypes عبارتند از:

  • float64: عدد ممیز شناور 64 بیتی

  • int64: عدد صحیح 64 بیتی

  • bool: 8 بیتی True یا False

همچنین dtypes هایی برای نمایش اعداد مختلط، اعداد صحیح بدون علامت و غیره وجود دارد.

در ماشین‌های مدرن، dtype پیش‌فرض برای آرایه‌ها float64 است

a = np.zeros(3)
type(a[0])
numpy.float64

اگر بخواهیم از اعداد صحیح استفاده کنیم، می‌توانیم به صورت زیر مشخص کنیم:

a = np.zeros(3, dtype=int)
type(a[0])
numpy.int64

10.2.2. شکل و بعد#

انتساب زیر را در نظر بگیرید

z = np.zeros(10)

در اینجا z یک آرایه مسطح است — نه بردار سطر و نه ستون.

z.shape
(10,)

در اینجا tuple شکل فقط یک عنصر دارد که طول آرایه است (tuple های با یک عنصر با کاما پایان می‌یابند).

برای اضافه کردن یک بعد اضافی، می‌توانیم ویژگی shape را تغییر دهیم

z.shape = (10, 1)   # تبدیل آرایه مسطح به بردار ستونی (دو بعدی)
z
array([[0.],
       [0.],
       [0.],
       [0.],
       [0.],
       [0.],
       [0.],
       [0.],
       [0.],
       [0.]])
z = np.zeros(4)     # آرایه مسطح
z.shape = (2, 2)    # آرایه دو بعدی
z
array([[0., 0.],
       [0., 0.]])

در مورد آخر، برای ساخت آرایه 2×2، می‌توانیم یک tuple را نیز به تابع zeros() ارسال کنیم، مانند z = np.zeros((2, 2)).

10.2.3. ایجاد آرایه‌ها#

همان‌طور که دیده‌ایم، تابع np.zeros یک آرایه از صفرها ایجاد می‌کند.

احتمالاً می‌توانید حدس بزنید که np.ones چه چیزی ایجاد می‌کند.

مرتبط با آن np.empty است که آرایه‌هایی را در حافظه ایجاد می‌کند که بعداً می‌توان آن‌ها را با داده پر کرد

z = np.empty(3)
z
array([0., 0., 0.])

اعدادی که در اینجا می‌بینید مقادیر زباله هستند.

(Python سه قطعه متوالی 64 بیتی حافظه را اختصاص می‌دهد و محتویات موجود آن slot های حافظه به عنوان مقادیر float64 تفسیر می‌شوند)

برای راه‌اندازی یک شبکه از اعداد با فاصله یکسان از np.linspace استفاده کنید

z = np.linspace(2, 4, 5)  # از 2 تا 4، با 5 عنصر

برای ایجاد یک ماتریس همانی از np.identity یا np.eye استفاده کنید

z = np.identity(2)
z
array([[1., 0.],
       [0., 1.]])

علاوه بر این، آرایه‌های NumPy را می‌توان از لیست‌های Python، tuple ها و غیره با استفاده از np.array ایجاد کرد

z = np.array([10, 20])                 # ndarray از لیست Python
z
array([10, 20])
type(z)
numpy.ndarray
z = np.array((10, 20), dtype=float)    # در اینجا 'float' معادل 'np.float64' است
z
array([10., 20.])
z = np.array([[1, 2], [3, 4]])         # آرایه 2 بعدی از یک لیست از لیست‌ها
z
array([[1, 2],
       [3, 4]])

همچنین np.asarray را ببینید که عملکرد مشابهی انجام می‌دهد، اما یک نسخه مجزا از داده‌های موجود در یک آرایه NumPy ایجاد نمی‌کند.

برای خواندن داده‌های آرایه از یک فایل متنی حاوی داده‌های عددی، از np.loadtxt استفاده کنید — برای جزئیات به مستندات مراجعه کنید.

10.2.4. نمایه‌گذاری آرایه#

برای یک آرایه مسطح، نمایه‌گذاری مانند توالی‌های Python است:

z = np.linspace(1, 2, 5)
z
array([1.  , 1.25, 1.5 , 1.75, 2.  ])
z[0]
np.float64(1.0)
z[0:2]  # دو عنصر، از عنصر 0 شروع می‌شود
array([1.  , 1.25])
z[-1]
np.float64(2.0)

برای آرایه‌های دو بعدی، نحو نمایه به صورت زیر است:

z = np.array([[1, 2], [3, 4]])
z
array([[1, 2],
       [3, 4]])
z[0, 0]
np.int64(1)
z[0, 1]
np.int64(2)

و الی آخر.

ستون‌ها و سطرها را می‌توان به صورت زیر استخراج کرد

z[0, :]
array([1, 2])
z[:, 1]
array([2, 4])

آرایه‌های NumPy از اعداد صحیح نیز می‌توانند برای استخراج عناصر استفاده شوند

z = np.linspace(2, 4, 5)
z
array([2. , 2.5, 3. , 3.5, 4. ])
indices = np.array((0, 2, 3))
z[indices]
array([2. , 3. , 3.5])

در نهایت، یک آرایه از dtype bool می‌تواند برای استخراج عناصر استفاده شود

z
array([2. , 2.5, 3. , 3.5, 4. ])
d = np.array([0, 1, 1, 0, 0], dtype=bool)
d
array([False,  True,  True, False, False])
z[d]
array([2.5, 3. ])

در زیر خواهیم دید که چرا این مفید است.

یک نکته جانبی: همه عناصر یک آرایه را می‌توان با استفاده از نماد slice برابر با یک عدد قرار داد

z = np.empty(3)
z
array([2. , 3. , 3.5])
z[:] = 42
z
array([42., 42., 42.])

10.2.5. متدهای آرایه#

آرایه‌ها متدهای مفیدی دارند که همگی به دقت بهینه شده‌اند

a = np.array((4, 3, 2, 1))
a
array([4, 3, 2, 1])
a.sort()              # a را در محل مرتب می‌کند
a
array([1, 2, 3, 4])
a.sum()               # مجموع
np.int64(10)
a.mean()              # میانگین
np.float64(2.5)
a.max()               # بیشینه
np.int64(4)
a.argmax()            # ایندکس عنصر حداکثر را برمی‌گرداند
np.int64(3)
a.cumsum()            # مجموع تجمعی عناصر a
array([ 1,  3,  6, 10])
a.cumprod()           # حاصل‌ضرب تجمعی عناصر a
array([ 1,  2,  6, 24])
a.var()               # واریانس
np.float64(1.25)
a.std()               # انحراف معیار
np.float64(1.118033988749895)
a.shape = (2, 2)
a.T                   # معادل a.transpose()
array([[1, 3],
       [2, 4]])

متد دیگری که ارزش دانستن دارد searchsorted() است.

اگر z یک آرایه غیر نزولی باشد، پس z.searchsorted(a) ایندکس اولین عنصر z که >= a است را برمی‌گرداند

z = np.linspace(2, 4, 5)
z
array([2. , 2.5, 3. , 3.5, 4. ])
z.searchsorted(2.2)
np.int64(1)

10.3. عملیات حسابی#

عملگرهای +، -، *، / و ** همگی به صورت عنصر به عنصر روی آرایه‌ها عمل می‌کنند

a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
a + b
array([ 6,  8, 10, 12])
a * b
array([ 5, 12, 21, 32])

می‌توانیم یک اسکالر را به هر عنصر به صورت زیر اضافه کنیم

a + 10
array([11, 12, 13, 14])

ضرب اسکالر مشابه است

a * 10
array([10, 20, 30, 40])

آرایه‌های دوبعدی از همان قوانین کلی پیروی می‌کنند

A = np.ones((2, 2))
B = np.ones((2, 2))
A + B
array([[2., 2.],
       [2., 2.]])
A + 10
array([[11., 11.],
       [11., 11.]])
A * B
array([[1., 1.],
       [1., 1.]])

به ویژه، A * B حاصل‌ضرب ماتریسی نیست، بلکه یک حاصل‌ضرب عنصر به عنصر است.

10.4. ضرب ماتریسی#

ما از نماد @ برای ضرب ماتریسی استفاده می‌کنیم، به صورت زیر:

A = np.ones((2, 2))
B = np.ones((2, 2))
A @ B
array([[2., 2.],
       [2., 2.]])

نحو با آرایه‌های مسطح کار می‌کند — NumPy حدس آموزش‌دیده‌ای از آنچه شما می‌خواهید می‌زند:

A @ (0, 1)
array([1., 1.])

از آنجا که ما در حال ضرب سمت راست هستیم، tuple به عنوان یک بردار ستونی تلقی می‌شود.

10.5. Broadcasting#

(این بخش یک بحث عالی در مورد broadcasting ارائه شده توسط Jake VanderPlas را گسترش می‌دهد.)

Note

Broadcasting یک جنبه بسیار مهم از NumPy است. در عین حال، broadcasting پیشرفته نسبتاً پیچیده است و برخی از جزئیات زیر را می‌توان در اولین مرور اجمالی خواند.

در عملیات عنصر به عنصر، آرایه‌ها ممکن است شکل یکسانی نداشته باشند.

وقتی این اتفاق می‌افتد، NumPy به طور خودکار آرایه‌ها را به شکل یکسان گسترش می‌دهد هرگاه امکان‌پذیر باشد.

این ویژگی مفید (اما گاهی گیج‌کننده) در NumPy broadcasting نامیده می‌شود.

ارزش broadcasting این است که

  • حلقه‌های for می‌توانند اجتناب شوند، که به کد عددی کمک می‌کند تا سریع اجرا شود و

  • broadcasting می‌تواند به ما اجازه دهد عملیات را روی آرایه‌ها پیاده‌سازی کنیم بدون اینکه واقعاً برخی ابعاد این آرایه‌ها را در حافظه ایجاد کنیم، که می‌تواند مهم باشد وقتی آرایه‌ها بزرگ هستند.

به عنوان مثال، فرض کنید a یک آرایه \(3 \times 3\) است (a -> (3, 3))، در حالی که b یک آرایه مسطح با سه عنصر است (b -> (3,)).

هنگام جمع کردن آن‌ها با هم، NumPy به طور خودکار b -> (3,) را به b -> (3, 3) گسترش می‌دهد.

جمع عنصر به عنصر منجر به یک آرایه \(3 \times 3\) می‌شود

a = np.array(
        [[1, 2, 3], 
         [4, 5, 6], 
         [7, 8, 9]])
b = np.array([3, 6, 9])

a + b
array([[ 4,  8, 12],
       [ 7, 11, 15],
       [10, 14, 18]])

در اینجا یک نمایش بصری از این عملیات broadcasting آورده شده است:

Hide code cell source

# Adapted and modified based on the code in the book written by Jake VanderPlas (see https://jakevdp.github.io/PythonDataScienceHandbook/06.00-figure-code.html#Broadcasting)
# Originally from astroML: see https://www.astroml.org/book_figures/appendix/fig_broadcast_visual.html


def draw_cube(ax, xy, size, depth=0.4,
              edges=None, label=None, label_kwargs=None, **kwargs):
    """draw and label a cube.  edges is a list of numbers between
    1 and 12, specifying which of the 12 cube edges to draw"""
    if edges is None:
        edges = range(1, 13)

    x, y = xy

    if 1 in edges:
        ax.plot([x, x + size],
                [y + size, y + size], **kwargs)
    if 2 in edges:
        ax.plot([x + size, x + size],
                [y, y + size], **kwargs)
    if 3 in edges:
        ax.plot([x, x + size],
                [y, y], **kwargs)
    if 4 in edges:
        ax.plot([x, x],
                [y, y + size], **kwargs)

    if 5 in edges:
        ax.plot([x, x + depth],
                [y + size, y + depth + size], **kwargs)
    if 6 in edges:
        ax.plot([x + size, x + size + depth],
                [y + size, y + depth + size], **kwargs)
    if 7 in edges:
        ax.plot([x + size, x + size + depth],
                [y, y + depth], **kwargs)
    if 8 in edges:
        ax.plot([x, x + depth],
                [y, y + depth], **kwargs)

    if 9 in edges:
        ax.plot([x + depth, x + depth + size],
                [y + depth + size, y + depth + size], **kwargs)
    if 10 in edges:
        ax.plot([x + depth + size, x + depth + size],
                [y + depth, y + depth + size], **kwargs)
    if 11 in edges:
        ax.plot([x + depth, x + depth + size],
                [y + depth, y + depth], **kwargs)
    if 12 in edges:
        ax.plot([x + depth, x + depth],
                [y + depth, y + depth + size], **kwargs)

    if label:
        if label_kwargs is None:
            label_kwargs = {}
        ax.text(x + 0.5 * size, y + 0.5 * size, label,
                ha='center', va='center', **label_kwargs)

solid = dict(c='black', ls='-', lw=1,
             label_kwargs=dict(color='k'))
dotted = dict(c='black', ls='-', lw=0.5, alpha=0.5,
              label_kwargs=dict(color='gray'))
depth = 0.3

# Draw a figure and axis with no boundary
fig = plt.figure(figsize=(5, 1), facecolor='w')
ax = plt.axes([0, 0, 1, 1], xticks=[], yticks=[], frameon=False)

# first block
draw_cube(ax, (1, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '1', **solid)
draw_cube(ax, (2, 7.5), 1, depth, [1, 2, 3, 6, 9], '2', **solid)
draw_cube(ax, (3, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '3', **solid)

draw_cube(ax, (1, 6.5), 1, depth, [2, 3, 4], '4', **solid)
draw_cube(ax, (2, 6.5), 1, depth, [2, 3], '5', **solid)
draw_cube(ax, (3, 6.5), 1, depth, [2, 3, 7, 10], '6', **solid)

draw_cube(ax, (1, 5.5), 1, depth, [2, 3, 4], '7', **solid)
draw_cube(ax, (2, 5.5), 1, depth, [2, 3], '8', **solid)
draw_cube(ax, (3, 5.5), 1, depth, [2, 3, 7, 10], '9', **solid)

# second block
draw_cube(ax, (6, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '3', **solid)
draw_cube(ax, (7, 7.5), 1, depth, [1, 2, 3, 6, 9], '6', **solid)
draw_cube(ax, (8, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '9', **solid)

draw_cube(ax, (6, 6.5), 1, depth, range(2, 13), '3', **dotted)
draw_cube(ax, (7, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '6', **dotted)
draw_cube(ax, (8, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '9', **dotted)

draw_cube(ax, (6, 5.5), 1, depth, [2, 3, 4, 7, 8, 10, 11, 12], '3', **dotted)
draw_cube(ax, (7, 5.5), 1, depth, [2, 3, 7, 10, 11], '6', **dotted)
draw_cube(ax, (8, 5.5), 1, depth, [2, 3, 7, 10, 11], '9', **dotted)

# third block
draw_cube(ax, (12, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '4', **solid)
draw_cube(ax, (13, 7.5), 1, depth, [1, 2, 3, 6, 9], '8', **solid)
draw_cube(ax, (14, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '12', **solid)

draw_cube(ax, (12, 6.5), 1, depth, [2, 3, 4], '7', **solid)
draw_cube(ax, (13, 6.5), 1, depth, [2, 3], '11', **solid)
draw_cube(ax, (14, 6.5), 1, depth, [2, 3, 7, 10], '15', **solid)

draw_cube(ax, (12, 5.5), 1, depth, [2, 3, 4], '10', **solid)
draw_cube(ax, (13, 5.5), 1, depth, [2, 3], '14', **solid)
draw_cube(ax, (14, 5.5), 1, depth, [2, 3, 7, 10], '18', **solid)

ax.text(5, 7.0, '+', size=12, ha='center', va='center')
ax.text(10.5, 7.0, '=', size=12, ha='center', va='center');
_images/115715ec71494a2e944c536d58b4fb480ed6b9d7d41de22509fe8f70c04cdd24.png

در مورد b -> (3, 1) چطور؟

در این حالت، NumPy به طور خودکار b -> (3, 1) را به b -> (3, 3) گسترش می‌دهد.

جمع عنصر به عنصر سپس منجر به یک ماتریس \(3 \times 3\) می‌شود

b.shape = (3, 1)

a + b
array([[ 4,  5,  6],
       [10, 11, 12],
       [16, 17, 18]])

در اینجا یک نمایش بصری از این عملیات broadcasting آورده شده است:

Hide code cell source

fig = plt.figure(figsize=(5, 1), facecolor='w')
ax = plt.axes([0, 0, 1, 1], xticks=[], yticks=[], frameon=False)

# first block
draw_cube(ax, (1, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '1', **solid)
draw_cube(ax, (2, 7.5), 1, depth, [1, 2, 3, 6, 9], '2', **solid)
draw_cube(ax, (3, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '3', **solid)

draw_cube(ax, (1, 6.5), 1, depth, [2, 3, 4], '4', **solid)
draw_cube(ax, (2, 6.5), 1, depth, [2, 3], '5', **solid)
draw_cube(ax, (3, 6.5), 1, depth, [2, 3, 7, 10], '6', **solid)

draw_cube(ax, (1, 5.5), 1, depth, [2, 3, 4], '7', **solid)
draw_cube(ax, (2, 5.5), 1, depth, [2, 3], '8', **solid)
draw_cube(ax, (3, 5.5), 1, depth, [2, 3, 7, 10], '9', **solid)

# second block
draw_cube(ax, (6, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 7, 9, 10], '3', **solid)
draw_cube(ax, (7, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '3', **dotted)
draw_cube(ax, (8, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '3', **dotted)

draw_cube(ax, (6, 6.5), 1, depth, [2, 3, 4, 7, 10], '6', **solid)
draw_cube(ax, (7, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '6', **dotted)
draw_cube(ax, (8, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '6', **dotted)

draw_cube(ax, (6, 5.5), 1, depth, [2, 3, 4, 7, 10], '9', **solid)
draw_cube(ax, (7, 5.5), 1, depth, [2, 3, 7, 10, 11], '9', **dotted)
draw_cube(ax, (8, 5.5), 1, depth, [2, 3, 7, 10, 11], '9', **dotted)

# third block
draw_cube(ax, (12, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '4', **solid)
draw_cube(ax, (13, 7.5), 1, depth, [1, 2, 3, 6, 9], '5', **solid)
draw_cube(ax, (14, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '6', **solid)

draw_cube(ax, (12, 6.5), 1, depth, [2, 3, 4], '10', **solid)
draw_cube(ax, (13, 6.5), 1, depth, [2, 3], '11', **solid)
draw_cube(ax, (14, 6.5), 1, depth, [2, 3, 7, 10], '12', **solid)

draw_cube(ax, (12, 5.5), 1, depth, [2, 3, 4], '16', **solid)
draw_cube(ax, (13, 5.5), 1, depth, [2, 3], '17', **solid)
draw_cube(ax, (14, 5.5), 1, depth, [2, 3, 7, 10], '18', **solid)

ax.text(5, 7.0, '+', size=12, ha='center', va='center')
ax.text(10.5, 7.0, '=', size=12, ha='center', va='center');
_images/bf46cd4b5d224c99a13c9cce28eb9190667cbbd5ef4977e1af59bb5d30ddf875.png

در برخی موارد، هر دو عملوند گسترش داده می‌شوند.

وقتی a -> (3,) و b -> (3, 1) داریم، a به a -> (3, 3) گسترش داده خواهد شد، و b به b -> (3, 3) گسترش داده خواهد شد.

در این حالت، جمع عنصر به عنصر منجر به یک ماتریس \(3 \times 3\) می‌شود

a = np.array([3, 6, 9])
b = np.array([2, 3, 4])
b.shape = (3, 1)

a + b
array([[ 5,  8, 11],
       [ 6,  9, 12],
       [ 7, 10, 13]])

در اینجا یک نمایش بصری از این عملیات broadcasting آورده شده است:

Hide code cell source

# Draw a figure and axis with no boundary
fig = plt.figure(figsize=(5, 1), facecolor='w')
ax = plt.axes([0, 0, 1, 1], xticks=[], yticks=[], frameon=False)

# first block
draw_cube(ax, (1, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '3', **solid)
draw_cube(ax, (2, 7.5), 1, depth, [1, 2, 3, 6, 9], '6', **solid)
draw_cube(ax, (3, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '9', **solid)

draw_cube(ax, (1, 6.5), 1, depth, range(2, 13), '3', **dotted)
draw_cube(ax, (2, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '6', **dotted)
draw_cube(ax, (3, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '9', **dotted)

draw_cube(ax, (1, 5.5), 1, depth, [2, 3, 4, 7, 8, 10, 11, 12], '3', **dotted)
draw_cube(ax, (2, 5.5), 1, depth, [2, 3, 7, 10, 11], '6', **dotted)
draw_cube(ax, (3, 5.5), 1, depth, [2, 3, 7, 10, 11], '9', **dotted)

# second block
draw_cube(ax, (6, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 7, 9, 10], '2', **solid)
draw_cube(ax, (7, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '2', **dotted)
draw_cube(ax, (8, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '2', **dotted)

draw_cube(ax, (6, 6.5), 1, depth, [2, 3, 4, 7, 10], '3', **solid)
draw_cube(ax, (7, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '3', **dotted)
draw_cube(ax, (8, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '3', **dotted)

draw_cube(ax, (6, 5.5), 1, depth, [2, 3, 4, 7, 10], '4', **solid)
draw_cube(ax, (7, 5.5), 1, depth, [2, 3, 7, 10, 11], '4', **dotted)
draw_cube(ax, (8, 5.5), 1, depth, [2, 3, 7, 10, 11], '4', **dotted)

# third block
draw_cube(ax, (12, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '5', **solid)
draw_cube(ax, (13, 7.5), 1, depth, [1, 2, 3, 6, 9], '8', **solid)
draw_cube(ax, (14, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '11', **solid)

draw_cube(ax, (12, 6.5), 1, depth, [2, 3, 4], '6', **solid)
draw_cube(ax, (13, 6.5), 1, depth, [2, 3], '9', **solid)
draw_cube(ax, (14, 6.5), 1, depth, [2, 3, 7, 10], '12', **solid)

draw_cube(ax, (12, 5.5), 1, depth, [2, 3, 4], '7', **solid)
draw_cube(ax, (13, 5.5), 1, depth, [2, 3], '10', **solid)
draw_cube(ax, (14, 5.5), 1, depth, [2, 3, 7, 10], '13', **solid)

ax.text(5, 7.0, '+', size=12, ha='center', va='center')
ax.text(10.5, 7.0, '=', size=12, ha='center', va='center');
_images/78242dd6d48ea60eddcd4cf0c915b782324c1186e96ff4582b06410dce4e224b.png

در حالی که broadcasting بسیار مفید است، گاهی اوقات می‌تواند گیج‌کننده به نظر برسد.

برای مثال، بیایید سعی کنیم a -> (3, 2) و b -> (3,) را جمع کنیم.

a = np.array(
      [[1, 2],
       [4, 5],
       [7, 8]])
b = np.array([3, 6, 9])

a + b
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[62], line 7
      3        [4, 5],
      4        [7, 8]])
      5 b = np.array([3, 6, 9])
      6 
----> 7 a + b

ValueError: operands could not be broadcast together with shapes (3,2) (3,) 

ValueError به ما می‌گوید که عملوندها نمی‌توانند با هم broadcast شوند.

در اینجا یک نمایش بصری برای نشان دادن اینکه چرا این broadcasting نمی‌تواند اجرا شود آورده شده است:

Hide code cell source

# Draw a figure and axis with no boundary
fig = plt.figure(figsize=(3, 1.3), facecolor='w')
ax = plt.axes([0, 0, 1, 1], xticks=[], yticks=[], frameon=False)

# first block
draw_cube(ax, (1, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '1', **solid)
draw_cube(ax, (2, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '2', **solid)

draw_cube(ax, (1, 6.5), 1, depth, [2, 3, 4], '4', **solid)
draw_cube(ax, (2, 6.5), 1, depth, [2, 3, 7, 10], '5', **solid)

draw_cube(ax, (1, 5.5), 1, depth, [2, 3, 4], '7', **solid)
draw_cube(ax, (2, 5.5), 1, depth, [2, 3, 7, 10], '8', **solid)

# second block
draw_cube(ax, (6, 7.5), 1, depth, [1, 2, 3, 4, 5, 6, 9], '3', **solid)
draw_cube(ax, (7, 7.5), 1, depth, [1, 2, 3, 6, 9], '6', **solid)
draw_cube(ax, (8, 7.5), 1, depth, [1, 2, 3, 6, 7, 9, 10], '9', **solid)

draw_cube(ax, (6, 6.5), 1, depth, range(2, 13), '3', **dotted)
draw_cube(ax, (7, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '6', **dotted)
draw_cube(ax, (8, 6.5), 1, depth, [2, 3, 6, 7, 9, 10, 11], '9', **dotted)

draw_cube(ax, (6, 5.5), 1, depth, [2, 3, 4, 7, 8, 10, 11, 12], '3', **dotted)
draw_cube(ax, (7, 5.5), 1, depth, [2, 3, 7, 10, 11], '6', **dotted)
draw_cube(ax, (8, 5.5), 1, depth, [2, 3, 7, 10, 11], '9', **dotted)


ax.text(4.5, 7.0, '+', size=12, ha='center', va='center')
ax.text(10, 7.0, '=', size=12, ha='center', va='center')
ax.text(11, 7.0, '?', size=16, ha='center', va='center');
_images/95f74aeaf96ecfc47d99a21190ce6c6a3c2f554267f7d1d000e549162c4ca44c.png

می‌بینیم که NumPy نمی‌تواند آرایه‌ها را به یک اندازه گسترش دهد.

این به این دلیل است که، وقتی b از b -> (3,) به b -> (3, 3) گسترش می‌یابد، NumPy نمی‌تواند b را با a -> (3, 2) مطابقت دهد.

زمانی که به ابعاد بالاتر می‌رویم، موارد پیچیده‌تر می‌شوند.

برای کمک به ما، می‌توانیم از لیست قوانین زیر استفاده کنیم:

  • مرحله 1: وقتی ابعاد دو آرایه مطابقت ندارند، NumPy ابعادی را به آن یک که ابعاد کمتری دارد با اضافه کردن بعد (ابعاد) در سمت چپ ابعاد موجود گسترش می‌دهد.

    • برای مثال، اگر a -> (3, 3) و b -> (3,) باشد، broadcasting یک بعد به سمت چپ اضافه خواهد کرد تا b -> (1, 3) شود؛

    • اگر a -> (2, 2, 2) و b -> (2, 2) باشد، broadcasting یک بعد به سمت چپ اضافه خواهد کرد تا b -> (1, 2, 2) شود؛

    • اگر a -> (3, 2, 2) و b -> (2,) باشد، broadcasting دو بعد به سمت چپ اضافه خواهد کرد تا b -> (1, 1, 2) شود (همچنین می‌توانید این فرآیند را به عنوان عبور از مرحله 1 دو بار ببینید).

  • مرحله 2: وقتی دو آرایه بعد یکسانی دارند اما شکل‌های متفاوت، NumPy سعی خواهد کرد ابعادی را که شاخص شکل آن‌ها 1 است گسترش دهد.

    • برای مثال، اگر a -> (1, 3) و b -> (3, 1) باشد، broadcasting ابعاد با شکل 1 را در هر دو a و b گسترش خواهد داد تا a -> (3, 3) و b -> (3, 3) شوند؛

    • اگر a -> (2, 2, 2) و b -> (1, 2, 2) باشد، broadcasting بعد اول b را گسترش خواهد داد تا b -> (2, 2, 2) شود؛

    • اگر a -> (3, 2, 2) و b -> (1, 1, 2) باشد، broadcasting b را در همه ابعاد با شکل 1 گسترش خواهد داد تا b -> (3, 2, 2) شود.

  • مرحله 3: پس از مرحله 1 و 2، اگر دو آرایه هنوز مطابقت نداشته باشند، یک ValueError ایجاد خواهد شد. برای مثال، فرض کنید a -> (2, 2, 3) و b -> (2, 2) باشند

    • طبق مرحله 1، b به b -> (1, 2, 2) گسترش خواهد یافت؛

    • طبق مرحله 2، b به b -> (2, 2, 2) گسترش خواهد یافت؛

    • می‌بینیم که پس از دو مرحله اول، آن‌ها با یکدیگر مطابقت ندارند. بنابراین، یک ValueError ایجاد خواهد شد

10.6. قابلیت تغییر و کپی کردن آرایه‌ها#

آرایه‌های NumPy انواع داده قابل تغییر هستند، مانند لیست‌های Python.

به عبارت دیگر، محتویات آن‌ها می‌توانند پس از مقداردهی اولیه در حافظه تغییر یابند (جهش یابند).

این مناسب است اما، زمانی که با مدل نام‌گذاری و ارجاع Python ترکیب می‌شود، می‌تواند منجر به اشتباهات توسط مبتدیان NumPy شود.

در این بخش برخی از موضوعات کلیدی را بررسی می‌کنیم.

10.6.1. قابلیت تغییر#

قبلاً نمونه‌هایی از قابلیت تغییر را در بالا دیدیم.

در اینجا یک مثال دیگر از جهش یک آرایه NumPy آورده شده است

a = np.array([42, 44])
a
array([42, 44])
a[-1] = 0  # عنصر آخر را به 0 تغییر دهید
a
array([42,  0])

قابلیت تغییر منجر به رفتار زیر می‌شود (که می‌تواند برای برنامه‌نویسان MATLAB شوکه‌کننده باشد…)

rng = np.random.default_rng()
a = rng.standard_normal(3)
a
array([0.06659573, 0.63505303, 1.18147425])
b = a
b[0] = 0.0
a
array([0.        , 0.63505303, 1.18147425])

آنچه اتفاق افتاده این است که ما a را با تغییر دادن b تغییر داده‌ایم.

نام b به a متصل است و صرفاً یک ارجاع دیگر به آرایه می‌شود (مدل انتساب Python با جزئیات بیشتر بعداً در دوره شرح داده شده است).

از این رو، حقوق برابری برای ایجاد تغییرات در آن آرایه دارد.

این در واقع معقول‌ترین رفتار پیش‌فرض است!

این به این معنی است که ما فقط اشاره‌گرها را به داده منتقل می‌کنیم، به جای ایجاد کپی.

ایجاد کپی از نظر سرعت و حافظه گران است.

10.6.2. ایجاد کپی#

البته هنگام نیاز می‌توان b را یک کپی مستقل از a ساخت.

این کار می‌تواند با استفاده از np.copy انجام شود

a = rng.standard_normal(3)
a
array([ 0.40324852, -0.72292693,  0.91765211])
b = np.copy(a)
b
array([ 0.40324852, -0.72292693,  0.91765211])

اکنون b یک کپی مستقل است (که deep copy نامیده می‌شود)

b[:] = 1
b
array([1., 1., 1.])
a
array([ 0.40324852, -0.72292693,  0.91765211])

توجه کنید که تغییر در b بر a تأثیر نگذاشته است.

10.7. ویژگی‌های اضافی#

بیایید نگاهی به برخی ویژگی‌های مفید دیگر NumPy بیندازیم.

10.7.1. توابع جهانی#

NumPy نسخه‌هایی از توابع استاندارد log، exp، sin و غیره را فراهم می‌کند که به صورت عنصر به عنصر روی آرایه‌ها عمل می‌کنند

z = np.array([1, 2, 3])
np.sin(z)
array([0.84147098, 0.90929743, 0.14112001])

این نیاز به حلقه‌های صریح عنصر به عنصر مانند موارد زیر را از بین می‌برد

n = len(z)
y = np.empty(n)
for i in range(n):
    y[i] = np.sin(z[i])

از آنجا که آن‌ها به صورت عنصر به عنصر روی آرایه‌ها عمل می‌کنند، این توابع گاهی اوقات توابع برداری شده نامیده می‌شوند.

در اصطلاح NumPy، آن‌ها همچنین ufuncها، یا توابع جهانی نامیده می‌شوند.

همان‌طور که در بالا دیدیم، عملیات حسابی معمول (+، *، و غیره) نیز به صورت عنصر به عنصر کار می‌کنند، و ترکیب این‌ها با ufunc ها مجموعه بسیار بزرگی از توابع سریع عنصر به عنصر را می‌دهد.

z
array([1, 2, 3])
(1 / np.sqrt(2 * np.pi)) * np.exp(- 0.5 * z**2)
array([0.24197072, 0.05399097, 0.00443185])

همه توابع تعریف شده توسط کاربر به صورت عنصر به عنصر عمل نخواهند کرد.

برای مثال، ارسال تابع f تعریف شده در زیر به یک آرایه NumPy باعث ValueError می‌شود

def f(x):
    return 1 if x > 0 else 0

تابع NumPy np.where یک جایگزین برداری شده ارائه می‌دهد:

x = rng.standard_normal(4)
x
array([ 0.35612417,  1.37535143,  0.26586937, -0.05957849])
np.where(x > 0, 1, 0)  # اگر x > 0 درست باشد 1 درج کنید، در غیر این صورت 0
array([1, 1, 1, 0])

همچنین می‌توانید از np.vectorize برای برداری کردن یک تابع داده شده استفاده کنید

f = np.vectorize(f)
f(x)                # ارسال همان بردار x مانند مثال قبلی
array([1, 1, 1, 0])

با این حال، این رویکرد همیشه همان سرعت یک تابع برداری شده با دقت بیشتر ساخته شده را به دست نمی‌آورد.

(بعداً خواهیم دید که JAX یک نسخه قدرتمند از np.vectorize دارد که می‌تواند و معمولاً کد بسیار کارآمدی تولید می‌کند.)

10.7.2. مقایسه‌ها#

به عنوان یک قاعده، مقایسه‌ها روی آرایه‌ها به صورت عنصر به عنصر انجام می‌شوند

z = np.array([2, 3])
y = np.array([2, 3])
z == y
array([ True,  True])
y[0] = 5
z == y
array([False,  True])
z != y
array([ True, False])

وضعیت برای >، <، >= و <= مشابه است.

همچنین می‌توانیم مقایسه‌ها را با اسکالرها انجام دهیم

z = np.linspace(0, 10, 5)
z
array([ 0. ,  2.5,  5. ,  7.5, 10. ])
z > 3
array([False, False,  True,  True,  True])

این به ویژه برای استخراج شرطی مفید است

b = z > 3
b
array([False, False,  True,  True,  True])
z[b]
array([ 5. ,  7.5, 10. ])

البته می‌توانیم — و اغلب انجام می‌دهیم — این کار را در یک مرحله انجام دهیم

z[z > 3]
array([ 5. ,  7.5, 10. ])

10.7.3. بسته‌های فرعی#

NumPy برخی از قابلیت‌های اضافی مرتبط با برنامه‌نویسی علمی را از طریق بسته‌های فرعی خود ارائه می‌دهد.

قبلاً دیده‌ایم که چگونه می‌توانیم با استفاده از Generator تصادفی NumPy متغیرهای تصادفی تولید کنیم.

z = rng.standard_normal(10000)  # تولید توزیع نرمال استاندارد
y = rng.binomial(10, 0.5, size=1000)    # 1000 نمونه از Bin(10, 0.5)
y.mean()
np.float64(5.083)

یکی دیگر از بسته‌های فرعی رایج استفاده شده np.linalg است

A = np.array([[1, 2], [3, 4]])

np.linalg.det(A)           # محاسبه دترمینان
np.float64(-2.0000000000000004)
np.linalg.inv(A)           # محاسبه معکوس
array([[-2. ,  1. ],
       [ 1.5, -0.5]])

بسیاری از این قابلیت‌ها همچنین در SciPy موجود است، مجموعه‌ای از ماژول‌ها که روی NumPy ساخته شده‌اند.

نسخه‌های SciPy را به زودی با جزئیات بیشتر پوشش خواهیم داد.

برای فهرست جامعی از آنچه در NumPy موجود است به این مستندات مراجعه کنید.

10.7.4. چندنخی ضمنی#

قبلاً مفهوم موازی‌سازی از طریق چندنخی را مورد بحث قرار دادیم.

NumPy سعی می‌کند چندنخی را در بسیاری از کد کامپایل شده خود پیاده‌سازی کند.

بیایید نگاهی به یک مثال بیندازیم تا این را در عمل ببینیم.

قطعه کد بعدی مقادیر ویژه تعداد زیادی ماتریس تولید شده تصادفی را محاسبه می‌کند.

اجرای آن چند ثانیه طول می‌کشد.

n = 20
m = 1000
for i in range(n):
    X = rng.standard_normal((m, m))
    λ = np.linalg.eigvals(X)

اکنون، بیایید نگاهی به خروجی مانیتور سیستم htop در دستگاه ما در حالی که این کد در حال اجرا است بیندازیم:

_images/htop_parallel_npmat.png

می‌بینیم که 4 مورد از 8 CPU با سرعت کامل در حال اجرا هستند.

این به این دلیل است که روتین eigvals NumPy به زیبایی وظایف را تقسیم می‌کند و آن‌ها را به نخ‌های مختلف توزیع می‌کند.

10.8. تمرین‌ها#

Exercise 10.1

عبارت چندجمله‌ای زیر را در نظر بگیرید

(10.1)#\[p(x) = a_0 + a_1 x + a_2 x^2 + \cdots a_N x^N = \sum_{n=0}^N a_n x^n\]

قبلاً، یک تابع ساده p(x, coeff) برای ارزیابی (10.1) بدون در نظر گرفتن کارایی نوشتید.

اکنون یک تابع جدید بنویسید که همان کار را انجام می‌دهد، اما از آرایه‌های NumPy و عملیات آرایه برای محاسبات خود استفاده می‌کند، به جای هر شکلی از حلقه Python.

(چنین قابلیتی قبلاً به عنوان np.poly1d پیاده‌سازی شده است، اما به خاطر تمرین از این کلاس استفاده نکنید)

Exercise 10.2

فرض کنید q یک آرایه NumPy با طول n با q.sum() == 1 باشد.

فرض کنید که q یک تابع جرم احتمال را نمایش می‌دهد.

می‌خواهیم یک متغیر تصادفی گسسته \(x\) تولید کنیم به طوری که \(\mathbb P\{x = i\} = q_i\).

به عبارت دیگر، x مقادیری را در range(len(q)) می‌گیرد و x = i با احتمال q[i].

الگوریتم استاندارد (تبدیل معکوس) به صورت زیر است:

  • فاصله واحد \([0, 1]\) را به \(n\) زیر فاصله \(I_0, I_1, \ldots, I_{n-1}\) تقسیم کنید به طوری که طول \(I_i\) برابر با \(q_i\) باشد.

  • یک متغیر تصادفی یکنواخت \(U\) در \([0, 1]\) رسم کنید و \(i\) ای را برگردانید که \(U \in I_i\).

احتمال رسم \(i\) برابر با طول \(I_i\) است که برابر با \(q_i\) است.

می‌توانیم الگوریتم را به صورت زیر پیاده‌سازی کنیم

from random import uniform

def sample(q):
    a = 0.0
    U = uniform(0, 1)
    for i in range(len(q)):
        if a < U <= a + q[i]:
            return i
        a = a + q[i]

اگر نمی‌توانید ببینید این چگونه کار می‌کند، سعی کنید جریان را برای یک مثال ساده، مانند q = [0.25, 0.75] به خوبی بررسی کنید رسم فواصل روی کاغذ کمک می‌کند.

تمرین شما این است که آن را با استفاده از NumPy سریع‌تر کنید و از حلقه‌های صریح اجتناب کنید

اگر می‌توانید، قابلیت را به عنوان یک کلاس به نام DiscreteRV پیاده‌سازی کنید، جایی که

  • داده‌های یک نمونه از کلاس، بردار احتمالات q است

  • کلاس یک متد draw() دارد که یک نمونه را مطابق با الگوریتم توصیف شده در بالا برمی‌گرداند

اگر می‌توانید، متد را طوری بنویسید که draw(k) k نمونه از q برگرداند.

Exercise 10.3

بحث قبلی ما در مورد تابع توزیع تجمعی تجربی را به خاطر بیاورید.

وظیفه شما این است که

  1. متد __call__ را با استفاده از NumPy کارآمدتر کنید.

  2. یک متد اضافه کنید که ECDF را روی \([a, b]\) رسم می‌کند، جایی که \(a\) و \(b\) پارامترهای متد هستند.

Exercise 10.4

به یاد بیاورید که broadcasting در Numpy می‌تواند به ما کمک کند عملیات عنصر به عنصر را روی آرایه‌ها با تعداد متفاوتی از ابعاد بدون استفاده از حلقه‌های for انجام دهیم.

در این تمرین، سعی کنید از حلقه‌های for برای تکرار نتیجه عملیات broadcasting زیر استفاده کنید.

قسمت 1: سعی کنید این مثال ساده را با استفاده از حلقه‌های for تکرار کنید و نتایج خود را با عملیات broadcasting زیر مقایسه کنید.

rng = np.random.default_rng(123)
x = rng.standard_normal((4, 4))
y = rng.standard_normal(4)
A = x / y

در اینجا خروجی آورده شده است

print(A)

Hide code cell output

[[-0.64562663  0.55727833 -4.13068155  0.57428108]
 [ 0.60065994 -0.87444021  2.04128976  1.60450491]
 [-0.20665053  0.488491   -0.31163862 -4.51767283]
 [ 0.77815951  1.01684966 -3.20810109  0.40359261]]

قسمت 2: به سمت تکرار نتیجه عملیات broadcasting زیر حرکت کنید. در عین حال، سرعت broadcasting و حلقه for که پیاده‌سازی می‌کنید را مقایسه کنید.

برای این قسمت از تمرین می‌توانید از توابع tic/toc از کتابخانه quantecon برای زمان‌سنجی اجرا استفاده کنید.

بیایید مطمئن شویم که این کتابخانه نصب شده است.

!pip install quantecon

Hide code cell output

Requirement already satisfied: quantecon in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (0.11.4)
Requirement already satisfied: numba>=0.49.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (0.65.1)
Requirement already satisfied: numpy>=1.17.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (2.4.6)
Requirement already satisfied: requests in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (2.34.2)
Requirement already satisfied: scipy>=1.5.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (1.18.0)
Requirement already satisfied: sympy in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from quantecon) (1.14.0)
Requirement already satisfied: llvmlite<0.48,>=0.47.0dev0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from numba>=0.49.0->quantecon) (0.47.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (3.4.7)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (3.18)
Requirement already satisfied: urllib3<3,>=1.26 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (2.7.0)
Requirement already satisfied: certifi>=2023.5.7 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->quantecon) (2026.6.17)
Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from sympy->quantecon) (1.3.0)

اکنون می‌توانیم بسته quantecon را import کنیم.

rng = np.random.default_rng(123)
x = rng.standard_normal((1000, 100, 100))
y = rng.standard_normal(100)

with qe.Timer("Broadcasting operation"):
    B = x / y
Broadcasting operation: 0.0115 seconds elapsed

در اینجا خروجی آورده شده است

print(B)

Hide code cell output

[[[ 1.11459186e+00  6.49554639e-01 -1.39222984e+00 ...  1.29919811e+00
    4.05186703e+00 -7.40287138e+01]
  [ 5.81192956e-01 -2.92842384e+00  3.21336857e-01 ...  1.20991613e+00
    2.27427587e+00  1.44385102e+02]
  [ 1.05593413e+00  1.42938670e+00  4.45508796e-01 ...  4.01152744e-01
    2.84732007e+00  8.92867644e+01]
  ...
  [-2.74886903e-02  1.26977472e+00  3.14108995e-01 ...  1.11710121e-01
    1.42995646e+00 -3.11820183e+01]
  [ 2.37232237e-02 -1.07344880e+00 -6.84853323e-02 ...  1.34568899e-01
   -1.03734701e-01 -1.06565258e+02]
  [-1.74039841e+00 -3.01228229e+00 -9.49433502e-01 ...  1.44494147e-01
   -7.23108571e+00 -2.90325461e+01]]

 [[ 5.31486218e-01  2.93920784e+00 -2.44783577e-01 ... -6.32917660e-01
   -2.52543447e+00 -5.31568329e+01]
  [-4.73496558e-01  1.49038936e-01  1.68780916e+00 ...  2.05750194e-01
   -1.40524927e+00 -6.14593964e-01]
  [-2.74888585e+00 -2.33570490e+00  3.41515715e-01 ...  1.24702883e-01
   -1.68468042e+00  8.33959001e+01]
  ...
  [-8.36506904e-01 -1.86665236e+00  6.00226688e-01 ...  8.54789843e-01
   -1.28668710e+00 -8.83692748e+01]
  [-6.70119507e-01  8.95293052e-02 -5.62763392e-01 ...  1.06747869e+00
    8.32890981e-01  1.32305974e+02]
  [-2.35822588e-01  1.71686185e+00 -6.38798692e-01 ...  7.17992258e-01
    5.11995938e+00  4.32277717e+01]]

 [[-8.17691283e-01 -9.69805518e-01  1.20574471e-02 ... -2.29327510e-01
   -6.87440789e-01 -1.08708489e+02]
  [ 1.32946051e-01  2.53886133e+00  1.00358890e+00 ...  1.52451468e+00
   -7.96146833e+00  4.73065584e+01]
  [ 1.39118740e+00  2.42744267e+00  8.99415032e-01 ... -1.98940955e+00
   -4.09483421e+00  1.58863413e+02]
  ...
  [-2.00083480e+00 -2.39858291e+00 -1.63204799e+00 ...  1.03229680e+00
   -2.92527147e+00  4.97550232e+01]
  [ 2.13075183e-01 -6.94472260e-01 -1.27241435e+00 ... -4.72444580e-02
   -1.36097040e+00 -2.69662919e+01]
  [-6.53406813e-01  1.10261376e+00 -8.36617414e-02 ...  1.43523014e+00
   -2.71989098e+00  4.76036526e+01]]

 ...

 [[ 2.10702762e-01 -2.73232476e+00 -9.47422331e-01 ...  5.53820263e-01
    3.67876353e+00  1.50165202e+00]
  [ 2.01068194e+00  1.23328379e+00  5.01861693e-01 ... -6.20423733e-01
    1.82152397e+00  3.91329194e+00]
  [ 7.84176143e-01 -2.10192441e-02 -7.69297715e-01 ... -1.13855768e-03
    2.80468628e+00 -5.61973382e+01]
  ...
  [-1.24033449e+00  5.90826010e-01 -1.58604105e+00 ... -1.06447092e+00
    4.35766108e+00 -6.65157650e+01]
  [-1.37676146e+00 -2.44562663e+00  3.03635548e-01 ...  1.09674555e+00
    8.51724363e-01  7.62734749e+01]
  [ 2.65023743e-01 -1.60418989e+00  8.92588170e-01 ... -1.52970264e+00
    3.35794715e+00  7.66550051e+01]]

 [[-9.76665830e-01 -2.52931081e-01  9.34827331e-01 ...  1.34729867e+00
   -9.49752368e+00 -5.40493267e+01]
  [-6.99969930e-01  7.16309528e-01 -4.87147176e-01 ...  3.84737379e-01
   -4.62349866e+00  6.57667401e+01]
  [-5.35240129e-01  8.29149751e-01 -1.38663673e-01 ... -5.26523746e-01
   -2.78363901e+00  3.97278864e+01]
  ...
  [ 5.90892302e-01  1.10587017e+00 -1.35634710e+00 ... -1.10155962e+00
    4.79718847e+00  1.38630977e+01]
  [-6.30986628e-01 -3.12261837e-02  1.84942661e-01 ...  2.85786218e+00
   -9.26774215e-01  7.36155149e+01]
  [ 2.35257249e+00 -3.14639965e-01  6.29523883e-01 ... -1.14837361e+00
   -1.06301767e+00  1.30911044e+01]]

 [[-4.05344988e-01 -2.22802559e+00  1.01293008e+00 ... -2.84828321e-01
    4.76669493e+00 -5.22389356e+01]
  [-3.01680393e-01 -3.08400312e-01 -1.13845805e+00 ... -3.54402179e-01
    2.69111708e+00 -9.15315118e+01]
  [ 4.75838349e-01 -3.26362244e+00 -4.72222604e-01 ...  2.99854221e-01
    2.37944637e+00 -1.17700185e+01]
  ...
  [ 1.03535657e+00 -5.74611584e-02  1.97073682e-01 ... -1.09443930e+00
    4.66582536e+00 -2.25831644e+02]
  [-6.71551286e-01  6.27890445e-01 -2.96405648e-01 ...  1.32173888e+00
    3.90295626e+00 -7.43606070e+00]
  [ 1.83056628e+00  4.61028305e-01  9.21541992e-01 ...  6.08721297e-01
    4.43683093e+00 -1.49100072e+02]]]