Химия в Python: Часть 2

—

от автора

Прошлая моя статья набрала хороший отклик.

И сегодня я решил написать продолжение той статьи.

Итак, к сожалению, я не смогу в этой статье показать все, что хотели комментаторы в прошлом туториале, но я постраюсь выложиться, согласно возрасту.

Хочу напомнить, что в этой статье я также уделил внимание коду.

Напоминаю, что весь исходный код — здесь, на моем GitHub’е.

Рефакторинг кода

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

Но я решил, что лучше будет использование готового csv-файла с информацией обо всех элементах.

А также, вместо прямого обращения, я создаю класс, который при инициализации принимает в себя список элементов и позволяет искать элемент по разным значениям.

Таблица с химическими элементами здесь.

Нам надо изменить файл с химическими элементами:

#!/usr/bin/python3 # -*- coding:utf-8 -*- """ Oxygen Library --------------------------------------------------------------------------------  Автор: Okulus Dev (aka DrArgentum)  Лицензия: GNU GPL v3 --------------------------------------------------------------------------------  Описание: файл с химическими элементами  Copyright (C) 2023  Okulus Dev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>. """ import csv from typing import Union   def round_to_nearest(num: int):     num = int(num + (0.5 if num > 0 else -0.5))     return num   class Element:     def __init__(self, atomic_number: int, name: str, symbol: str, atomic_mass: float,                 neutrons: int, protons: int, electrons: int, period: int, group: int,                 phase: str, radioctive: bool, natural: bool, metall: bool, nonmetall: bool,                 metalloid: bool, element_type: str):         self.atomic_number = int(atomic_number)         self.name = name         self.short_name = symbol         self.relative_atomic_mass = float(atomic_mass)         self.neutrons = int(neutrons)         self.protons = int(protons)         self.electrons = int(electrons)         self.period = int(period)         self.group = group         self.phase = phase         if natural == '':             self.natural = False         else:             self.natural = True         if radioctive == '':             self.radioctive = False         else:             self.radioctive = True         if metall == '':             self.metall = False         else:             self.metall = True         if nonmetall == '':             self.nonmetall = False         else:             self.nonmetall = True         if metalloid == '':             self.metalloid = False         else:             self.metalloid = True         if element_type == '':             self.element_type = 'Unknown'         else:             self.element_type = element_type   AVOGADRO_NUMBER = 6.02214076e23 ELEMENTS = []  # путь до csv файла with open('oxygen/chemistry/data/PeriodicTable.csv', newline='') as File:     reader = csv.reader(File)     c = 0     for row in reader:         if c == 0:             c += 1             continue         # принимаем элементы вплоть до типа элемента         # todo: добавить другие поля         ELEMENTS.append(Element(row[0], row[1], row[2], row[3], row[4], row[5],                                 row[6], row[7], row[8], row[9], row[10],                                 row[11], row[12], row[13], row[14], row[15]))   class MendeleevTable:     def __init__(self, elements: list) -> None:         self.elements = elements      def get_element_by_shortname(self, shortname: str) -> Union[Element, None]:         for element in self.elements:             if element.short_name == shortname:                 return element          return None      def get_element_by_name(self, name: str) -> Union[Element, None]:         for element in self.elements:             if element.name == name:                 return element          return None      def get_element_by_number(self, num: int) -> Union[Element, None]:         for element in self.elements:             if element.atomic_number == num:                 return element          return None   MendeleevTable = MendeleevTable(ELEMENTS)

Следующее, что нам понадобится — это класс химической формулы. Если пользователь ввел уже существующую химическую формулу, например сахарозу или воду, то мы выводим информацию об этом

""" Copyright (C) 2023  Okulus Dev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>. """ # нам нужен будет импорт калькулятора молекулярной массы, в репозитории здесь все есть # но т.к. это туториал, то я надеюсь, что вы сами импортируйте или вставите код # (дабы не запутать вас)   class ChemicalFormula:     """Класс химической формулы"""     def __init__(self, elements: dict, formula: str,                  name: str, molecular_mass: float=None):         self.elements = elements         self.formula = formula         if molecular_mass is not None:             self.molecular_mass = molecular_mass         else:             self.molucular_mass = calculate_relative_molecular_mass(formula, False)         self.name = name   # Химические формулы CHEMICAL_FORMULAS = {     'H2O': ChemicalFormula({('H', 2), ('O', 1)}, "H2O", 'Вода', None),     'C12H22O11': ChemicalFormula({('C', 12), ('H', 22), ('O', 11)},                                  "C12H22O11", 'Сахароза (сахар)', None) }   def read_formula(formula: str):     """Читаем формулу и выводим ее, если существует таковая"""     if formula in CHEMICAL_FORMULAS:         print(f'Формула {formula} это - {CHEMICAL_FORMULAS[formula].name}')         elements_in_formula = []          for el in CHEMICAL_FORMULAS[formula].elements:             elements_in_formula.append(f"{el[1]} {MendeleevTable.get_element_by_shortname(el[0]).name}")          elements_in_formula_str = ", ".join(elements_in_formula)         print(f'{CHEMICAL_FORMULAS[formula].name} состоит из {elements_in_formula_str}')

