Кольца Ньютона
В этом задании Вам предстоит смоделировать классический эксперимент по интерференции света вокруг точки касания линзы и пластины. Схема эксперимента показана на рисунке:

Сверху по нормали падает монохроматическая плоская электромагнитная волна. Пройдя через линзу, среду и пластину, волна попадает на экран внизу. Из-за интерференции в оптической системе, на экране наблюдается картина из максимумов и минимумов интенсивности. Интерференция происходит между волной, прошедшей без отражений, и дважды отразившейся в малом промежутке d волной. Мы пренебрегаем изменением направления и модуля амплитуды второй волны. Выражения для разности фаз интерферирующих волн и интенсивности изображения приведены на рисунке.
Базовая версия
Решите эту задачу рамках объектно-ориенториванного подхода. Базовая версия решения требует реализации классов NewtonRings и NRPlotter. Первый отвечает за вычисление распределения интенсивности на экране, а второй - за визуализацию. Минимальная сигнатура класса NewtonRings:
class NewtonRings:
def __init__(self,
r:float, # радиус кривизны (м)
lam:float, # длина волны (нм)
n_lens:float, # показатель преломления линзы
n_plate:float, # показатель преломления пластины
n_medium:float) # показатель преломления среды
pass
def __call__(self,
x:np.ndarray, # x-координаты точек на экране (м)
y.np.ndarray # y-координаты точек на экране (м)
) -> np.ndarray:
""" Вычисляет интенсивность изображения на экране """
pass
Интенсивность изображения в максимуме должна быть равна 4.
Класс NRPlotter в базовой версии довольно простой:
class NRPlotter:
def __init__(self, nr:NewtonRings):
pass
def plot(self):
pass
Конструктор принимает объект класса NewtonRings, а метод NRPlotter.plot выполняет отрисовку изображения. Работа с этими классами должна происходить следующим образом:
import matplotlib.pyplot as plt
nr = NewtonRings(r=1000., lam=400., n_lens=1.25,
n_plate=1.3, n_medium=1.2)
nrp = NRPlotter(nr)
nrp.plot()
plt.show()
Подберите разумные значения для диапазона значений параметров x и y, предполагая, что длина падающей волны находится внутри видимого спектра, а радиус кривизны линзы очень большой, порядка километра. Интерференционная картина должна выглядить примерно так:

Указания 1
- Выражение для амплитуды волны можно преобразовать таким образом, что оно не будет содержать комплексных величин.
- При отражении от оптически более плотной среды фаза волны меняется на pi. Этот эффект необходимо учесть.
- Рассмотрите использование функции
matplotlib.pyplot.contourfдля визуализации интерференционной картины - Регулярную двумерную сетку можно создать с помощью функции
numpy.meshgrid. Она возвращает массивы, которые можно подавать на вход фукнцииmatplotlib.pyplot.contourf - Необходимо подобрать оптимальный шаг сетки так, чтобы контуры верно отображались и отрисовка не занимала слишком много времени (и памяти). Если сетка будет иметь слишком большой шаг, изображение будет содержать артефакты
- Рассмотрите использование функций
pyplot.axes('equal')иpyplot.axes('off')чтобы сделать масштабы по горизонтали и вертикали одинаковыми и убрать отрисовку элементов осей
Как проверять решение локально
Код решения и полученные изображения будут проверяться вручную. Работу класса NewtonRings можно проверить локально с помощью обычной процедуры: устанавливаем необходимые пакеты (один раз):
pip install -r requirements.txt
Запускаем тесты:
pytest -vs
Продвинутая версия
В продвинутой версии мы реализуем графический интерфейс пользователя (GUI), с помощью которого можно будет изменять параметры системы (длину волны, показатели преломления и др.).
Реализовать GUI можно различными средствами. Например:
- Виджеты matplotlib. Этот вариант наиболее простой в реализации, но даже небольшое количество виджетов приводит к сильному замедлению работы программы
- Библиотека tkinter. Для этого варианта объект
matplotlib.Figureнеобходимо будет встроить в окно Tk - Библиотека PyQt5. Эту библиотеку необходимо установить отдельно. В этом варианте также необходимо будет встроить объект
matplotlib.Figureв окно Qt5. Больше примеров можно найти здесь.
Покажем несколько примером GUI. Виджеты matplotlib:

Библиотека tkinter:

Библиотека PyQy5:

Нет необходимости копировать эти примеры. Вы можете самостоятельно разработать интерфейс и его функциональность.
Указания 2
- Многократный вызов функции
pyplot.contourfприведет к замедлению работы программы. Чтобы этого избежать необходимо перед вызовом функции удалять предыдущие объекты:
for coll in self.cnt.collections:
if coll in self.ax.collections:
self.ax.collections.remove(coll)
cnt = ax.contourf()
Указания для работы с виджетами matplotlib
В этой задаче могут оказаться полезными виджеты TextBox, Button, Slider, RadioButtons. Конструктор любого виджета matplotlib первым параметром принимает объект Axes, который определяет размер и положение виджета.
Чтобы связать виджет с логикой программы необходимо связать с ним функции, которые должны вызываться при определенных событиях. Например:
- Методы
on_text_changeиon_submitвиджетаTextBoxпринимают callable объект для вызова при изменеии текста и при нажатии клавишиEnter, соответственно - Метод
on_clickedвиджетовButtonиRadioButtons - Метод
on_changedвиджетаSlider
Ссылка на объект виджета должна быть доступна в течение времени исполнения программы, чтобы виджет был активен.
Подробное описание и примеры смотрите в документации.
Указания для работы с tkinter
Минимальный пример встраивания графика matplotlib в программу с tkinter находится в файле tkinter_example.py. Рассмотрите использование виджетов tkinter.Label, tkinter.Entry, tkinter.Button, tkinter.Scale и tkinter.ttk.Combobox. Виджеты tkinter.Scale и tkinter.Button можно связать с callback-функциями через параметр конструктора command:
s = tkinter.Scale(master=master, command=callable)
b = tkinter.Button(master=master, text=text, command=callable)
Задать и прочитать содержимое виджета tkinter.Entry можно с помощью методов insert и get, соответственно. Виджет tkinter.ttk.Combobox имеет методы set и get.
Метод bind позволяет связать виджет, событие и действие (callback-функцию). Так, например, можно связать главное окно программы, некоторое действие и нажатие клавиши Enter:
class Gui(tkinter.Tk):
def __init__(self):
super().__init__()
self.bind('<Return>', callable)
Указания для работы с PyQt5
Минимальный пример встраивания графика matplotlib в программу с PyQt5 находится в файле pyqt_example.py. Рассмотрите использование виджетов QPushButton, QSlider, QLabel, QLineEdit, QComboBox из модуля PyQt5.QtWidgets.
Связать эти виджеты с callback-функциями можно следующим образом:
button = QPushButton(label, parent)
button.clicked.connect(callable)
qle = QLineEdit(init, parent)
qle.textChanged.connect(callable)
qle.returnPressed.connect(callable)
sld = QSlider(Qt.Vertical, parent)
sld.valueChanged.connect(callable)
combo = QComboBox(self)
combo.addItems(items)
combo.activated.connect(fcn)
Класс FFPlotter удобно сделать наследником класса FigureCanvasQTAgg из модуля matplotlib.backends.backend_qt5agg, как показано в примере. Класс FFGui удобно сделать наследником класса QMainWindow из модуля PyQt5.QtWidgets.
В примере GUI выше реализовано сохранения окна в графический файл. С библиотекой PyQt5 снимок окна программы можно выполнить с помощью объекта QPixmap из модуля PyQt5.QtGui. Вот так может выглядеть метод класса FFGui для сохранения снимка окна:
class FFGui(QMainWindow):
# ...
def save(self):
pix = QPixmap(self.size())
self.render(pix)
pix.save('ffilter.png', 'png')