Skip to content

Commit 6b326d2

Browse files
authored
INP manager refactor (#143)
* Implement Card Set 2 (Element Information) for SAMMY INP files with parsing and generation functionality * Add Card Set 3 (Physical Constants) implementation and tests for SAMMY INP files * Add Card03Density class and unit tests for sample density in SAMMY INP files * Refactor InpManager to remove unused normalization parameters and update element information formatting * Update InpManager to use default values for title and reaction type in generated sections * update pixi lock file
1 parent 93cca90 commit 6b326d2

File tree

9 files changed

+1857
-931
lines changed

9 files changed

+1857
-931
lines changed

pixi.lock

Lines changed: 922 additions & 879 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python
2+
"""
3+
Card Set 2 (Element Information) for SAMMY INP files.
4+
5+
This module provides the Card02 class for parsing and generating the element
6+
information line in SAMMY input files. This card appears early in the INP file
7+
and defines the sample element, atomic weight, and energy range.
8+
9+
Format specification (Card Set 2):
10+
Cols Format Variable Description
11+
1-10 A ELMNT Sample element's name (left-aligned)
12+
11-20 F AW Atomic weight (amu)
13+
21-30 F EMIN Minimum energy for dataset (eV)
14+
31-40 F EMAX Maximum energy (eV)
15+
16+
Example:
17+
Si 27.976928 300000. 1800000.
18+
"""
19+
20+
from typing import List
21+
22+
from pydantic import BaseModel, Field
23+
24+
from pleiades.utils.logger import loguru_logger
25+
26+
logger = loguru_logger.bind(name=__name__)
27+
28+
# Format specification for Card Set 2
29+
CARD02_FORMAT = {
30+
"element": slice(0, 10),
31+
"atomic_weight": slice(10, 20),
32+
"min_energy": slice(20, 30),
33+
"max_energy": slice(30, 40),
34+
}
35+
36+
37+
class ElementInfo(BaseModel):
38+
"""Pydantic model for element information in Card Set 2.
39+
40+
Attributes:
41+
element: Sample element's name (up to 10 characters)
42+
atomic_weight: Atomic weight in amu
43+
min_energy: Minimum energy for dataset in eV
44+
max_energy: Maximum energy in eV
45+
"""
46+
47+
element: str = Field(..., description="Sample element's name", max_length=10)
48+
atomic_weight: float = Field(..., description="Atomic weight (amu)", gt=0)
49+
min_energy: float = Field(..., description="Minimum energy (eV)", ge=0)
50+
max_energy: float = Field(..., description="Maximum energy (eV)", gt=0)
51+
52+
def model_post_init(self, __context) -> None:
53+
"""Validate that max_energy > min_energy."""
54+
if self.max_energy <= self.min_energy:
55+
raise ValueError(f"max_energy ({self.max_energy}) must be greater than min_energy ({self.min_energy})")
56+
57+
58+
class Card02(BaseModel):
59+
"""
60+
Class representing Card Set 2 (element information) in SAMMY INP files.
61+
62+
This card defines the sample element, atomic weight, and energy range for the analysis.
63+
"""
64+
65+
@classmethod
66+
def from_lines(cls, lines: List[str]) -> ElementInfo:
67+
"""Parse element information from Card Set 2 line.
68+
69+
Args:
70+
lines: List of input lines (expects single line for Card 2)
71+
72+
Returns:
73+
ElementInfo: Parsed element information
74+
75+
Raises:
76+
ValueError: If format is invalid or required values missing
77+
"""
78+
if not lines or not lines[0].strip():
79+
message = "No valid Card 2 line provided"
80+
logger.error(message)
81+
raise ValueError(message)
82+
83+
line = lines[0]
84+
if len(line) < 40:
85+
line = f"{line:<40}"
86+
87+
try:
88+
element = line[CARD02_FORMAT["element"]].strip()
89+
atomic_weight = float(line[CARD02_FORMAT["atomic_weight"]].strip())
90+
min_energy = float(line[CARD02_FORMAT["min_energy"]].strip())
91+
max_energy = float(line[CARD02_FORMAT["max_energy"]].strip())
92+
except (ValueError, IndexError) as e:
93+
message = f"Failed to parse Card 2 line: {e}"
94+
logger.error(message)
95+
raise ValueError(message)
96+
97+
if not element:
98+
message = "Element name cannot be empty"
99+
logger.error(message)
100+
raise ValueError(message)
101+
102+
return ElementInfo(
103+
element=element,
104+
atomic_weight=atomic_weight,
105+
min_energy=min_energy,
106+
max_energy=max_energy,
107+
)
108+
109+
@classmethod
110+
def to_lines(cls, element_info: ElementInfo) -> List[str]:
111+
"""Convert element information to Card Set 2 formatted line.
112+
113+
Args:
114+
element_info: ElementInfo object containing element data
115+
116+
Returns:
117+
List containing single formatted line for Card Set 2
118+
"""
119+
if not isinstance(element_info, ElementInfo):
120+
message = "element_info must be an instance of ElementInfo"
121+
logger.error(message)
122+
raise ValueError(message)
123+
124+
line = (
125+
f"{element_info.element:<10s}"
126+
f"{element_info.atomic_weight:10.6f}"
127+
f"{element_info.min_energy:10.3f}"
128+
f"{element_info.max_energy:10.1f}"
129+
)
130+
131+
return [line]
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python
2+
"""
3+
Card Set 3 (Physical Constants) for SAMMY INP files.
4+
5+
This module provides the Card03 class for parsing and generating the physical
6+
constants line in SAMMY input files. This card appears after the element information
7+
and defines temperature, flight path, and resolution parameters.
8+
9+
Format specification (Card Set 3 - Physical Constants):
10+
The line contains five floating-point values with variable spacing:
11+
- TEMP: Temperature (K)
12+
- FPL: Flight path length (m)
13+
- DELTAL: Spread in flight-path length (m)
14+
- DELTAG: Gaussian resolution width (μs)
15+
- DELTAE: e-folding width of exponential resolution (μs)
16+
17+
Example:
18+
300. 200.0000 0.182233 0.0 0.002518
19+
"""
20+
21+
from typing import List
22+
23+
from pydantic import BaseModel, Field
24+
25+
from pleiades.utils.logger import loguru_logger
26+
27+
logger = loguru_logger.bind(name=__name__)
28+
29+
30+
class PhysicalConstants(BaseModel):
31+
"""Pydantic model for physical constants in Card Set 3.
32+
33+
Attributes:
34+
temperature: Temperature in Kelvin
35+
flight_path_length: Flight path length in meters
36+
delta_l: Spread in flight-path length in meters
37+
delta_g: Gaussian resolution width in microseconds
38+
delta_e: e-folding width of exponential resolution in microseconds
39+
"""
40+
41+
temperature: float = Field(..., description="Temperature (K)", gt=0)
42+
flight_path_length: float = Field(..., description="Flight path length (m)", gt=0)
43+
delta_l: float = Field(default=0.0, description="Spread in flight-path length (m)", ge=0)
44+
delta_g: float = Field(default=0.0, description="Gaussian resolution width (μs)", ge=0)
45+
delta_e: float = Field(default=0.0, description="e-folding width of exponential resolution (μs)", ge=0)
46+
47+
48+
class Card03(BaseModel):
49+
"""
50+
Class representing Card Set 3 (physical constants) in SAMMY INP files.
51+
52+
This card defines temperature, flight path, and resolution parameters for the analysis.
53+
"""
54+
55+
@classmethod
56+
def from_lines(cls, lines: List[str]) -> PhysicalConstants:
57+
"""Parse physical constants from Card Set 3 line.
58+
59+
Args:
60+
lines: List of input lines (expects single line for Card 3)
61+
62+
Returns:
63+
PhysicalConstants: Parsed physical constants
64+
65+
Raises:
66+
ValueError: If format is invalid or required values missing
67+
"""
68+
if not lines or not lines[0].strip():
69+
message = "No valid Card 3 line provided"
70+
logger.error(message)
71+
raise ValueError(message)
72+
73+
line = lines[0].strip()
74+
fields = line.split()
75+
76+
if len(fields) < 2:
77+
message = f"Card 3 line must have at least 2 fields (TEMP, FPL), got {len(fields)}"
78+
logger.error(message)
79+
raise ValueError(message)
80+
81+
try:
82+
temperature = float(fields[0])
83+
flight_path_length = float(fields[1])
84+
delta_l = float(fields[2]) if len(fields) > 2 else 0.0
85+
delta_g = float(fields[3]) if len(fields) > 3 else 0.0
86+
delta_e = float(fields[4]) if len(fields) > 4 else 0.0
87+
except (ValueError, IndexError) as e:
88+
message = f"Failed to parse Card 3 line: {e}"
89+
logger.error(message)
90+
raise ValueError(message)
91+
92+
return PhysicalConstants(
93+
temperature=temperature,
94+
flight_path_length=flight_path_length,
95+
delta_l=delta_l,
96+
delta_g=delta_g,
97+
delta_e=delta_e,
98+
)
99+
100+
@classmethod
101+
def to_lines(cls, constants: PhysicalConstants) -> List[str]:
102+
"""Convert physical constants to Card Set 3 formatted line.
103+
104+
Args:
105+
constants: PhysicalConstants object containing parameter data
106+
107+
Returns:
108+
List containing single formatted line for Card Set 3
109+
"""
110+
if not isinstance(constants, PhysicalConstants):
111+
message = "constants must be an instance of PhysicalConstants"
112+
logger.error(message)
113+
raise ValueError(message)
114+
115+
line = (
116+
f" {constants.temperature:5.1f} "
117+
f"{constants.flight_path_length:8.4f} "
118+
f"{constants.delta_l:8.6f} "
119+
f"{constants.delta_g:3.1f} "
120+
f"{constants.delta_e:8.6f}"
121+
)
122+
123+
return [line]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python
2+
"""
3+
Sample Density for SAMMY INP files.
4+
5+
This module provides the Card03Density class for parsing and generating the sample
6+
density line in SAMMY input files. This line appears after the physical constants
7+
and defines the material density and number density.
8+
9+
Format specification (Sample Density):
10+
The line contains two floating-point values:
11+
- Density: Material density (g/cm³)
12+
- Number density: Number density (atoms/barn-cm)
13+
14+
Example:
15+
4.20000 0.347162
16+
"""
17+
18+
from typing import List
19+
20+
from pydantic import BaseModel, Field
21+
22+
from pleiades.utils.logger import loguru_logger
23+
24+
logger = loguru_logger.bind(name=__name__)
25+
26+
27+
class SampleDensity(BaseModel):
28+
"""Pydantic model for sample density parameters.
29+
30+
Attributes:
31+
density: Material density in g/cm³
32+
number_density: Number density in atoms/barn-cm
33+
"""
34+
35+
density: float = Field(..., description="Material density (g/cm³)", gt=0)
36+
number_density: float = Field(..., description="Number density (atoms/barn-cm)", gt=0)
37+
38+
39+
class Card03Density(BaseModel):
40+
"""
41+
Class representing sample density line in SAMMY INP files.
42+
43+
This line defines the material density and number density for the sample.
44+
"""
45+
46+
@classmethod
47+
def from_lines(cls, lines: List[str]) -> SampleDensity:
48+
"""Parse sample density from density line.
49+
50+
Args:
51+
lines: List of input lines (expects single line)
52+
53+
Returns:
54+
SampleDensity: Parsed sample density parameters
55+
56+
Raises:
57+
ValueError: If format is invalid or required values missing
58+
"""
59+
if not lines or not lines[0].strip():
60+
message = "No valid density line provided"
61+
logger.error(message)
62+
raise ValueError(message)
63+
64+
line = lines[0].strip()
65+
fields = line.split()
66+
67+
if len(fields) < 2:
68+
message = f"Density line must have 2 fields (density, number_density), got {len(fields)}"
69+
logger.error(message)
70+
raise ValueError(message)
71+
72+
try:
73+
density = float(fields[0])
74+
number_density = float(fields[1])
75+
except (ValueError, IndexError) as e:
76+
message = f"Failed to parse density line: {e}"
77+
logger.error(message)
78+
raise ValueError(message)
79+
80+
return SampleDensity(
81+
density=density,
82+
number_density=number_density,
83+
)
84+
85+
@classmethod
86+
def to_lines(cls, sample_density: SampleDensity) -> List[str]:
87+
"""Convert sample density to formatted line.
88+
89+
Args:
90+
sample_density: SampleDensity object containing density data
91+
92+
Returns:
93+
List containing single formatted line
94+
"""
95+
if not isinstance(sample_density, SampleDensity):
96+
message = "sample_density must be an instance of SampleDensity"
97+
logger.error(message)
98+
raise ValueError(message)
99+
100+
line = f" {sample_density.density:8.6f} {sample_density.number_density:.6e}"
101+
102+
return [line]

0 commit comments

Comments
 (0)