Дальше нам нужен основной код.

#!venv/bin/python3 """ Oxygen Library --------------------------------------------------------------------------------  Автор: Okulus Dev (aka DrArgentum)  Лицензия: GNU GPL v3 --------------------------------------------------------------------------------  Описание: Базовые функции для использования химии в ваших проектах   Перечень:    1. Парсинг элементов из формулы    2. Вычисление молекулярной массы    3. Вычисление массовой доли  Copyright (C) 2023  Okulus Dev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>. """ import re from collections import Counter # здесь нам нужен импорт или вставка кода химического элемента   def repl(m):     return m[1] * int(m[2] if m[2] else 1)   def is_balanced(text, brackets="〈〉()[]{}"):     opening, closing = brackets[::2], brackets[1::2]     stack = []     for character in text:         if character in opening:             stack.append(opening.index(character))         elif character in closing:             if stack and stack[-1] == closing.index(character):                 stack.pop()               else:                 return False     return (not stack)     def parse_molecule(formula: str) -> dict:     """Парсинг молекулы"""     if not is_balanced(formula):         raise ValueError(f"Brackets in {formula} is not balanced")         sys.exit()          while '(' in formula:         formula = re.sub(r'\((\w*)\)(\d*)', repl, formula)     while '[' in formula:         formula = re.sub(r'\[(\w*)\](\d*)', repl, formula)     formula = re.sub(r'([A-Z][a-z]?)(\d*)', repl, formula)     formula_dict = Counter(re.findall('[A-Z][a-z]*', formula))      return formula_dict   def get_element_mass(element: str):     """Получаем массу элемента по его химическому значку"""     try:         return MendeleevTable.get_element_by_shortname(element).relative_atomic_mass     except:         raise ValueError(f"Element {element} does not exists. Try other!")         sys.exit()  def calculate_mass_fraction_of_element(formula: str, element: str):     """Вычисляем массовую долю элемента в формуле"""     formula = parse_molecule(formula)     mass_fraction = 0     mass = 0      for i in formula.items():         mass += get_element_mass(i[0]) * i[1]      for i in formula.items():         if MendeleevTable.get_element_by_shortname(i[0]).short_name == element:             mass_fraction = (get_element_mass(i[0]) * i[1] / mass) * 100             break      return mass_fraction   def calculate_relative_molecular_mass(formula: str, print_info: bool=False) -> dict:     """Вычисляем относительную массовую долю формулы"""     result = parse_molecule(formula)     mass = 0     neutrons = 0     electrons = 0     protons = 0      for i in result.items():         try:             if print_info:                 print(f'{i[1]} {MendeleevTable.get_element_by_shortname([i[0]][0]).name} = \ {MendeleevTable.get_element_by_shortname([i[0]][0]).relative_atomic_mass * i[1]}')                 print(f'Кол-во протонов в {MendeleevTable.get_element_by_shortname([i[0]][0]).short_name} \ ({MendeleevTable.get_element_by_shortname([i[0]][0]).name}): {MendeleevTable.get_element_by_shortname([i[0]][0]).protons}')                 print(f'Кол-во электронов в {MendeleevTable.get_element_by_shortname([i[0]][0]).short_name} \ ({MendeleevTable.get_element_by_shortname([i[0]][0]).name}): {MendeleevTable.get_element_by_shortname([i[0]][0]).electrons}')                 print(f'Кол-во нейтронов в {MendeleevTable.get_element_by_shortname([i[0]][0]).short_name} \ ({MendeleevTable.get_element_by_shortname([i[0]][0]).name}): {MendeleevTable.get_element_by_shortname([i[0]][0]).neutrons}')              neutrons += MendeleevTable.get_element_by_shortname([i[0]][0]).neutrons * i[1]             electrons += MendeleevTable.get_element_by_shortname([i[0]][0]).electrons * i[1]             protons += MendeleevTable.get_element_by_shortname([i[0]][0]).protons * i[1]              mass += get_element_mass(i[0]) * i[1]         except Exception as e:             print(e)             raise ValueError(f'Element {i[0]} does not exists. Try other!')      return {         'mass': mass,         'electrons': electrons,         'neutrons': neutrons,         'protons': protons     } 

И потом мы все это подключаем к запускаемому файлу:

#!/usr/bin/python3 # -*- coding:utf-8 -*- """ --------------------------------------------------------------------------------  Автор: Okulus Dev (aka DrArgentum)  Лицензия: GNU GPL v3  Название: Основной файл  Файл: oxygen.py --------------------------------------------------------------------------------  Описание: Главный файл, содержащий импорты всех библиотек и функций  Copyright (C) 2023  Okulus Dev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>. """ import argparse import textwrap # from oxygen.chemistry.base import calculate_relative_molecular_mass, \ #                                     calculate_mass_fraction_of_element # from oxygen.chemistry.formulas import read_formula # сверху я привел примеры нужных функций, вставьте их код сюда или импортируйте их сами   def get_molecular_mass_from_formule(formula):     print(f'Расчет формулы {formula}:\n')     mass = calculate_relative_molecular_mass(formula, True)      if mass is not None:         print(f'Относительная молекулярная масса формулы {formula} = ~{mass["mass"]}')         print(f'Количество протонов в формуле {formula} = ~{mass["protons"]}')         print(f'Количество электронов в формуле {formula} = ~{mass["electrons"]}')         print(f'Количество нейтронов в формуле {formula} = ~{mass["neutrons"]}')     else:         print(f'Ошибка парсинга формулы {formula}')   def main():     parser = argparse.ArgumentParser(prog='Oxygen Library', allow_abbrev=True,                             description='Oxygen',                             formatter_class=argparse.RawDescriptionHelpFormatter,     epilog=textwrap.dedent(''' Примеры использования:  # Включаем режим химии oxygen.py -c  # Вычисление относительной молекулярной массы формулы oxygen.py -c -rmm <ФОРМУЛА>  # Вычисление массовой доли элемента в формуле oxygen.py -c -mf <ФОРМУЛА> -mfe <ЭЛЕМЕНТ ИЗ ФОРМУЛЫ>  # Вычисление относительной молекулярной массы формулы с определением формулы сложного вещества oxygen.py -cr -rmm <ФОРМУЛА>  # Вычисление массовой доли элемента в формуле с определением формулы сложного вещества oxygen.py -cr -mf <ФОРМУЛА> -mfe <ЭЛЕМЕНТ ИЗ ФОРМУЛЫ>  Copyright Okulus Dev (C) 2023 '''))     parser.add_argument('-c', '--chemistry-mode', help='включить мод химии',                         action='store_true')     parser.add_argument('-r', '--read-formula', help='включить чтение формулы',                         action='store_true', default=False)     parser.add_argument('-rmm', '--relative-molecular-mass',                         help='рассчет молекулярной массы формулы')     parser.add_argument('-mf', '--mass-fraction', metavar='ФОРМУЛА',                         help='рассчет массовой доли в формуле')     parser.add_argument('-mfe', '--mf-element', metavar='ФОРМУЛА',                         help='элемент для рассчета массовой доли')     args = parser.parse_args()      if args.chemistry_mode:         if args.relative_molecular_mass:             get_molecular_mass_from_formule(args.relative_molecular_mass)             if args.read_formula:                 read_formula(args.relative_molecular_mass)         elif args.mass_fraction:             if args.mf_element:                 res = calculate_mass_fraction_of_element(args.mass_fraction,                                                           args.mf_element)                 if args.read_formula:                     read_formula(args.mass_fraction)                 print(f"Массовая доля {args.mf_element} в \ {args.mass_fraction}: {res}%")             else:                 print('К сожалению, вы не указали нужный элемент.')                 if args.read_formula:                     read_formula(args.mass_fraction)   if __name__ == "__main__":     main() 

Здесь мы остановимся. Я также добавил парсер аргументов командной строки argparse.

Примеры использования:  # Включаем режим химии oxygen.py -c  # Вычисление относительной молекулярной массы формулы oxygen.py -c -rmm <ФОРМУЛА>  # Вычисление массовой доли элемента в формуле oxygen.py -c -mf <ФОРМУЛА> -mfe <ЭЛЕМЕНТ ИЗ ФОРМУЛЫ>  # Вычисление относительной молекулярной массы формулы с определением формулы сложного вещества oxygen.py -cr -rmm <ФОРМУЛА>  # Вычисление массовой доли элемента в формуле с определением формулы сложного вещества oxygen.py -cr -mf <ФОРМУЛА> -mfe <ЭЛЕМЕНТ ИЗ ФОРМУЛЫ>

Argparse позволяет комбинировать флаги, и вместо -c -r можно писать -cr.

Также я сократил названия функций и добавил их названия.

В следующей статье мы займемся вычислительной химией глубоко. С вами был доктор Аргентум, всем пока!


ссылка на оригинал статьи https://habr.com/ru/articles/776262/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *