Глава 4. Автоматизация спектроскопического эксперимента
В случае достаточно интенсивно используемого экспериментального оборудования для параллельной работы над различными научными задачами всегда возникает проблема в виде огромных плохо структурированных каталогов, каждый из которых содержит несколько сотен файлов экспериментальных данных со сходными именами. Поиск результатов измеренных, например, несколько лет назад, обычно превращается в длительный квест. Решение данной проблемы очевидно: программа должна иметь возможность работать с базой данных, т.е. напрямую записывать, а также проводить поиск и загружать из базы данных необходимые результаты. Поскольку реализация этой задачи в целом достаточно трудоёмка, мы рассмотрим базовые моменты взаимодействия сетевой реляционной СУБД PostgeSQL с программой на Python.
Инсталлятор СУБД PostgreSQL и подробная инструкция по установке находятся в свободном доступе на сайте разработчиков.
Для взаимодействия с СУБД PostgreSQL будем использовать возможности консоли iPython, поскольку создавать структуру базы нам придётся с использованием команд языка SQL. Для этого дополнительно потребуется установить, как минимум, три пакета:
pip install ipython-sql
pip install psycopg2
pip install pgcli
Если по каким-либо причинам у вас не установился пакет pgcli, вы, безусловно, можете использовать любую из программ-оболочек для PostgreSQL, в том числе устанавливаемую вместе с этой СУБД клиентскую программу psql. Если же всё установилось, запустите iPython и введите команду:
In[1]: %load_ext pgcli.magic
Попробуем соединиться с СУБД, которая у нас находилась по локальному IP-адресу под аккаунтом администратора (имя postgres, база данных postgres):
Если ваша СУБД работает на том же компьютере, что и Python, вместо конкретного адреса введите localhost:5432
In[2]: pgcli postgres://postgres:MyPassword@172.20.180.147:5432/postgres
Если соединение было успешным, вы попадаете в консоль управления СУБД:
postgres@172:postgres>
Создадим базу данных для хранения наших экспериментальных результатов и определим пользователя, от имени которого мы будем работать с данными. Этому пользователю необходимо разрешить чтение, добавление, изменение и удаление данных для всех таблиц из базы данных. Для этого требуется выполнить несколько стандартных SQL-инструкций администрирования. В настоящем пособии основы языка SQL не рассматриваются, поэтому мы можем лишь порекомендовать читателям обратиться к соответствующим учебным ресурсам по данному вопросу и, прежде всего, к документации СУБД PostgreSQL.
В первую очередь следует соединиться с базой данных postgres с аккаунтом администратора и выполнить следующие команды:
…> create database experiments; …> create user labo with encrypted password 'ВашПароль'; …> grant connect on database experiments to labo;
Теперь выйдем из базы postgres…
…> quit;
… и соединимся с созданной базой данных experiments всё c тем же аккаунтом администратора. Далее выполним команды:
…> create schema exp; …> grant usage on schema exp to labo; …> grant all privileges on all sequences in schema exp to labo; …> grant select, insert, update, delete on all tables in schema exp to labo; …> alter user labo set search_path to exp, public;
Далее создадим связанные внешними ключами реляционные таблицы, в которые будем записывать информацию о проведённых экспериментах и сохранять сами экспериментальные данные.
Мы реализовали простейшую структуру базы данных, состоящую из трёх таблиц, имеющих ключевые поля id в виде счётчиков, значение которых автоматически увеличивается с каждой новой записью. Таблицы sets и meastypes являются дополнительными и служат для уточнения вида хранимых данных. Эти таблицы связаны с таблицей results с помощью внешних ключей (поля setid и typeid). В таблицу results будут заноситься результаты экспериментов с помощью программы, которую напишем на Python.
Выйдем теперь из режима суперпользователя и попробуем соединиться с базой данных с помощью логина и пароля для пользователя labo.
…> quit; In[3]: pgcli postgres://labo:МойПароль@172.20.180.147:5432/еxperiments labo@172:experiments>
Теперь мы работаем от имени пользователя, которому разрешено читать и записывать в созданные таблицы. Именно этот аккаунт будет использовать программа на Python. Наполним таблицы Sets и MeasTypes некоторым содержанием:
…> insert into sets(name, description) values ('MDR-2019','Спектральный комплекс на базе монохроматора МДР-23'), ('VA-vacuum','Измерение вольт-амперных характеристик на базе Keithley 6485'); …> insert into meastypes(description) values ('Измерение спектров люминесценции'), ('Измерение кинетик люминесценции'), ('Измерение вольт-амперных характеристик');
Посмотрим содержимое таблиц Sets и MeasTypes:
Далее напишем функцию на языке Python, которая записывает информацию в базу данных. Предположим, у нас есть файл со спектроскопическими данными, который мы получили в предыдущей задаче, и созданные нами функции можно будет включить в общую программу управления спектральной установкой. В представленной ниже функции параметр d_data – это словарь, который включает в себя и параметры измерения, и обозначение осей координат, и набор результатов измерений. Для того, чтобы корректно записать и, самое главное, воспроизвести этот набор данных, мы сохраняем их в поле data таблицы results в формате JSON.
Формат JSON (JavaScript Object Notation), хоть и имеет отношение к языку JavaScript, но в настоящее время обрёл популярность как универсальный текстовый формат обмена данными. Этот формат достаточно компактно описывает структуру данных, при этом сама информация остаётся человекочитаемой, а значит, при необходимости можно вручную вносить правку в JSON-строку. В нашем случае информация, которая будет заноситься в поле data таблицы results, будет иметь вид, подобный этому:
{"parameters": {"l_start": 400.0, "l_stop": 500.0, "l_step": 1.0, "t_meas": 1.0}, "coordinates": ["Wavelength, nm ", "Intensity, imp."], "data": [[350.0, 16012.0], [351.0, 15365.0], [352.0, 15685.0], [353.0, 15432.0], [354.0, 15271.0], [355.0, 15652.0], [356.0, 15232.0], [357.0, 18715.0], [358.0, 15262.0], [359.0, 14974.0], [360.0, 15159.0], [361.0, 15149.0], [362.0, 15252.0], [363.0, 15075.0], [364.0, 15342.0], [365.0, 15260.0], [366.0, 15097.0], [367.0, 15016.0], [368.0, 15228.0], [369.0, 15135.0], [370.0, 15479.0]]}
Из представленного JSON-кода довольно легко выделить структуру данных в стиле словарей и списков Python. Очевидно, что для другой экспериментальной методики структура данных наверняка будет какой-либо иной. Важно, что в любом случае достаточно одного метода json.dumps(), чтобы реализовать предложенную схему хранения (строка 10). Не забудьте добавить import json в начало программы.
Как же реализована запись в базу данных? Как обычно, Python предлагает весьма лаконичный код для реализации такого действия, поскольку основной функционал реализован в модуле psycopg2. Метод psycopg2.connect() (строки 7-10) создаёт соединение с базой данных, которое по окончании обмена данными должно быть закрыто с помощью метода close(). Все операции по взаимодействию с базой данных мы заключили в конструкцию try … except … finally для корректной обработки возможных ошибок без прерывания работы программы. Для выполнения запроса нам потребуется метод cursor объекта conn (строка 7). Курсор для баз данных представляет собой широко используемую идеологию последовательного доступа к данным, при этом сам набор данных, который мы можем сканировать с помощью курсора, формируется с помощью SQL-запроса. Однако в данном случае мы не читаем, а пишем в базу данных, поэтому ограничимся только выполнением одной SQL-инструкции. Строки 8-10 содержат параметризованный запрос INSERT INTO на добавление одной строки данных. Сам SQL-запрос оформлен в тройные кавычки для того, чтобы его можно было разместить на нескольких строках. На самом деле язык SQL строконезависимый, и это сделано только для удобства восприятия кода. Операторы форматирования %s внутри строки представляют типичную для Python реализацию форматирования текстовых строк с возможностью введения нескольких параметров. Они определяют место в строке SQL-инструкции, куда будут введены значения переменных d_name, d_comment и d_data, которые необходимо занести в базу данных. Важно, что набор переменных должен быть оформлен в виде кортежа – это второй аргумент метода execute. Даже если переменная одна – она должна быть оформлена как элемент кортежа.
Поскольку мы не ждём возврата результата от базы данных, необходимо завершить транзакцию (тем самым запустив выполнение запроса) с помощью conn.commit() и закрыть соединение с базой данных. Используя командную строку iPython + pgcli, можно посмотреть, изменилось ли содержимое таблицы results (SELECT * FROM results). Если ваша запись содержит русские символы, возможны проблемы с их отображением в терминале iPython + pgcli, однако при чтении соответствующей информации с помощью Python-программы проблем не должно быть.
Рассмотрим следующую тестовую функцию, которая читает информацию из базы данных:
Функция выводит только одну запись из всех связанных таблиц, значение поля id которой в таблице results соответствует параметру функции id. Поэтому курсор принимает значение SELECT-запроса с условием WHERE, который выдаст шесть «столбцов» данных выбранной нами записи. Собственно, экспериментальные результаты находятся в JSON-массиве в последнем столбце, который мы конвертируем обратно в словарь Python с помощью метода json.loads (строка 14).
Конечно, для этой функции необходимо указать параметр id, который как-то надо узнать или получить из таблицы results, например, в результате поиска записи по заданным условиям. Но, поскольку мы тестируем работу с базой данных, вызов функции dbLoadData может быть оформлен так:
ЗАДАНИЕ:
Реализуйте функцию showData, которая изображает график спектра (строка 7).
Если вы заметили неточность, ошибку или хотите поделиться своими мыслями по поводу статьи - мы рады обратной связи. Давайте вместе сделаем ресурс лучше!