# -*- coding: utf-8 -*- """접수 단계 검사. 여기서 보는 것은 **형식과 자격**뿐이다. 실제 채점(전기화학창·리튬 이동)은 워커가 한다. 스페이스는 MP 데이터도 GPU 도 없으므로, 여기서 물성을 판정하는 척하면 안 된다. 거절은 참가자가 **고칠 수 있는 것**에만 쓴다. 값을 얻지 못한 항목은 거절이 아니라 보류로 둔다. """ import re # 채점 대상 원소. SUPPORTED = set(""" H Li Be B C N O F Na Mg Al Si P S Cl K Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I Cs Ba La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Ac Th Pa U Np Pu """.split()) _CIF_HINT = ("data_", "_cell_length_a", "_atom_site") def parse_formula(text): """화학식을 조성으로. **괄호를 반드시 처리한다.** 직접 정규식으로 훑으면 `LiZr2(PO4)3` 가 P1O4 가 되어 조성이 통째로 틀린다. Materials Project 자신이 LGPS 를 `Li10Ge(PS6)2` 로 적으므로 이건 예외가 아니라 기본이다. """ from pymatgen.core import Composition c = Composition(str(text).strip()) return {str(k): float(v) for k, v in c.get_el_amt_dict().items()} def check(text, cif=None, require_elements=("Li",), max_atoms=60, supported=None, max_elements=6): """접수 판정. (ok, reason, info) 를 돌려준다.""" supported = supported or SUPPORTED raw = (text or "").strip() if not raw: return False, "화학식을 입력해 주세요.", {} if len(raw) > 120: return False, "화학식이 너무 깁니다.", {} if not re.match(r"^[A-Za-z0-9()\[\]\.\s]+$", raw): return False, "화학식에 쓸 수 없는 문자가 있습니다.", {} try: comp = parse_formula(raw) except Exception: return False, "화학식을 해석하지 못했습니다. 예: Li3YCl6, LiZr2(PO4)3", {} if not comp: return False, "화학식을 해석하지 못했습니다.", {} for el in require_elements: if el not in comp: return False, "리튬 전해질 시즌입니다. %s 를 포함해야 합니다." % el, {} bad = sorted(e for e in comp if e not in supported) if bad: return False, ("계산 기준 상태가 없는 원소입니다: %s" % ", ".join(bad)), {} if len(comp) > max_elements: return False, "원소가 %d 종을 넘습니다 (현재 %d 종)." % (max_elements, len(comp)), {} # **기약 조성**을 기준으로 삼는다. Li3YCl6 와 Li9Y3Cl18 은 같은 물질이므로 같은 것으로 # 세야 한다. 원본 조성으로 열쇠를 만들면 배수만 바꿔 같은 물질을 몇 번이고 올릴 수 있다. from math import gcd from functools import reduce integral = all(abs(v - round(v)) < 1e-6 for v in comp.values()) if integral: ints = {k: int(round(v)) for k, v in comp.items()} g = reduce(gcd, ints.values()) or 1 reduced = {k: v // g for k, v in ints.items()} else: reduced = dict(comp) # 비정수 조성(도핑 등)은 그대로 둔다 n_atom = sum(reduced.values()) if n_atom > max_atoms: return False, ("기약 조성의 원자 수가 %d 개로 상한 %d 를 넘습니다." % (n_atom, max_atoms)), {} info = {"composition": comp, "reduced": reduced, "n_atoms": n_atom, "n_elements": len(comp)} if cif: c = str(cif) if len(c) > 400_000: return False, "구조 파일이 너무 큽니다 (400KB 상한).", {} if not any(h in c for h in _CIF_HINT): return False, "구조 파일이 CIF 형식으로 보이지 않습니다.", {} info["has_structure"] = True return True, "", info