C++ и Python

Кольца Ньютона (5)

Кольца Ньютона

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

drawing

Сверху по нормали падает монохроматическая плоская электромагнитная волна. Пройдя через линзу, среду и пластину, волна попадает на экран внизу. Из-за интерференции в оптической системе, на экране наблюдается картина из максимумов и минимумов интенсивности. Интерференция происходит между волной, прошедшей без отражений, и дважды отразившейся в малом промежутке 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, предполагая, что длина падающей волны находится внутри видимого спектра, а радиус кривизны линзы очень большой, порядка километра. Интерференционная картина должна выглядить примерно так:

newton

Указания 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:

newton_widgets

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

newton_tkinter

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

newton_pyqt

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

Указания 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')