Skip to content

Commit

Permalink
add boradening paramter card
Browse files Browse the repository at this point in the history
  • Loading branch information
KedoKudo committed Jan 17, 2025
1 parent 3d5c043 commit 8911e3f
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/pleiades/sammy/parameters/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from pleiades.sammy.parameters.broadening import BroadeningParameterCard # noqa: F401
from pleiades.sammy.parameters.external_r import ExternalREntry # noqa: F401
from pleiades.sammy.parameters.resonance import ResonanceEntry # noqa: F401
14 changes: 7 additions & 7 deletions src/pleiades/sammy/parameters/broadening.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,20 +194,20 @@ def to_lines(self) -> List[str]:

# Format main parameter line
main_parts = [
format_float(self.crfn, width=10),
format_float(self.temp, width=10),
format_float(self.thick, width=10),
format_float(self.deltal, width=10),
format_float(self.deltag, width=10),
format_float(self.deltae, width=10),
format_float(self.crfn, width=9),
format_float(self.temp, width=9),
format_float(self.thick, width=9),
format_float(self.deltal, width=9),
format_float(self.deltag, width=9),
format_float(self.deltae, width=9),
format_vary(self.flag_crfn),
format_vary(self.flag_temp),
format_vary(self.flag_thick),
format_vary(self.flag_deltal),
format_vary(self.flag_deltag),
format_vary(self.flag_deltae),
]
lines.append("".join(main_parts))
lines.append(" ".join(main_parts))

# Add uncertainties line if any uncertainties are present
if any(getattr(self, f"d_{param}") is not None for param in ["crfn", "temp", "thick", "deltal", "deltag", "deltae"]):
Expand Down
17 changes: 15 additions & 2 deletions src/pleiades/sammy/parameters/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,21 @@ def format_float(value: Optional[float], width: int = 11) -> str:
"""Helper to format float values in fixed width with proper spacing"""
if value is None:
return " " * width
# Format with proper scientific notation and alignment
return f"{value:<{width}.4E}"

# Subtract 5 characters for "E+xx" (scientific notation exponent)
# The rest is for the significant digits (1 before the dot and decimals)
max_decimals = max(0, width - 6) # At least room for "0.E+00"

# Create a format string with dynamic precision
format_str = f"{{:.{max_decimals}E}}"
formatted = format_str.format(value)

# Ensure the string fits the width
if len(formatted) > width:
raise ValueError(f"Cannot format value {value} to fit in {width} characters.")

# Align to the left if required
return f"{formatted:<{width}}"


def format_vary(value: VaryFlag) -> str:
Expand Down
37 changes: 37 additions & 0 deletions src/pleiades/sammy/parfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python
"""Top level parameter file handler for SAMMY."""

from typing import Optional

from pydantic import BaseModel, Field

from pleiades.sammy.parameters import (
BroadeningParameterCard,
ExternalREntry,
ResonanceEntry,
)


class SammyParameterFile(BaseModel):
"""Top level parameter file for SAMMY."""

resonance: ResonanceEntry = Field(description="Resonance parameters")
fudge: float = Field(0.1, description="Fudge factor", ge=0.0, le=1.0)
# Add additional optional cards
external_r: Optional[ExternalREntry] = Field(None, description="External R matrix")
broadening: Optional[BroadeningParameterCard] = Field(None, description="Broadening parameters")

@classmethod
def from_file(cls, file_path):
"""Load a SAMMY parameter file from disk."""
with open(file_path, "r") as f:
lines = f.readlines()

# Parse resonance card
resonance = ResonanceEntry.from_str(lines[0])

return cls(resonance=resonance)


if __name__ == "__main__":
print("TODO: usage example for SAMMY parameter file handling")
163 changes: 163 additions & 0 deletions tests/unit/pleiades/sammy/parameters/test_broadening.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/usr/bin/env python
"""Unit tests for card 04::broadening parameters."""

import pytest

from pleiades.sammy.parameters.broadening import BroadeningParameterCard, BroadeningParameters
from pleiades.sammy.parameters.helper import VaryFlag

# Test data with proper 10-char width formatting
MAIN_ONLY_LINE = "1.234E+00 2.980E+02 1.500E-01 2.500E-02 1.000E+00 5.000E-01 1 0 1 0 1 0"

WITH_UNC_LINES = [
"1.234E+00 2.980E+02 1.500E-01 2.500E-02 1.000E+00 5.000E-01 1 0 1 0 1 0",
"1.000E-02 1.000E+00 1.000E-03 1.000E-03 1.000E-02 1.000E-02",
]

FULL_LINES = [
"1.234E+00 2.980E+02 1.500E-01 2.500E-02 1.000E+00 5.000E-01 1 0 1 0 1 0",
"1.000E-02 1.000E+00 1.000E-03 1.000E-03 1.000E-02 1.000E-02",
"1.000E-01 2.000E-02 1 1",
"5.000E-03 1.000E-03",
]

COMPLETE_CARD = [
"BROADening parameters may be varied",
"1.234E+00 2.980E+02 1.500E-01 2.500E-02 1.000E+00 5.000E-01 1 0 1 0 1 0",
"1.000E-02 1.000E+00 1.000E-03 1.000E-03 1.000E-02 1.000E-02",
"1.000E-01 2.000E-02 1 1",
"5.000E-03 1.000E-03",
"",
]


def test_main_parameters_parsing():
"""Test parsing of main parameters only."""
params = BroadeningParameters.from_lines([MAIN_ONLY_LINE])

# Check values
assert params.crfn == pytest.approx(1.234)
assert params.temp == pytest.approx(298.0)
assert params.thick == pytest.approx(0.15)
assert params.deltal == pytest.approx(0.025)
assert params.deltag == pytest.approx(1.0)
assert params.deltae == pytest.approx(0.5)

# Check flags
assert params.flag_crfn == VaryFlag.YES
assert params.flag_temp == VaryFlag.NO
assert params.flag_thick == VaryFlag.YES
assert params.flag_deltal == VaryFlag.NO
assert params.flag_deltag == VaryFlag.YES
assert params.flag_deltae == VaryFlag.NO

# Check optional fields are None
assert params.deltc1 is None
assert params.deltc2 is None
assert params.d_crfn is None
assert params.d_temp is None


def test_parameters_with_uncertainties():
"""Test parsing of parameters with uncertainties."""
params = BroadeningParameters.from_lines(WITH_UNC_LINES)

# Check main values
assert params.crfn == pytest.approx(1.234)
assert params.temp == pytest.approx(298.0)

# Check uncertainties
assert params.d_crfn == pytest.approx(0.01)
assert params.d_temp == pytest.approx(1.0)
assert params.d_thick == pytest.approx(0.001)
assert params.d_deltal == pytest.approx(0.001)
assert params.d_deltag == pytest.approx(0.01)
assert params.d_deltae == pytest.approx(0.01)


def test_full_parameters():
"""Test parsing of full parameter set including Gaussian parameters."""
params = BroadeningParameters.from_lines(FULL_LINES)

# Check Gaussian parameters
assert params.deltc1 == pytest.approx(0.1)
assert params.deltc2 == pytest.approx(0.02)
assert params.d_deltc1 == pytest.approx(0.005)
assert params.d_deltc2 == pytest.approx(0.001)
assert params.flag_deltc1 == VaryFlag.YES
assert params.flag_deltc2 == VaryFlag.YES


def test_format_compliance():
"""Test that output lines comply with fixed-width format."""
params = BroadeningParameters.from_lines(FULL_LINES)
output_lines = params.to_lines()

print(output_lines)

# Check first line field widths
first_line = output_lines[0]
assert len(first_line[:10].rstrip()) == 9 # 9 chars + 1 space
assert len(first_line[10:20].rstrip()) == 9
assert len(first_line[20:30].rstrip()) == 9
assert len(first_line[30:40].rstrip()) == 9
assert len(first_line[40:50].rstrip()) == 9
assert len(first_line[50:60].rstrip()) == 9


def test_complete_card():
"""Test parsing and formatting of complete card including header."""
card = BroadeningParameterCard.from_lines(COMPLETE_CARD)
output_lines = card.to_lines()

# Check header
assert output_lines[0].startswith("BROAD")

# Check number of lines
assert len(output_lines) == 6 # Header + 4 data lines + blank

# Check last line is blank
assert output_lines[-1].strip() == ""


def test_invalid_header():
"""Test error handling for invalid header."""
bad_lines = ["WRONG header", MAIN_ONLY_LINE]
with pytest.raises(ValueError, match="Invalid header"):
BroadeningParameterCard.from_lines(bad_lines)


def test_missing_gaussian_parameter():
"""Test error handling for incomplete Gaussian parameters."""
bad_lines = [
MAIN_ONLY_LINE,
"1.000E-02 1.000E+00 1.000E-03 1.000E-03 1.000E-02 1.000E-02",
"1.000E-01 1", # Missing DELTC2
]
with pytest.raises(ValueError, match="Both DELTC1 and DELTC2 must be present"):
BroadeningParameters.from_lines(bad_lines)


def test_empty_input():
"""Test error handling for empty input."""
with pytest.raises(ValueError, match="No valid parameter line provided"):
BroadeningParameters.from_lines([])


def test_roundtrip():
"""Test that parsing and then formatting produces identical output."""
card = BroadeningParameterCard.from_lines(COMPLETE_CARD)
output_lines = card.to_lines()

# Parse the output again
reparsed_card = BroadeningParameterCard.from_lines(output_lines)

# Compare all attributes
assert card.parameters.crfn == reparsed_card.parameters.crfn
assert card.parameters.temp == reparsed_card.parameters.temp
assert card.parameters.deltc1 == reparsed_card.parameters.deltc1
assert card.parameters.flag_crfn == reparsed_card.parameters.flag_crfn


if __name__ == "__main__":
pytest.main(["-v", __file__])

0 comments on commit 8911e3f

Please sign in to comment.