tooluniverse.crystal_structure_tool 源代码

"""
Crystal Structure Validator Tool

Compute theoretical density from unit cell parameters (a, b, c, alpha, beta,
gamma), Z value, and molecular weight, then compare against a reported density.

No external API calls. Pure Python with stdlib math only.
"""

import math
from typing import Any, Dict
from .base_tool import BaseTool
from .tool_registry import register_tool

AVOGADRO = 6.02214076e23
ANG3_TO_CM3 = 1e-24


def _unit_cell_volume(a, b, c, alpha_deg, beta_deg, gamma_deg) -> float:
    """Compute unit cell volume in A^3 for any crystal system."""
    alpha = math.radians(alpha_deg)
    beta = math.radians(beta_deg)
    gamma = math.radians(gamma_deg)

    ca, cb, cg = math.cos(alpha), math.cos(beta), math.cos(gamma)
    val = 1.0 - ca**2 - cb**2 - cg**2 + 2.0 * ca * cb * cg
    if val < 0:
        raise ValueError(
            f"Invalid cell angles: alpha={alpha_deg}, beta={beta_deg}, "
            f"gamma={gamma_deg} produce negative discriminant ({val:.6f})"
        )
    return a * b * c * math.sqrt(val)


def _theoretical_density(Z, MW, volume_ang3) -> float:
    """Calculate density in g/cm^3."""
    volume_cm3 = volume_ang3 * ANG3_TO_CM3
    return (Z * MW) / (volume_cm3 * AVOGADRO)


def _detect_crystal_system(a, b, c, alpha, beta, gamma, tol=0.01, ang_tol=0.1) -> str:
    eq_ab = abs(a - b) < tol
    eq_ac = abs(a - c) < tol
    eq_bc = abs(b - c) < tol
    all_eq = eq_ab and eq_ac

    def is_90(x):
        return abs(x - 90.0) < ang_tol

    def is_120(x):
        return abs(x - 120.0) < ang_tol

    all_90 = is_90(alpha) and is_90(beta) and is_90(gamma)

    if all_eq and all_90:
        return "cubic"
    if eq_ab and all_90 and not eq_ac:
        return "tetragonal"
    if all_90 and not eq_ab and not eq_ac and not eq_bc:
        return "orthorhombic"
    if eq_ab and is_90(alpha) and is_90(beta) and is_120(gamma):
        return "hexagonal"
    if (
        all_eq
        and abs(alpha - beta) < ang_tol
        and abs(alpha - gamma) < ang_tol
        and not all_90
    ):
        return "trigonal (rhombohedral)"
    if is_90(alpha) and is_90(gamma) and not is_90(beta):
        return "monoclinic"
    return "triclinic"


[文档] @register_tool("CrystalStructureTool") class CrystalStructureTool(BaseTool): """Validate crystal structure by computing density from unit cell parameters."""
[文档] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.parameter = tool_config.get("parameter", {}) self.required = self.parameter.get("required", [])
[文档] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: operation = arguments.get("operation", "validate") if operation != "validate": return { "status": "error", "error": f"Unknown operation: {operation}. Only 'validate' is supported.", } try: return self._validate(arguments) except Exception as e: return {"status": "error", "error": f"Validation failed: {str(e)}"}
[文档] def _validate(self, arguments: Dict[str, Any]) -> Dict[str, Any]: a = arguments.get("a") b = arguments.get("b") c = arguments.get("c") alpha = arguments.get("alpha", 90.0) beta = arguments.get("beta", 90.0) gamma = arguments.get("gamma", 90.0) Z = arguments.get("Z") mw = arguments.get("mw") reported_density = arguments.get("reported_density") for name, val in [("a", a), ("Z", Z), ("mw", mw)]: if val is None: return { "status": "error", "error": f"Missing required parameter: {name}", } # Default b and c to a for cubic if b is None: b = a if c is None: c = a V = _unit_cell_volume(a, b, c, alpha, beta, gamma) d_calc = _theoretical_density(Z, mw, V) system = _detect_crystal_system(a, b, c, alpha, beta, gamma) data = { "crystal_system": system, "cell_params": { "a": a, "b": b, "c": c, "alpha": alpha, "beta": beta, "gamma": gamma, }, "volume_ang3": round(V, 4), "Z": Z, "mw": mw, "calculated_density_g_cm3": round(d_calc, 4), } if reported_density is not None: diff = abs(d_calc - reported_density) pct = ( (diff / reported_density) * 100 if reported_density != 0 else float("inf") ) data["reported_density_g_cm3"] = reported_density data["difference_g_cm3"] = round(diff, 4) data["percent_error"] = round(pct, 2) if pct < 1.0: data["verdict"] = "OK" elif pct < 5.0: data["verdict"] = "WARNING — deviation > 1%" else: data["verdict"] = "MISMATCH — deviation > 5%" return {"status": "success", "data": data}