Skip to main content
LibreTexts - Ukrayinska

1.3: Модуляризація Кодексу

  • Page ID
    79647
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)

    1.3.1: Проектування потенційної функції

    Ми могли б продовжувати змінювати вищевказаний код простим способом. Наприклад, ми могли б додати більше частинок, додаючи змінні x1, x2, q1, q2, і так далі, і змінюючи нашу формулу для обчислення phi. Однак це не дуже задовільно: кожен раз, коли ми хочемо розглянути нову колекцію позицій частинок або зарядів, або змінити кількість частинок, нам доведеться переписати внутрішню «логіку» програми - тобто частину, яка обчислює потенціали. У термінології програмування наша програма недостатньо «модульна». В ідеалі ми хочемо виділити частину програми, яка обчислює потенціал від частини, яка визначає числові входи для розрахунку, як позиції та заряди.

    Для модуляризації коду визначимо функцію, яка обчислює потенціал довільної множини заряджених частинок, вибіркових при довільному наборі позицій. Для такої функції знадобиться три набори входів:

    • Масив позицій частинок\(\vec{x}\equiv [x_{0}, \cdots, x_{N-1}]\). (До речі, не плутайте: ми використовуємо ці\(N\) числа для позначення позицій\(N\) частинок у одномерному просторі, а не положення однієї частинки в\(N\) -вимірному просторі.)
    • Масив зарядів частинок\(\vec{q}\equiv [q_{0}, \cdots, q_{N-1}]\).
    • Масив точок вибірки\(\vec{X}\equiv [X_{0}, \cdots, X_{M-1}]\), які є точками, де ми хочемо знати\(\phi(X)\).

    Кількість частинок і кількість точок вибірки повинні бути довільними натуральними числами.\(N\)\(M\) Крім того,\(N\) і рівних не\(M\) потрібно.

    Функція, яку ми маємо намір записати, повинна обчислити масив.

    \[\begin{bmatrix}\phi(X_{0})\\ \phi(X_{1}) \\ \vdots \\ \phi(X_{M-1})\end{bmatrix}\]

    який містить значення сумарного електричного потенціалу в кожній з точок відбору проб. Загальний потенціал можна записати як суму внесків від усіх частинок. Визначимо\(\phi_{j}(x)\) як потенціал, вироблений частинкою\(j\):

    \[\phi_{j(x)}\equiv\frac{q_{j}}{|x-x_{j}|}\]

    Тоді загальний потенціал

    \[\begin{bmatrix}\phi(X_0)\\ \phi(X_1) \\ \vdots \\ \phi(X_{M-1})\end{bmatrix} = \begin{bmatrix}\phi_0(X_0)\\ \phi_0(X_1) \\ \vdots \\ \phi_0(X_{M-1})\end{bmatrix} + \begin{bmatrix}\phi_1(X_0)\\ \phi_1(X_1) \\ \vdots \\ \phi_1(X_{M-1})\end{bmatrix} + \cdots + \begin{bmatrix}\phi_{N-1}(X_0)\\ \phi_{N-1}(X_1) \\ \vdots \\ \phi_{N-1}(X_{M-1})\end{bmatrix}.\]

    1.3.2 Написання програми

    Давайте код це вгору. Поверніться до файлу potentials.py, і видаліть весь вміст файлу. Потім замініть його наступним:

    from scipy import *
    import matplotlib.pyplot as plt
    
    ## Return the potential at measurement points X, due to particles
    ## at positions xc and charges qc.  xc, qc, and X must be 1D arrays,
    ## with xc and qc of equal length.  The return value is an array
    ## of the same length as X, containing the potentials at each X point.
    def potential(xc, qc, X):
        M = len(X)
        N = len(xc)
        phi = zeros(M)
        for j in range(N):
            phi += qc[j] / abs(X - xc[j])
        return phi
    
    charges_x  = array([0.2, -0.2])
    charges_q  = array([1.5, -0.1])
    xplot = linspace(-3, 3, 500)
    
    phi = potential(charges_x, charges_q, xplot)
    
    plt.plot(xplot, phi)
    pmin, pmax = -50, 50
    plt.ylim(pmin, pmax)
    plt.show()
    
    clipboard_e4adc3c5073e950f6c32016d35b4b68ef.png
    Малюнок\(\PageIndex{1}\)

    При введенні або вставці вищезазначеного в файл обов'язково збережіть відступи (тобто кількість пробілів на початку кожного рядка). Відступ важливий у Python; як ми побачимо, він використовується для визначення структури програми. Тепер збережіть і запустіть програму знову:

    • У графічному інтерфейсі Windows введіть F5 у вікні редагування, що показує potentials.py.
    • У GNU/Linux введіть python -i potentials.py з командного рядка.
    • Крім того, з командного рядка Python введіть потенціали імпорту, які завантажать та запускатимуть ваш файл potentials.py.

    Тепер ви повинні побачити фігуру, подібну до тієї, що знаходиться праворуч, показуючи електричний потенціал, вироблений двома частинками, одна в положенні\(x_{0}=0.2\) із зарядом,\(q_{0}=1.5\) а інша - у положенні\(x_{1}=-0.2\) із зарядом\(q_{1}=-0.1\).

    У вищезгаданій програмі менше\(20\) рядків фактичного коду, але вони роблять досить багато речей. Пройдемося по ним по черзі:

    Модуль Імпорт

    Перші два рядки імпортують модулі Scipy і Matplotlib для використання в нашій програмі. Ми ще не пояснили, як працює імпорт, тому давайте зробимо це зараз.

    from scipy import *
    import matplotlib.pyplot as plt
    

    Кожен модуль Python, включаючи Scipy і Matplotlib, визначає різноманітні функції та змінні. Якщо ви використовуєте декілька модулів, у вас може виникнути ситуація, коли, скажімо, два різних модулі визначають функцію з однаковою назвою, але роблять абсолютно різні речі. Це було б дуже погано. Щоб уникнути цього, Python реалізує концепцію, яка називається простором імен. Припустимо, ви імпортуєте модуль (скажімо Scipy) так:

    import scipy
    

    Однією з функцій, визначених Scipy, є linspace, яку ми вже бачили. Ця функція була визначена модулем scipy і лежить всередині простору імен scipy. Як результат, під час імпортування модуля Scipy за допомогою рядка import scipy, вам потрібно викликати функцію linspace наступним чином:

    x = scipy.linspace(-3, 3, 500)
    

    Сценарій. спереду говорить, що ви маєте на увазі функцію linspace, яка була визначена в просторі імен scipy. (Примітка: онлайн-документація для linspace посилається на неї як numpy.linspace, але точно така ж функція також присутня у просторі імен scipy. Насправді все нумпі. * функції реплікуються в просторі імен scipy. Отже, якщо не вказано інше, нам потрібно лише імпортувати scipy.)

    Ми будемо використовувати багато функцій, які визначені в просторі імен scipy. Оскільки це було б дратує, щоб продовжувати друкувати scipy. Повсюду, ми вирішили використовувати дещо інший оператор імпорту:

    from scipy import *
    

    Це імпортує всі функції та змінні у просторі імен scipy безпосередньо до простору імен вашої програми. Тому ви можете просто викликати linspace, без scipy. префікс. Очевидно, що ви не хочете робити це для кожного модуля, який ви використовуєте, інакше ви потрапите з проблемою зіткнення імен, про яку ми згадували раніше! Єдиний модуль, з яким ми будемо використовувати цей ярлик, - scipy.

    Інший спосіб уникнути необхідності вводити довгі префікси показує цей рядок:

    import matplotlib.pyplot as plt
    

    Це імпортує модуль matplotlib.pyplot (тобто модуль pyplot, який вкладено в модуль matplotlib). Ось де визначаються сюжет, шоу та інші функції побудови графіків. Як plt у наведеному вище рядку говорить про те, що ми будемо посилатися на простір імен matplotlib.pyplot як коротку форму plt замість цього. Отже, замість виклику функції plot наступним чином:

    matplotlib.pyplot.plot(x, y)
    

    ми будемо називати його так:

    plt.plot(x, y)

    Коментарі

    Повернемося до програми, яку ми розглядали раніше. Наступні кілька рядків, що починаються з #, - це «коментарі». Python ігнорує символ # і все, що слід за ним, аж до кінця рядка. Коментарі дуже важливі, навіть в таких простих програмах.

    Коли ви пишете власні програми, будь ласка, не забудьте включити коментарі. Вам не потрібен коментар для кожного рядка коду - це було б надмірно, але, як мінімум, кожна функція повинна мати коментар, що пояснює, що вона робить, і які вхідні та повертаються значення.

    Визначення функції

    Тепер ми перейдемо до визначення функції з назвою potential, яка є функцією, яка обчислює потенціал:

    def potential(xc, qc, X):
        M = len(X)
        N = len(xc)
        phi = zeros(M)
        for j in range(N):
            phi += qc[j] / abs(X - xc[j])
        return phi
    

    Перший рядок, що починається з def, є заголовком функції. Цей заголовок функції стверджує, що функція називається potential, і що вона має три входи. У обчислювальній термінології входи, які приймає функція, називаються параметрами. Тут параметри мають назву xc, qc і X. Як пояснюється коментарями, ми маємо намір використовувати їх для позицій частинок, зарядів частинок та позицій, в яких вимірювати потенціал відповідно.

    Визначення функції складається з заголовка функції, разом решта рядків з відступом під ним. Визначення функції завершується, як тільки ми потрапляємо до рядка, який знаходиться на тому ж рівні відступу, що і заголовок функції. (Цей кінцевий рядок вважається окремим рядком коду, який не є частиною визначення функції).

    За умовністю слід використовувати 4 пробіли на рівні відступу.

    Рядки з відступом під заголовком функції називаються тілом функції. Це код, який запускається при кожному виклику функції. При цьому тіло функції складається з шести рядків коду, які призначені для обчислення загального електричного потенціалу, згідно з процедурою, яку ми виклали в попередньому розділі:

    • Перші два рядки визначають дві корисні змінні, M і N. Їх значення встановлюються на довжини масивів X і xc відповідно.
    • Наступний рядок викликає функцію нулів. Вхідні дані для нулів - M, довжина масиву X (тобто третій параметр нашої функції). Таким чином, нулі повертають масив тієї ж довжини, що і X, при цьому кожен елемент має значення 0.0. Наразі це являє собою електричний потенціал за відсутності будь-яких зарядів. Надаємо цьому масиву ім'я phi.
    • Потім функція перебирає кожну з частинок і додає свій внесок у потенціал, використовуючи конструкцію, відому як цикл for. Код для j в діапазоні (N): є «рядком заголовка» циклу, а наступний рядок з відступом на 4 пробіли глибше, ніж рядок заголовка, - «тіло» циклу.

      У рядку заголовка зазначено, що ми повинні запускати тіло циклу кілька разів, при цьому змінна j встановлена на різні значення під час кожного запуску. Значення j для циклу задаються діапазоном (N). Це виклик функції функції діапазону, на вході якої N (кількість електричних зарядів). Виклик функції range (N) повертає послідовність, яка вказує N послідовних цілих чисел, від 0 до N-1 включно. (Зверніть увагу, що останнім значенням у послідовності є N-1, а не N. Тому що ми починаємо з 0, це означає, що є в цілому N цілих чисел в послідовності. Крім того, діапазон виклику (N) збігається з діапазоном виклику (0, N).

    • Для кожного j обчислюємо qc [j]/abs (X - xc [j]). Це масив, елементами якого є значення електричного потенціалу на безлічі позицій X, що виникають з окремої частинки\(j\). У математичному плані ми обчислюємо
      \[\phi_j(X) \equiv \frac{q_j}{|X - x_j|}\]

      використовуючи масив позицій X. Потім ми додаємо цей масив до phi. Як тільки це буде зроблено для всіх j, масив phi буде містити бажаний загальний потенціал,
      \[\phi(X) = \sum_{j=0}^{N-1}\phi_j(X).\]

    • Нарешті, ми викликаємо return, щоб вказати вихід функції, або повертає значення. Це масив phi.

    Код верхнього рівня: Числові константи

    Після визначення функції приходить код для використання функції:

    charges_x  = array([0.2, -0.2])
    charges_q  = array([1.5, -0.1])
    xplot = linspace(-3, 3, 500)
    

    Як і оператори імпорту на початку програми, ці рядки коду лежать на верхньому рівні, тобто вони не мають відступів. Заголовок функції, який визначає потенційну функцію, також знаходиться на верхньому рівні. Запуск програми Python складається з послідовного запуску її коду верхнього рівня.

    Наведені вище рядки визначають змінні для зберігання деяких числових констант. У перших двох рядках змінні charges_x і charges_q зберігають числові значення цікавлять нас позицій і зарядів. Вони ініціалізуються за допомогою функції масиву. Вам може бути цікаво, чому виклик функції масиву має квадратні дужки, вкладені в коми. Ми пояснимо пізніше, у частині 2 підручника.

    У третьому рядку виклик функції linspace повертає масив, вміст якого ініціалізується на 500 чисел від -3 до 3 (включно).

    Далі викликаємо потенційну функцію, передаючи charges_x, charges_q і xplot в якості входів:

    phi = potential(charges_x, charges_q, xplot)
    

    Ці входи надають значення параметрів визначення функції xc, qc та X відповідно. Повертається значення виклику функції - масив, що містить загальний потенціал, обчислюється на кожній із позицій, зазначених у xplot. Це повернене значення зберігається як змінна з ім'ям phi.

    побудова графіків

    Нарешті, створюємо сюжет:

    plt.plot(xplot, phi)
    pmin, pmax = -50, 50
    plt.ylim(pmin, pmax)
    plt.show()
    

    Ми вже бачили, як працюють функції сюжету і шоу. Тут, перш ніж викликати plt.show, ми додали дві додаткові лінії, щоб зробити потенційну криву більш розбірливою, відрегулювавши межі осі y графіка. Функція ylim приймає два параметри, нижню і верхню межі осі y. В цьому випадку встановлюємо межі -50 і 50 відповідно. Існує функція xlim, яка робить те ж саме для осі x.

    Зверніть увагу, що в рядку pmin, pmax = -50, 50, ми встановлюємо дві змінні (pmin і pmax) на одному рядку. Це трохи «синтаксичного цукру», щоб код трохи легше читався. Це еквівалентно наявності двох окремих рядків, як це:

    pmin = -50
    pmax = 50
    

    Ми пояснимо, як працює ця конструкція, у наступній частині підручника.