#!/usr/bin/env python
"""
Card Set 2 (Element Information) for SAMMY INP files.
This module provides the Card02 class for parsing and generating the element
information line in SAMMY input files. This card appears early in the INP file
and defines the sample element, atomic weight, and energy range.
Format specification (Card Set 2):
Cols Format Variable Description
1-10 A ELMNT Sample element's name (left-aligned)
11-20 F AW Atomic weight (amu)
21-30 F EMIN Minimum energy for dataset (eV)
31-40 F EMAX Maximum energy (eV)
Example:
Si 27.976928 300000. 1800000.
"""
from typing import List
from pydantic import BaseModel, Field
from pleiades.utils.logger import loguru_logger
logger = loguru_logger.bind(name=__name__)
# Format specification for Card Set 2
CARD02_FORMAT = {
"element": slice(0, 10),
"atomic_weight": slice(10, 20),
"min_energy": slice(20, 30),
"max_energy": slice(30, 40),
}
[docs]
class ElementInfo(BaseModel):
"""Pydantic model for element information in Card Set 2.
Attributes:
element: Sample element's name (up to 10 characters)
atomic_weight: Atomic weight in amu
min_energy: Minimum energy for dataset in eV
max_energy: Maximum energy in eV
"""
element: str = Field(..., description="Sample element's name", max_length=10)
atomic_weight: float = Field(..., description="Atomic weight (amu)", gt=0)
min_energy: float = Field(..., description="Minimum energy (eV)", ge=0)
max_energy: float = Field(..., description="Maximum energy (eV)", gt=0)
[docs]
def model_post_init(self, __context) -> None:
"""Validate that max_energy > min_energy."""
if self.max_energy <= self.min_energy:
raise ValueError(f"max_energy ({self.max_energy}) must be greater than min_energy ({self.min_energy})")
[docs]
class Card02(BaseModel):
"""
Class representing Card Set 2 (element information) in SAMMY INP files.
This card defines the sample element, atomic weight, and energy range for the analysis.
"""
[docs]
@classmethod
def from_lines(cls, lines: List[str]) -> ElementInfo:
"""Parse element information from Card Set 2 line.
Args:
lines: List of input lines (expects single line for Card 2)
Returns:
ElementInfo: Parsed element information
Raises:
ValueError: If format is invalid or required values missing
"""
if not lines or not lines[0].strip():
message = "No valid Card 2 line provided"
logger.error(message)
raise ValueError(message)
line = lines[0]
if len(line) < 40:
line = f"{line:<40}"
try:
element = line[CARD02_FORMAT["element"]].strip()
atomic_weight = float(line[CARD02_FORMAT["atomic_weight"]].strip())
min_energy = float(line[CARD02_FORMAT["min_energy"]].strip())
max_energy = float(line[CARD02_FORMAT["max_energy"]].strip())
except (ValueError, IndexError) as e:
message = f"Failed to parse Card 2 line: {e}"
logger.error(message)
raise ValueError(message)
if not element:
message = "Element name cannot be empty"
logger.error(message)
raise ValueError(message)
return ElementInfo(
element=element,
atomic_weight=atomic_weight,
min_energy=min_energy,
max_energy=max_energy,
)
[docs]
@classmethod
def to_lines(cls, element_info: ElementInfo) -> List[str]:
"""Convert element information to Card Set 2 formatted line.
Args:
element_info: ElementInfo object containing element data
Returns:
List containing single formatted line for Card Set 2
"""
if not isinstance(element_info, ElementInfo):
message = "element_info must be an instance of ElementInfo"
logger.error(message)
raise ValueError(message)
line = (
f"{element_info.element:<10s}"
f"{element_info.atomic_weight:10.6f}"
f"{element_info.min_energy:10.3f}"
f"{element_info.max_energy:10.1f}"
)
return [line]