1.3: Модуляризація Кодексу
- Page ID
- 79647
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()
При введенні або вставці вищезазначеного в файл обов'язково збережіть відступи (тобто кількість пробілів на початку кожного рядка). Відступ важливий у 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
Ми пояснимо, як працює ця конструкція, у наступній частині підручника.