From f2e4015596bcce2456aaa11c8bcfd5acc0511778 Mon Sep 17 00:00:00 2001 From: "Alexander M. Long" Date: Tue, 18 Feb 2025 16:05:00 -0700 Subject: [PATCH 01/47] adding ex012 example in notebook format. --- notebook/samexm/ex012/ex012.ipynb | 0 notebook/samexm/ex012/ex012a.par | 176 ++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 notebook/samexm/ex012/ex012.ipynb create mode 100755 notebook/samexm/ex012/ex012a.par diff --git a/notebook/samexm/ex012/ex012.ipynb b/notebook/samexm/ex012/ex012.ipynb new file mode 100644 index 0000000..e69de29 diff --git a/notebook/samexm/ex012/ex012a.par b/notebook/samexm/ex012/ex012a.par new file mode 100755 index 0000000..0c3fd15 --- /dev/null +++ b/notebook/samexm/ex012/ex012a.par @@ -0,0 +1,176 @@ +-3.6616E+06 1.5877E+05 3.6985E+09 0 0 1 1 +-8.7373E+05 1.0253E+03 1.0151E+02 0 0 1 1 +-3.6529E+05 1.0000E+03 3.0406E+01 0 0 0 1 +-6.3159E+04 1.0000E+03 4.6894E+01 0 0 0 1 +-4.8801E+04 1.0000E+03 9.2496E+00 0 0 0 1 +31739.99805 1.0000E+03 1.5667E+01 0 0 0 5 +55676.96094 1.5803E+03 6.5331E+05 0 0 0 1 +67732.84375 2.5000E+03 2.6589E+03 0 0 0 3 +70800.00781 1.0000E+03 2.9617E+01 0 0 0 5 +86797.35938 2.5000E+03 7.2618E+02 0 0 0 3 +181617.5000 5.6000E+03 3.4894E+07 0 0 0 1 +298700.0000 1.0000E+03 9.8860E+03 0 0 0 5 +301310.8125 3.6000E+03 2.3548E+03 0 0 0 1 +354588.6875 1.0000E+03 1.4460E+04 0 0 0 5 +399675.9375 6.6000E+02 8.1361E+02 0 0 0 3 +532659.8750 2.5000E+03 5.3281E+05 0 0 0 3 +565576.8750 2.9000E+03 1.0953E+07 0 0 0 3 +587165.7500 8.8000E+03 1.9916E+05 0 0 0 2 +590290.1250 3.6000E+03 5.2366E+05 0 0 0 1 +602467.3125 3.4000E+03 5.0491E+04 0 0 0 4 +714043.4375 2.5000E+03 1.2165E+03 0 0 0 3 +771711.9375 1.0000E+03 5.3139E+04 0 0 0 5 +812491.6250 9.7000E+03 3.0100E+07 0 0 0 3 +845233.8750 2.0000E+03 3.9791E+05 0 0 0 4 +872305.8125 1.3000E+03 3.2140E+04 0 0 0 5 +910043.5625 1.1300E+03 3.6733E+06 0 0 0 3 +962233.0000 1.6000E+04 7.6614E+07 0 0 0 2 +1017777.188 1.0000E+03 7.6192E+04 0 0 0 5 +1042856.812 1.0000E+03 9.3370E+05 0 0 0 5 +1085169.250 3.6000E+03 7.2794E+04 0 0 0 1 +1148103.625 1.0000E+03 3.1469E+03 0 0 0 5 +1162663.625 3.8000E+03 3.0136E+06 0 0 0 1 +1199501.375 7.6000E+03 1.4914E+07 0 0 0 2 +1201238.750 3.6000E+03 4.6012E+06 0 0 0 1 +1256447.250 3.6000E+03 1.7383E+07 0 0 0 1 +1264441.750 1.0000E+03 8.4364E+05 0 0 0 5 +1379920.250 2.4000E+03 6.5299E+04 0 0 0 4 +1408269.750 2.7000E+03 5.1983E+06 0 0 0 3 +1479927.250 1.6500E+03 3.5025E+06 0 0 0 4 +1482395.375 8.8000E+03 8.8694E+02 0 0 0 2 +1512343.875 1.0000E+03 9.1493E+04 0 0 0 5 +1528742.375 2.4000E+03 2.9225E+06 0 0 0 4 +1580564.875 2.4000E+03 1.4955E+06 0 0 0 4 +1592844.250 8.8000E+03 1.1199E+07 0 0 0 2 +1597168.625 2.4000E+03 4.0172E+06 0 0 0 4 +1639561.500 1.0000E+03 1.5293E+07 0 0 0 5 +1651146.000 1.0000E+03 2.1555E+07 0 0 0 5 +1658595.000 8.8000E+03 1.5553E+06 0 0 0 2 +1664961.125 2.4000E+03 2.1590E+05 0 0 0 4 +1784952.750 2.4000E+03 1.9294E+05 0 0 0 4 +1805652.625 2.5000E+03 1.2991E+06 0 0 0 3 +1850667.250 1.0000E+03 3.5515E+07 0 0 0 5 +1852435.250 2.5000E+03 7.0707E+07 0 0 0 3 +1923655.250 1.0000E+03 1.0171E+06 0 0 0 5 +2248678.250 3.6000E+03 4.4476E+08 0 0 0 1 +1968869.750 1.0000E+03 5.7341E+06 0 0 0 5 +3007280.500 3.6000E+03 2.8996E+05 0 0 0 1 +3067775.250 3.6000E+03 4.2229E+05 0 0 0 1 +-2.1796E+06 4.0908E+05 1.7222E+09 0 0 0 8 +-8.6024E+05 9.9997E+02 3.4170E+07 0 0 0 8 +-4.3128E+05 1.0059E+03 2.2851E+08 0 0 0 8 +15282.00000 1.6460E+03 1.0000E+04 0 0 0 10 +38819.00000 2.4000E+03 7.5926E+04 0 0 0 13 +159682.9688 1.9000E+03 1.2003E+06 0 0 0 10 +184456.4844 1.5000E+03 1.3674E+05 0 0 0 10 +336790.2812 8.0000E+02 2.5128E+06 0 0 0 10 +385764.1875 4.6700E+03 2.4133E+07 0 0 0 9 +552241.8125 5.7000E+03 1.2989E+06 0 0 0 13 +566558.4375 3.0000E+03 7.0820E+07 0 0 0 10 +619664.6250 3.0000E+03 7.2596E+05 0 0 0 13 +649726.0000 3.0000E+03 1.0959E+06 0 0 0 13 +653064.6250 6.3000E+03 1.9386E+07 0 0 0 12 +715064.6250 3.0000E+02 9.7857E+05 0 0 0 10 +716771.3125 3.0000E+03 2.1930E+08 0 0 0 8 +802258.9375 3.0000E+03 9.9349E+06 0 0 0 15 +862003.6875 3.0000E+03 4.3293E+08 0 0 0 8 +872483.5000 3.0000E+02 1.7335E+07 0 0 0 10 +955891.2500 3.0000E+02 9.8289E+05 0 0 0 13 +1098425.500 3.0000E+03 5.7787E+04 0 0 0 15 +1113807.625 3.0000E+02 7.6533E+07 0 0 0 10 +1122279.500 3.0000E+02 4.8816E+06 0 0 0 13 +1178601.750 3.0000E+03 8.2959E+06 0 0 0 9 +1192267.500 3.0000E+02 3.7506E+05 0 0 0 12 +1207629.500 3.0000E+02 1.9795E+07 0 0 0 13 +1388859.125 3.0000E+03 4.2714E+06 0 0 0 15 +1769072.750 3.0000E+03 3.2136E+04 0 0 0 8 +2248487.000 3.0000E+03 1.6932E+05 0 0 0 8 +-1.1851E+06 1.1848E+05 2.6057E+08 0 0 0 18 +-1.6155E+05 6.5000E+02 4.2640E+05 0 0 0 18 +2235.000000 3.7000E+02 9.3266E+02 0 0 0 20 +4977.000000 6.0000E+02 1.1220E+03 0 0 0 19 +183488.8281 6.0000E+03 9.9976E+06 0 0 0 18 +235225.3906 8.0000E+02 1.1541E+05 0 0 0 21 +302839.2188 3.7000E+02 2.7443E+05 0 0 0 20 +413136.1875 6.0000E+02 1.5801E+06 0 0 0 19 +645239.8125 8.0000E+02 4.0143E+05 0 0 0 21 +704912.0000 8.0000E+02 4.2319E+05 0 0 0 21 +745454.0000 3.7000E+02 1.4735E+07 0 0 0 20 +796946.1250 6.0000E+02 4.6932E+05 0 0 0 19 +807379.8125 6.0000E+02 2.7433E+05 0 0 0 19 +810796.9375 6.0000E+02 4.1931E+05 0 0 0 19 +844674.6250 3.7000E+02 3.3153E+06 0 0 0 20 +878822.8125 8.0000E+02 1.1063E+05 0 0 0 21 +979821.3125 6.0000E+02 5.9182E+05 0 0 0 19 +1182175.750 6.0000E+03 5.9124E+06 0 0 0 18 +1217821.125 8.0000E+02 1.8889E+06 0 0 0 21 +1274871.500 6.0000E+02 2.2255E+06 0 0 0 19 +1302032.875 8.0000E+02 3.0479E+05 0 0 0 21 +1310774.875 3.7000E+02 3.3971E+05 0 0 0 20 +1337984.375 6.0000E+02 4.6240E+06 0 0 0 19 +1356024.750 3.7000E+02 1.2271E+07 0 0 0 20 +1383597.625 6.0000E+02 2.5336E+07 0 0 0 19 +1400981.375 8.0000E+02 1.8976E+06 0 0 0 21 +1412107.750 3.7000E+02 6.6862E+05 0 0 0 20 +1586007.000 6.0000E+03 2.3644E+07 0 0 0 18 +2583249.500 6.0000E+03 9.2076E+07 0 0 0 18 +-1.1592E+07 1.2000E+03 7.5174E+09 0 0 0 23 +-9.1926E+06 1.2000E+03 1.4356E+10 0 0 0 23 +433901.3750 1.2000E+03 4.3760E+07 0 0 0 25 +999736.9375 1.2000E+03 9.6583E+07 0 0 0 26 +1307683.875 1.2000E+03 4.2027E+07 0 0 0 25 +1630813.125 1.2000E+03 7.1367E+04 0 0 0 25 +1650581.000 1.2000E+03 3.9013E+06 0 0 0 29 +1833142.125 1.2000E+03 7.8352E+06 0 0 0 26 +1898468.875 1.2000E+03 2.8678E+07 0 0 0 24 +2372699.000 1.2000E+03 1.5533E+08 0 0 0 23 +2888249.750 1.2000E+03 1.8859E+06 0 0 0 24 +3004838.500 1.2000E+03 2.3338E+03 0 0 0 24 +3181814.500 1.2000E+03 5.1769E+08 0 0 0 24 +3203037.750 1.2000E+03 1.7923E+08 0 0 0 23 +3204505.000 1.2000E+03 3.1091E+05 0 0 0 28 +3239197.250 1.2000E+03 2.5141E+08 0 0 0 26 +3431828.250 1.2000E+03 6.1570E+05 0 0 0 27 +3438834.250 1.2000E+03 1.8039E+06 0 0 0 28 +3636796.000 1.2000E+03 7.0184E+08 0 0 0 25 +3763624.250 1.2000E+03 1.6031E+07 0 0 0 29 +3853751.500 1.2000E+03 5.6245E+08 0 0 0 23 +4175216.750 1.2000E+03 7.0220E+07 0 0 0 26 +4288830.500 1.2000E+03 5.6268E+07 0 0 0 25 +4303685.000 1.2000E+03 1.6078E+07 0 0 0 24 +4461693.000 1.2000E+03 7.7996E+07 0 0 0 23 +4525253.000 1.2000E+03 4.1733E+06 0 0 0 27 +4591673.500 1.2000E+03 9.1070E+04 0 0 0 29 +4592562.000 1.2000E+03 1.7527E+05 0 0 0 28 +4816678.000 1.2000E+03 4.7145E+07 0 0 0 25 +5055537.000 1.2000E+03 5.2134E+07 0 0 0 26 +5118596.000 1.2000E+03 1.7864E+07 0 0 0 29 +5365329.500 1.2000E+03 9.8062E+05 0 0 0 27 +5617318.000 1.2000E+03 3.3197E+07 0 0 0 28 +5666636.000 1.2000E+03 1.2687E+07 0 0 0 27 +5912201.500 1.2000E+03 1.2942E+07 0 0 0 29 +5986110.000 1.2000E+03 7.1061E+06 0 0 0 26 +6076846.000 1.2000E+03 5.1667E+06 0 0 0 28 +6116665.000 1.2000E+03 5.5499E+06 0 0 0 24 +6389893.000 1.2000E+03 4.9356E+06 0 0 0 29 +6813680.500 1.2000E+03 3.2629E+06 0 0 0 28 +6833136.500 1.2000E+03 5.1405E+06 0 0 0 27 +7373000.000 1.2000E+03 2.4000E+06 0 0 0 24 +8820920.000 1.2000E+03 1.1234E+10 0 0 0 25 +10934820.00 1.2000E+03 2.7611E+05 0 0 0 23 +15050371.00 1.2000E+03 6.3499E+05 0 0 0 23 +25004488.00 1.2000E+03 6.9186E+03 0 0 0 23 + + +RADIUS PARAMETERS FOLLOW + 4.13642 4.13642 0-1 1 4 5 + 4.94372 4.94372 0-1 2 3 6 7 + 4.40000 4.40000 0-1 8 91011121314151617 + 4.20000 4.20000 0-11819202122 + 4.20000 4.20000 0-123242526272829 + +ISOTOPIC MASSES AND ABUNDANCES FOLLOW + 27.976929 1.0000000 .9200 1 1 2 3 4 5 6 7 + 28.976496 .0467000 .0500 1 8 91011121314151617 + 29.973772 .0310000 .0200 11819202122 + 16.000000 1.0000000 .0100000 023242526272829 From 907d17d4c66e5aa8eea13a9b5e7a9aa80372341c Mon Sep 17 00:00:00 2001 From: "Alexander M. Long" Date: Tue, 18 Feb 2025 16:57:59 -0700 Subject: [PATCH 02/47] fixed bug with reading in isotope header for card 10 in the parfile --- src/pleiades/sammy/parameters/isotope.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pleiades/sammy/parameters/isotope.py b/src/pleiades/sammy/parameters/isotope.py index c0b48db..bfb656a 100644 --- a/src/pleiades/sammy/parameters/isotope.py +++ b/src/pleiades/sammy/parameters/isotope.py @@ -354,10 +354,9 @@ def is_header_line(cls, line: str) -> bool: line: Input line to check Returns: - bool: True if line matches any valid header format + bool: True if the first 5 characters of the line are 'ISOTO' """ - line_upper = line.strip().upper() - return any(header.upper() in line_upper for header in CARD_10_HEADERS) + return line.strip().upper().startswith("ISOTO") @classmethod def from_lines(cls, lines: List[str]) -> "IsotopeCard": From a01df6b7b8c90f095f37ed087271f63fbb19729a Mon Sep 17 00:00:00 2001 From: "Alexander M. Long" Date: Tue, 18 Feb 2025 16:58:41 -0700 Subject: [PATCH 03/47] added some print statements for errors from_string that prints the actual line you are reading --- src/pleiades/sammy/parfile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 9686cea..a831c9a 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -229,6 +229,9 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Try parsing as resonance table params["resonance"] = ResonanceCard.from_lines(group) except Exception as e: + print(f"Failed to parse card without header: {str(e)}") + print(f"Line: {group[0]}") + print(f"Lines: {group}") raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") return cls(**params) From 8fe77d56ad8a1e3f8fa830ff2dfb4cba4ac44248 Mon Sep 17 00:00:00 2001 From: "Alexander M. Long" Date: Tue, 18 Feb 2025 17:28:03 -0700 Subject: [PATCH 04/47] cut out print statements I added into wrong error message. --- src/pleiades/sammy/parfile.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index a831c9a..78dfe6b 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -230,8 +230,6 @@ def from_string(cls, content: str) -> "SammyParameterFile": params["resonance"] = ResonanceCard.from_lines(group) except Exception as e: print(f"Failed to parse card without header: {str(e)}") - print(f"Line: {group[0]}") - print(f"Lines: {group}") raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") return cls(**params) From 3f3fd8cb0fe4e1e51fb0a909e40a4dd88f5fec87 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 09:06:30 -0700 Subject: [PATCH 05/47] added repo map (not complete) --- docs/repo.map | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/repo.map diff --git a/docs/repo.map b/docs/repo.map new file mode 100644 index 0000000..133e15a --- /dev/null +++ b/docs/repo.map @@ -0,0 +1,78 @@ +PLEIADES +├── LICENSE +├── pleiades.egg-info +├── scripts +├── pleiades +│ ├── __init__.py +│ ├── __main__.py +│ ├── utils +│ ├── __pycache__ +│ ├── core +│ │ ├── transmission.py +│ │ ├── constants.py +│ │ ├── __init__.py +│ │ ├── models.py +│ │ ├── data_manager.py +│ │ └── __pycache__ +│ ├── sammy +│ │ ├── interface.py +│ │ ├── config.py +│ │ ├── __init__.py +│ │ ├── factory.py +│ │ ├── parfile.py +│ │ ├── backends +│ │ │ ├── nova_ornl.py +│ │ │ ├── __init__.py +│ │ │ ├── local.py +│ │ │ ├── docker.py +│ │ │ └── __pycache__ +│ │ ├── parameters +│ │ │ ├── unused_var.py +│ │ │ ├── paramagnetic.py +│ │ │ ├── orres.py +│ │ │ ├── normalization.py +│ │ │ ├── misc.py +│ │ │ ├── helper.py +│ │ │ ├── external_r.py +│ │ │ ├── data_reduction.py +│ │ │ ├── user_resolution.py +│ │ │ ├── resonance.py +│ │ │ ├── resolution.py +│ │ │ ├── radius.py +│ │ │ ├── last.py +│ │ │ ├── det_efficiency.py +│ │ │ ├── broadening.py +│ │ │ ├── background.py +│ │ │ ├── __init__.py +│ │ │ ├── isotope.py +│ │ │ └── __pycache__ +│ │ └── __pycache__ +│ └── data +├── examples +├── environment.yaml +├── docs +│ └── repo.map +├── .readthedocs.yaml +├── .gitignore +├── .github +├── legacy +├── src +│ └── pleiades +├── pyproject.toml +├── poetry.lock +├── paper +├── tests +│ ├── unit +│ │ ├── pleiades +│ │ ├── conftest.py +│ │ └── __pycache__ +│ ├── data +│ │ ├── config +│ │ ├── ex012 +│ │ └── __pycache__ +│ └── __pycache__ +├── README.rst +├── .pre-commit-config.yaml +├── notebook +├── .git +└── .. \ No newline at end of file From be1d4f87bb41a053759597b44af7af624e3c098d Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 09:14:32 -0700 Subject: [PATCH 06/47] adding notebooks. --- docs/repo.map | 11 +++++++++++ notebook/samexm/ex012/ex012a.ipynb | 0 notebook/samexm/ex027/ex027.ipynb | 0 3 files changed, 11 insertions(+) create mode 100644 notebook/samexm/ex012/ex012a.ipynb create mode 100644 notebook/samexm/ex027/ex027.ipynb diff --git a/docs/repo.map b/docs/repo.map index 133e15a..ab4aae4 100644 --- a/docs/repo.map +++ b/docs/repo.map @@ -74,5 +74,16 @@ PLEIADES ├── README.rst ├── .pre-commit-config.yaml ├── notebook +│ ├── sammy.param +│ │ ├── example.ipynb +│ │ └── __pycache__ +│ ├── samexm +│ │ ├── ex012 +│ │ │ ├── ex012.ipynb +│ │ │ ├── ex012a.par +│ │ ├── ex027 +│ │ │ ├── ex027a.endf +│ │ └── __pycache__ +│ └── __pycache__ ├── .git └── .. \ No newline at end of file diff --git a/notebook/samexm/ex012/ex012a.ipynb b/notebook/samexm/ex012/ex012a.ipynb new file mode 100644 index 0000000..e69de29 diff --git a/notebook/samexm/ex027/ex027.ipynb b/notebook/samexm/ex027/ex027.ipynb new file mode 100644 index 0000000..e69de29 From b06009884418e139d5b463845fc61a73aad76e50 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 09:15:58 -0700 Subject: [PATCH 07/47] deleted notebook --- notebook/samexm/ex012/ex012.ipynb | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 notebook/samexm/ex012/ex012.ipynb diff --git a/notebook/samexm/ex012/ex012.ipynb b/notebook/samexm/ex012/ex012.ipynb deleted file mode 100644 index e69de29..0000000 From 2fb392db5167c30e620d75e65d4cfe81cd6b5341 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 15:33:46 -0700 Subject: [PATCH 08/47] made fudge card optional --- src/pleiades/sammy/parfile.py | 79 ++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 78dfe6b..4e6d474 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -80,9 +80,8 @@ class SammyParameterFile(BaseModel): combinations of cards based on the analysis needs. """ - # REQUIRED CARDS - fudge: float = Field(default=0.1, description="Fudge factor for initial uncertainties", ge=0.0, le=1.0) - # OPTIONAL CARDS + # CARDS for parameter file object + fudge: Optional[float] = Field(None, description="Fudge factor for initial uncertainties", ge=0.0, le=1.0) resonance: Optional[ResonanceCard] = Field(None, description="Resonance parameters") external_r: Optional[ExternalREntry] = Field(None, description="External R matrix parameters") broadening: Optional[BroadeningParameterCard] = Field(None, description="Broadening parameters") @@ -93,7 +92,6 @@ class SammyParameterFile(BaseModel): orres: Optional[ORRESCard] = Field(None, description="ORRES card parameters") paramagnetic: Optional[ParamagneticParameters] = Field(None, description="Paramagnetic parameters") user_resolution: Optional[UserResolutionParameters] = Field(None, description="User-defined resolution function parameters") - # TODO: Need to verify by Sammy experts on whether the following are mandatory or optional isotope: Optional[IsotopeCard] = Field(None, description="Isotope parameters") def to_string(self) -> str: @@ -178,22 +176,6 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Initialize parameters params = {} - # First parse out fudge factor if it exists - fudge_idx = None - for i, line in enumerate(lines): - stripped = line.strip() - if stripped: # Skip empty lines - try: - params["fudge"] = float(stripped) - fudge_idx = i - break - except ValueError: - continue - # now remove the fudge factor line from the list to simplify grouping - # lines into cards - if fudge_idx is not None: - del lines[fudge_idx] - # Second, partition lines into group of lines based on blank lines card_groups = [] current_group = [] @@ -202,16 +184,19 @@ def from_string(cls, content: str) -> "SammyParameterFile": if line.strip(): current_group.append(line) else: - if current_group: # Only add non-empty groups + # Only add non-empty groups + if current_group: card_groups.append(current_group) current_group = [] - if current_group: # Don't forget last group - card_groups.append(current_group) + # Don't forget last group + if current_group: card_groups.append(current_group) # Process each group of lines for group in card_groups: - if not group: # Skip empty groups + + # Skip empty groups + if not group: continue # Check first line for header to determine card type @@ -222,15 +207,23 @@ def from_string(cls, content: str) -> "SammyParameterFile": try: params[CardOrder.get_field_name(card_type)] = card_class.from_lines(group) except Exception as e: - raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}") + raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") else: - # No header - check if it's a resonance table - try: - # Try parsing as resonance table - params["resonance"] = ResonanceCard.from_lines(group) - except Exception as e: - print(f"Failed to parse card without header: {str(e)}") - raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") + + # check if group if fudge factor + if len(group) == 1: + try: + params["fudge"] = float(group[0]) + except ValueError as e: + raise ValueError(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") + else: + #check if it's a resonance table + try: + # Try parsing as resonance table + params["resonance"] = ResonanceCard.from_lines(group) + except Exception as e: + print(f"Failed to parse card without header: {str(e)}") + raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") return cls(**params) @@ -247,7 +240,7 @@ def _parse_card(cls, card_type: CardOrder, lines: List[str]): print(card_type) print(lines) # Convert any parsing error into ValueError with context - raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}") from e + raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\n") from e @classmethod def _get_card_class(cls, card_type: CardOrder): @@ -291,7 +284,7 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": except UnicodeDecodeError as e: raise ValueError(f"Failed to read parameter file - invalid encoding: {e}") except Exception as e: - raise ValueError(f"Failed to parse parameter file: {e}") + raise ValueError(f"Failed to parse parameter file: {filepath}\n {e}") def to_file(self, filepath: Union[str, pathlib.Path]) -> None: """Write parameter file to disk. @@ -316,6 +309,24 @@ def to_file(self, filepath: Union[str, pathlib.Path]) -> None: except Exception as e: raise ValueError(f"Failed to format parameter file content: {e}") + def print_parameters(self) -> None: + """Print the details of the parameter file.""" + print("Sammy Parameter File Details:") + + # check if any cards are present + if all(value is None for value in self.dict().values()): + print("No cards present in the parameter file.") + return + else: + for card_type in CardOrder: + field_name = CardOrder.get_field_name(card_type) + value = getattr(self, field_name) + if value is not None: + print(f"{field_name}:") + if card_type == CardOrder.FUDGE: + print(f" Fudge factor: {value}") + else: + print(value) if __name__ == "__main__": print("TODO: usage example for SAMMY parameter file handling") From 7cca3dd2e9f48bd5ba3dcfab4e74e6e316cb038e Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 15:33:58 -0700 Subject: [PATCH 09/47] updated repo.map --- docs/repo.map | 154 +++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 83 deletions(-) diff --git a/docs/repo.map b/docs/repo.map index ab4aae4..f8184e8 100644 --- a/docs/repo.map +++ b/docs/repo.map @@ -1,89 +1,77 @@ PLEIADES -├── LICENSE -├── pleiades.egg-info -├── scripts -├── pleiades -│ ├── __init__.py -│ ├── __main__.py -│ ├── utils -│ ├── __pycache__ -│ ├── core -│ │ ├── transmission.py -│ │ ├── constants.py -│ │ ├── __init__.py -│ │ ├── models.py -│ │ ├── data_manager.py -│ │ └── __pycache__ -│ ├── sammy -│ │ ├── interface.py -│ │ ├── config.py -│ │ ├── __init__.py -│ │ ├── factory.py -│ │ ├── parfile.py -│ │ ├── backends -│ │ │ ├── nova_ornl.py -│ │ │ ├── __init__.py -│ │ │ ├── local.py -│ │ │ ├── docker.py -│ │ │ └── __pycache__ -│ │ ├── parameters -│ │ │ ├── unused_var.py -│ │ │ ├── paramagnetic.py -│ │ │ ├── orres.py -│ │ │ ├── normalization.py -│ │ │ ├── misc.py -│ │ │ ├── helper.py -│ │ │ ├── external_r.py -│ │ │ ├── data_reduction.py -│ │ │ ├── user_resolution.py -│ │ │ ├── resonance.py -│ │ │ ├── resolution.py -│ │ │ ├── radius.py -│ │ │ ├── last.py -│ │ │ ├── det_efficiency.py -│ │ │ ├── broadening.py -│ │ │ ├── background.py -│ │ │ ├── __init__.py -│ │ │ ├── isotope.py -│ │ │ └── __pycache__ -│ │ └── __pycache__ -│ └── data -├── examples -├── environment.yaml -├── docs -│ └── repo.map ├── .readthedocs.yaml ├── .gitignore -├── .github -├── legacy -├── src -│ └── pleiades +├── .pre-commit-config.yaml ├── pyproject.toml ├── poetry.lock -├── paper -├── tests -│ ├── unit -│ │ ├── pleiades -│ │ ├── conftest.py -│ │ └── __pycache__ -│ ├── data -│ │ ├── config -│ │ ├── ex012 -│ │ └── __pycache__ -│ └── __pycache__ +├── LICENSE +├── environment.yaml +├── pleiades.egg-info ├── README.rst -├── .pre-commit-config.yaml -├── notebook -│ ├── sammy.param -│ │ ├── example.ipynb -│ │ └── __pycache__ -│ ├── samexm -│ │ ├── ex012 -│ │ │ ├── ex012.ipynb -│ │ │ ├── ex012a.par -│ │ ├── ex027 -│ │ │ ├── ex027a.endf -│ │ └── __pycache__ -│ └── __pycache__ -├── .git -└── .. \ No newline at end of file +├── src/ +│ └── pleiades +| ├── __init__.py +| ├── __main__.py +| ├── utils/ +| ├── data/ +| | ├── cross-sections/ +| | ├── isotopes/ +| | └── resonances/ +| ├── core/ +| │ ├── transmission.py +| │ ├── constants.py +| │ ├── __init__.py +| │ ├── models.py +| │ └── data_manager.py +| └── sammy/ +| ├── interface.py +| ├── config.py +| ├── __init__.py +| ├── factory.py +| ├── parfile.py +| ├── backends/ +| │ ├── nova_ornl.py +| │ ├── __init__.py +| │ ├── local.py +| │ └── docker.py +| └── parameters/ +| ├── unused_var.py +| ├── paramagnetic.py +| ├── orres.py +| ├── normalization.py +| ├── misc.py +| ├── helper.py +| ├── external_r.py +| ├── data_reduction.py +| ├── user_resolution.py +| ├── resonance.py +| ├── resolution.py +| ├── radius.py +| ├── last.py +| ├── det_efficiency.py +| ├── broadening.py +| ├── background.py +| ├── __init__.py +| └── isotope.py +├── examples/ +├── scripts/ +├── docs/ +│ └── repo.map +├── legacy/ +├── paper/ +├── tests/ +│ ├── unit/ +│ │ ├── pleiades +│ │ └── conftest.py +│ └── data/ +│ ├── config +│ └── ex012 +└── notebook/ + ├── sammy.param/ + │ └── example.ipynb + └── samexm/ + ├── ex012/ + │ ├── ex012.ipynb + │ └── ex012a.par + └── ex027/ + └── ex027a.endf \ No newline at end of file From 21fc928b07b7970d39c9355c5cdbd2ed72b276db Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 15:35:03 -0700 Subject: [PATCH 10/47] got rid off le=1.0 for abundances in Isotope class. --- src/pleiades/core/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pleiades/core/models.py b/src/pleiades/core/models.py index 1591ed2..fb24aa1 100644 --- a/src/pleiades/core/models.py +++ b/src/pleiades/core/models.py @@ -178,7 +178,7 @@ class Isotope(BaseModel): atomic_mass: Mass thickness: NonNegativeFloat thickness_unit: str = Field(pattern=r"^(cm|mm|atoms/cm2)$") - abundance: float = Field(ge=0.0, le=1.0) + abundance: float = Field(ge=0.0) density: NonNegativeFloat density_unit: str = Field(default="g/cm3", pattern=r"^g/cm3$") From 124fb1dedfcb6890f97b01f790fd18d827330847 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 15:36:13 -0700 Subject: [PATCH 11/47] modified isotope.py to not use extended format and git rid of validate_abundance. --- src/pleiades/sammy/parameters/isotope.py | 82 ++++++++++-------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/src/pleiades/sammy/parameters/isotope.py b/src/pleiades/sammy/parameters/isotope.py index bfb656a..67917fc 100644 --- a/src/pleiades/sammy/parameters/isotope.py +++ b/src/pleiades/sammy/parameters/isotope.py @@ -33,6 +33,8 @@ 41-45 I IGRISO Second spin group number ...etc for up to 9 groups per line +NOTE: the extended format is currently not supported with pleiades. + Continuation lines are indicated by "-1" in columns 79-80 (standard format) or column 80 (extended format). These lines contain only additional spin group numbers in the same format as the parent line. @@ -54,10 +56,19 @@ # Format definitions for standard format (<99 spin groups) # Each numeric field has specific width requirements FORMAT_STANDARD = { - "mass": slice(0, 10), # AMUISO: Atomic mass (amu) - "abundance": slice(10, 20), # PARISO: Fractional abundance - "uncertainty": slice(20, 30), # DELISO: Uncertainty on abundance - "flag": slice(30, 32), # IFLISO: Treatment flag + "mass": slice(0, 10), # AMUISO: Atomic mass (amu) + "abundance": slice(10, 20), # PARISO: Fractional abundance + "uncertainty": slice(20, 30), # DELISO: Uncertainty on abundance + "flag": slice(30, 32), # IFLISO: Treatment flag +} + +# Spin group number positions +# Standard format: 2 columns per group starting at col 33 +SPIN_GROUP_STANDARD = { + "width": 2, # Character width of each group number + "start": 32, # Start of first group + "per_line": 24, # Max groups per line + "cont_marker": slice(78, 80), # "-1" indicates continuation } # Format for extended format (>99 spin groups) @@ -68,16 +79,6 @@ "flag": slice(30, 35), # IFLISO: Treatment flag } -# Spin group number positions -# Standard format: 2 columns per group starting at col 33 -# Extended format: 5 columns per group starting at col 36 -SPIN_GROUP_STANDARD = { - "width": 2, - "start": 32, - "per_line": 24, # Max groups per line - "cont_marker": slice(78, 80), # "-1" indicates continuation -} - SPIN_GROUP_EXTENDED = { "width": 5, "start": 35, @@ -177,8 +178,9 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet if not lines or not lines[0].strip(): raise ValueError("No valid parameter line provided") - # Parse main parameters from first line - format_dict = FORMAT_EXTENDED if extended else FORMAT_STANDARD + # Set format to standard. + format_dict = FORMAT_STANDARD # NOTE: EXTENDED format is currently not supported. + main_line = f"{lines[0]:<80}" # Pad to full width params = {} @@ -195,6 +197,7 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet # Parse flag flag_str = main_line[format_dict["flag"]].strip() or "0" + try: params["flag"] = VaryFlag(int(flag_str)) except (ValueError, TypeError): @@ -202,7 +205,7 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet # Parse spin groups spin_groups = [] - group_format = SPIN_GROUP_EXTENDED if extended else SPIN_GROUP_STANDARD + group_format = SPIN_GROUP_STANDARD #NOTE: EXTENDED format is currently not supported. # Helper function to parse groups from a line def parse_groups(line: str, start_pos: int = None, continuation: bool = False) -> List[int]: @@ -216,6 +219,7 @@ def parse_groups(line: str, start_pos: int = None, continuation: bool = False) - Returns: List of parsed group numbers """ + groups = [] pos = start_pos if start_pos is not None else group_format["start"] @@ -233,6 +237,7 @@ def parse_groups(line: str, start_pos: int = None, continuation: bool = False) - if group is not None: groups.append(group) pos += width + return groups # Parse groups from first line @@ -246,6 +251,7 @@ def parse_groups(line: str, start_pos: int = None, continuation: bool = False) - spin_groups.extend(parse_groups(line, start_pos=0, continuation=True)) params["spin_groups"] = spin_groups + return cls(**params) def to_lines(self, extended: bool = False) -> List[str]: @@ -318,6 +324,9 @@ class IsotopeCard(BaseModel): isotopes (List[IsotopeParameters]): List of isotope parameter sets extended (bool): Whether to use extended format for >99 spin groups + NOTE: Fixed formats for both standard and extended are defined in the IsotopeParameters class. + + Example: >>> lines = [ ... "ISOTOpic abundances and masses", @@ -331,21 +340,6 @@ class IsotopeCard(BaseModel): isotopes: List[IsotopeParameters] = Field(default_factory=list) extended: bool = Field(default=False, description="Use extended format for >99 spin groups") - @model_validator(mode="after") - def validate_abundances(self) -> "IsotopeCard": - """Validate that total abundance doesn't exceed 1.0. - - Returns: - IsotopeCard: Self if validation passes - - Raises: - ValueError: If total abundance > 1.0 - """ - total = sum(iso.abundance for iso in self.isotopes) - if total > 1.0: - raise ValueError(f"Total abundance {total} exceeds 1.0") - return self - @classmethod def is_header_line(cls, line: str) -> bool: """Check if line is a valid header line. @@ -385,28 +379,22 @@ def from_lines(cls, lines: List[str]) -> "IsotopeCard": # Check if we need extended format extended = False - for line in content_lines: - # Look for any spin group number > 99 in the data - numbers = [int(n) for n in line.split() if n.strip().isdigit()] - if any(abs(n) > 99 for n in numbers): - extended = True - break # Parse isotopes isotopes = [] current_lines = [] for line in content_lines: - # If line starts with a number, it's a new isotope - if line.strip() and line[0].isdigit(): - if current_lines: - isotopes.append(IsotopeParameters.from_lines(current_lines, extended=extended)) - current_lines = [] - current_lines.append(line) - # Don't forget the last isotope - if current_lines: - isotopes.append(IsotopeParameters.from_lines(current_lines, extended=extended)) + current_lines.append(line) + + # check if characters 79-80 is a "-1". this means that there are more spin groups in the next line. + if line[78:80] == "-1": continue + + # Otherwise the are no more lines for spin groups, so process the current lines. + else: + isotopes.append(IsotopeParameters.from_lines(current_lines, extended=extended)) + current_lines = [] return cls(isotopes=isotopes, extended=extended) From 472889edbad007dbdd50680c3ae17bcc4c9a6528 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 15:36:42 -0700 Subject: [PATCH 12/47] added a fudge factor line --- notebook/samexm/ex012/ex012a.par | 1 + 1 file changed, 1 insertion(+) diff --git a/notebook/samexm/ex012/ex012a.par b/notebook/samexm/ex012/ex012a.par index 0c3fd15..c60437d 100755 --- a/notebook/samexm/ex012/ex012a.par +++ b/notebook/samexm/ex012/ex012a.par @@ -161,6 +161,7 @@ 15050371.00 1.2000E+03 6.3499E+05 0 0 0 23 25004488.00 1.2000E+03 6.9186E+03 0 0 0 23 +0.1 RADIUS PARAMETERS FOLLOW 4.13642 4.13642 0-1 1 4 5 From 1b9d628db34c824bcee6a61d09476d26931b4a3a Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 15:56:42 -0700 Subject: [PATCH 13/47] modified print_parameters function. --- src/pleiades/sammy/parfile.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 4e6d474..6c19572 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -323,10 +323,11 @@ def print_parameters(self) -> None: value = getattr(self, field_name) if value is not None: print(f"{field_name}:") - if card_type == CardOrder.FUDGE: - print(f" Fudge factor: {value}") + if isinstance(value, list): + for index, item in enumerate(value): + print(f" {field_name}[{index}]: {item}") else: - print(value) + print(f" {value}") if __name__ == "__main__": print("TODO: usage example for SAMMY parameter file handling") From f0c01b10f6d5f37251514ad5d28eb0c429ccc10b Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Wed, 19 Feb 2025 21:13:57 -0700 Subject: [PATCH 14/47] modified notebook ex012a.ipynb --- notebook/samexm/ex012/ex012a.ipynb | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/notebook/samexm/ex012/ex012a.ipynb b/notebook/samexm/ex012/ex012a.ipynb index e69de29..c003d48 100644 --- a/notebook/samexm/ex012/ex012a.ipynb +++ b/notebook/samexm/ex012/ex012a.ipynb @@ -0,0 +1,57 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Example # 12 from sammy code: \n", + "In a real experiment, the sample usually contains more than one nuclide. There may be several isotopes of the same element, the sample may be an oxide or other chemical compound, or there may be contaminants. The treatment for all of these is the same: Include spin groups and resonance parameters for any and all nuclides. Assign the appropriate abundances for each nuclide; search for the correct values of the abundances if needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib # Importing pathlib to work with file system paths\n", + "import tempfile # Importing tempfile to create temporary files and directories\n", + "\n", + "# Importing specific classes from the pleiades.sammy module\n", + "from pleiades.sammy.parfile import SammyParameterFile\n", + "\n", + "# reading in SAMMY parameters from a parameter file \"./ex012a.par\"\n", + "sammy_params = SammyParameterFile.from_file(\"./ex012a.par\")\n", + "sammy_params.print_parameters()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pleiades", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From c67acaba8a3da4c315ae1dda9864a5058f577bb7 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 12:36:34 -0700 Subject: [PATCH 15/47] modified notebook --- notebook/samexm/ex012/ex012a.ipynb | 39 ++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/notebook/samexm/ex012/ex012a.ipynb b/notebook/samexm/ex012/ex012a.ipynb index c003d48..9b6d7cf 100644 --- a/notebook/samexm/ex012/ex012a.ipynb +++ b/notebook/samexm/ex012/ex012a.ipynb @@ -4,13 +4,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Example # 12 from sammy code: \n", - "In a real experiment, the sample usually contains more than one nuclide. There may be several isotopes of the same element, the sample may be an oxide or other chemical compound, or there may be contaminants. The treatment for all of these is the same: Include spin groups and resonance parameters for any and all nuclides. Assign the appropriate abundances for each nuclide; search for the correct values of the abundances if needed." + "This notebook is based on example 12 from the SAMMY repo. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -18,13 +17,43 @@ "import tempfile # Importing tempfile to create temporary files and directories\n", "\n", "# Importing specific classes from the pleiades.sammy module\n", - "from pleiades.sammy.parfile import SammyParameterFile\n", - "\n", + "from pleiades.sammy.parfile import SammyParameterFile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we want to load SAMMY parameters based on a given parameter file. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# reading in SAMMY parameters from a parameter file \"./ex012a.par\"\n", "sammy_params = SammyParameterFile.from_file(\"./ex012a.par\")\n", "sammy_params.print_parameters()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write the parameters to an output file \"ex012b.par\". " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "sammy_params.to_file(\"./ex012b.par\")" + ] + }, { "cell_type": "code", "execution_count": null, From e4fa46c6ed22f6910c7bf191446b26c46c1fea7c Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 12:39:40 -0700 Subject: [PATCH 16/47] adding logger for debugging issues --- src/pleiades/utils/logger.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/pleiades/utils/logger.py diff --git a/src/pleiades/utils/logger.py b/src/pleiades/utils/logger.py new file mode 100644 index 0000000..6ac4d02 --- /dev/null +++ b/src/pleiades/utils/logger.py @@ -0,0 +1,25 @@ +import logging + +class Logger: + def __init__(self, name: str, level: int = logging.INFO): + self.logger = logging.getLogger(name) + self.logger.setLevel(level) + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + self.logger.addHandler(handler) + + def debug(self, message: str): + self.logger.debug(message) + + def info(self, message: str): + self.logger.info(message) + + def warning(self, message: str): + self.logger.warning(message) + + def error(self, message: str): + self.logger.error(message) + + def critical(self, message: str): + self.logger.critical(message) \ No newline at end of file From 359ab09773b305efd884ea578eee90b82a583833 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 12:44:13 -0700 Subject: [PATCH 17/47] adding os and logger imports to parfile.py --- src/pleiades/sammy/parfile.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 6c19572..35de88c 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """Top level parameter file handler for SAMMY.""" -import pathlib +import pathlib, os from enum import Enum, auto from typing import List, Optional, Union @@ -22,6 +22,13 @@ UserResolutionParameters, ) +# +from pleiades.utils.logging import Logger + +# Initialize logger with file logging +log_file_path = os.path.join(os.getcwd(), 'pleiades.log') +logger = Logger(__name__, log_file=log_file_path) + class CardOrder(Enum): """Defines the standard order of cards in SAMMY parameter files. @@ -152,6 +159,9 @@ def _get_card_class_with_header(cls, line: str): for card_type, card_class in card_checks: if hasattr(card_class, "is_header_line") and card_class.is_header_line(line): + print("parfile.py - SammyParameterFile - _get_card_class_with_header() ================") + print(f"card_type:{card_type}\t card_class:{card_class}") + print("==============================================================================") return card_type, card_class return None, None From d80ef6b0d8d4995eb9cccaeddd83f3791efe598f Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 14:07:56 -0700 Subject: [PATCH 18/47] moved _log_and_raise_error() to logger.py --- src/pleiades/utils/logger.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/pleiades/utils/logger.py b/src/pleiades/utils/logger.py index 6ac4d02..f7fe0d7 100644 --- a/src/pleiades/utils/logger.py +++ b/src/pleiades/utils/logger.py @@ -1,13 +1,25 @@ import logging +def _log_and_raise_error(logger, message: str, exception_class: Exception): + logger.error(message) + raise exception_class(message) + class Logger: - def __init__(self, name: str, level: int = logging.INFO): + def __init__(self, name: str, level: int = logging.DEBUG, log_file: str = None): + + # self.logger = logging.getLogger(name) self.logger.setLevel(level) - handler = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - handler.setFormatter(formatter) - self.logger.addHandler(handler) + + # Create file handler if log_file is specified + if log_file: + file_handler = logging.FileHandler(log_file) + file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(file_formatter) + self.logger.addHandler(file_handler) + + # Log a break line + self.logger.info("logging initialized...") def debug(self, message: str): self.logger.debug(message) @@ -22,4 +34,6 @@ def error(self, message: str): self.logger.error(message) def critical(self, message: str): - self.logger.critical(message) \ No newline at end of file + self.logger.critical(message) + + \ No newline at end of file From 260c4e3d5d396f24a11a5e719c42f7d7d4d4265c Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 14:08:13 -0700 Subject: [PATCH 19/47] implemented logging in parfile.py for most functions. --- src/pleiades/sammy/parfile.py | 60 ++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 35de88c..28a21cc 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -22,14 +22,12 @@ UserResolutionParameters, ) -# -from pleiades.utils.logging import Logger +from pleiades.utils.logger import Logger, _log_and_raise_error # Initialize logger with file logging -log_file_path = os.path.join(os.getcwd(), 'pleiades.log') +log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') logger = Logger(__name__, log_file=log_file_path) - class CardOrder(Enum): """Defines the standard order of cards in SAMMY parameter files. @@ -110,6 +108,8 @@ def to_string(self) -> str: The output follows the standard card order from Table VI B.2. Each card is separated by appropriate blank lines. """ + + logger.info("SammyParameterFile.to_string(): Attempting to convert parameter file to string format") lines = [] # Process each card type in standard order @@ -130,9 +130,12 @@ def to_string(self) -> str: card_lines = value.to_lines() if card_lines: # Only add non-empty line lists lines.extend(card_lines) + logger.debug(f"SammyParameterFile.to_string(): Added lines for {card_type.name} card") # Join all lines with newlines - return "\n".join(lines) + result = "\n".join(lines) + logger.info("SammyParameterFile.to_string(): Successfully converted parameter file to string format") + return result @classmethod def _get_card_class_with_header(cls, line: str): @@ -159,11 +162,10 @@ def _get_card_class_with_header(cls, line: str): for card_type, card_class in card_checks: if hasattr(card_class, "is_header_line") and card_class.is_header_line(line): - print("parfile.py - SammyParameterFile - _get_card_class_with_header() ================") - print(f"card_type:{card_type}\t card_class:{card_class}") - print("==============================================================================") + logger.debug(f"SammyParameterFile._get_card_class_with_header(): card_type:{card_type}\t card_class:{card_class}") return card_type, card_class + logger.info(f"SammyParameterFile._get_card_class_with_header(): No matches found for {line}") return None, None @classmethod @@ -181,7 +183,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Early exit for empty content if not lines: - raise ValueError("Empty parameter file content") + _log_and_raise_error("Empty parameter file content", ValueError) # Initialize parameters params = {} @@ -216,7 +218,9 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Process card with header try: params[CardOrder.get_field_name(card_type)] = card_class.from_lines(group) + logger.info(f"SammyParameterFile.from_string(): Successfully parsed {card_type.name} card") except Exception as e: + logger.error(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") else: @@ -224,31 +228,39 @@ def from_string(cls, content: str) -> "SammyParameterFile": if len(group) == 1: try: params["fudge"] = float(group[0]) + logger.info("SammyParameterFile.from_string(): Successfully parsed fudge factor") except ValueError as e: + logger.error(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") else: #check if it's a resonance table try: # Try parsing as resonance table params["resonance"] = ResonanceCard.from_lines(group) + logger.info("SammyParameterFile.from_string(): Successfully parsed resonance table") except Exception as e: - print(f"Failed to parse card without header: {str(e)}") + logger.error(f"Failed to parse card without header: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") + logger.info("SammyParameterFile.from_string(): Successfully parsed all parameter file content from string") return cls(**params) @classmethod def _parse_card(cls, card_type: CardOrder, lines: List[str]): """Parse a card's lines into the appropriate object.""" + logger.info(f"SammyParameterFile._parse_card(): Attempting to parse card of type: {card_type.name}") + card_class = cls._get_card_class(card_type) + if not card_class: - raise ValueError(f"No parser implemented for card type: {card_type}") + _log_and_raise_error(f"No parser implemented for card type: {card_type}", ValueError) try: - return card_class.from_lines(lines) + parsed_card = card_class.from_lines(lines) + logger.info(f"Successfully parsed card of type: {card_type.name}") + return parsed_card except Exception as e: - print(card_type) - print(lines) + logger.error(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {lines}") # Convert any parsing error into ValueError with context raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\n") from e @@ -285,16 +297,20 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": ValueError: If file content is invalid """ filepath = pathlib.Path(filepath) + logger.info(f"SammyParameterFile.from_file(): Attempting to read parameter file from: {filepath}") + if not filepath.exists(): - raise FileNotFoundError(f"Parameter file not found: {filepath}") + _log_and_raise_error(f"Parameter file not found: {filepath}", FileNotFoundError) try: content = filepath.read_text() + logger.info(f"SammyParameterFile.from_file(): Successfully read content in file: {filepath}") return cls.from_string(content) + except UnicodeDecodeError as e: - raise ValueError(f"Failed to read parameter file - invalid encoding: {e}") + _log_and_raise_error(f"Failed to read parameter file - invalid encoding: {e}", ValueError) except Exception as e: - raise ValueError(f"Failed to parse parameter file: {filepath}\n {e}") + _log_and_raise_error(f"Failed to parse parameter file: {filepath}\n {e}", ValueError) def to_file(self, filepath: Union[str, pathlib.Path]) -> None: """Write parameter file to disk. @@ -307,6 +323,7 @@ def to_file(self, filepath: Union[str, pathlib.Path]) -> None: ValueError: If content cannot be formatted """ filepath = pathlib.Path(filepath) + logger.info(f"SammyParameterFile.to_file(): Attempting to write parameter file to: {filepath}") # Create parent directories if they don't exist filepath.parent.mkdir(parents=True, exist_ok=True) @@ -314,13 +331,18 @@ def to_file(self, filepath: Union[str, pathlib.Path]) -> None: try: content = self.to_string() filepath.write_text(content) + logger.info(f"SammyParameterFile.to_file(): Successfully wrote parameter file to: {filepath}") + except OSError as e: - raise OSError(f"Failed to write parameter file: {e}") + _log_and_raise_error(f"Failed to write parameter file: {e}", OSError) except Exception as e: - raise ValueError(f"Failed to format parameter file content: {e}") + _log_and_raise_error(f"Failed to format parameter file content: {e}", ValueError) def print_parameters(self) -> None: """Print the details of the parameter file.""" + + logger.info("SammyParameterFile.print_parameters(): Printing out Sammy Parameter Detials") + print("Sammy Parameter File Details:") # check if any cards are present From dc780beb5f34396b863e1555f2325bf10725d5e3 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 15:08:56 -0700 Subject: [PATCH 20/47] adding more logging functionality. --- src/pleiades/sammy/parameters/radius.py | 136 ++++++++++++++++++------ 1 file changed, 104 insertions(+), 32 deletions(-) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 62fa8d6..9b89731 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -5,14 +5,18 @@ for radius parameters in SAMMY parameter files. """ -import re +import re, os from enum import Enum from typing import List, Optional, Tuple, Union from pydantic import BaseModel, Field, field_validator, model_validator from pleiades.sammy.parameters.helper import VaryFlag, format_float, format_vary, parse_keyword_pairs_to_dict, safe_parse +from pleiades.utils.logger import Logger, _log_and_raise_error, get_current_class_and_function +# Initialize logger with file logging +log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') +logger = Logger(__name__, log_file=log_file_path) class OrbitalMomentum(str, Enum): """Valid values for orbital angular momentum specification.""" @@ -203,6 +207,7 @@ def validate_true_radius_consistency(self) -> "RadiusParameters": CARD_7_HEADER = "RADIUs parameters follow" + class RadiusCardDefault(BaseModel): """Handler for default format radius parameter cards (Card Set 7). @@ -231,6 +236,7 @@ def is_header_line(cls, line: str) -> bool: Returns: bool: True if line is a valid header """ + #logger.debug(f"{get_current_class_and_function()}: Checking if header line - {line}") return line.strip().upper().startswith("RADIU") @staticmethod @@ -309,16 +315,21 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": Raises: ValueError: If lines are invalid or required data is missing """ + + #logger.info(f"{get_current_class_and_function()}: Parsing radius parameters from lines") if not lines: + logger.error(f"{get_current_class_and_function()}: No lines provided") raise ValueError("No lines provided") # Validate header if not cls.is_header_line(lines[0]): + logger.error(f"{get_current_class_and_function()}: Invalid header line: {lines[0]}") raise ValueError(f"Invalid header line: {lines[0]}") # Get content lines (skip header and trailing blank) content_lines = [line for line in lines[1:] if line.strip()] if not content_lines: + logger.error(f"{get_current_class_and_function()}: No parameter lines found") raise ValueError("No parameter lines found") # Parse first line for main parameters @@ -326,6 +337,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": # Ensure line is long enough if len(main_line) < 24: # Minimum length for main parameters + logger.error(f"{get_current_class_and_function()}: Parameter line too short") raise ValueError("Parameter line too short") # Parse main parameters @@ -339,13 +351,16 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": try: params["vary_effective"] = VaryFlag(int(main_line[FORMAT_DEFAULT["ifleff"]].strip() or "0")) params["vary_true"] = VaryFlag(int(main_line[FORMAT_DEFAULT["ifltru"]].strip() or "0")) + #logger.info(f"{get_current_class_and_function()}: Successfully parsed flags") except ValueError: + logger.error(f"{get_current_class_and_function()}: Invalid vary flags") raise ValueError("Invalid vary flags") # Parse spin groups and channels spin_groups, channels = cls._parse_spin_groups_and_channels(content_lines) if not spin_groups: + logger.error(f"{get_current_class_and_function()}: No spin groups found") raise ValueError("No spin groups found") params["spin_groups"] = spin_groups @@ -354,9 +369,13 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": # Create parameters object try: parameters = RadiusParameters(**params) + #logger.info(f"{get_current_class_and_function()}: Successfully created radius parameters") + except ValueError as e: + logger.error(f"{get_current_class_and_function()}: Invalid parameter values: {e}") raise ValueError(f"Invalid parameter values: {e}") + #logger.info(f"{get_current_class_and_function()}: Successfully parsed radius parameters") return cls(parameters=parameters) def to_lines(self) -> List[str]: @@ -365,6 +384,8 @@ def to_lines(self) -> List[str]: Returns: List[str]: Lines including header """ + + #logger.info(f"{get_current_class_and_function()}: Converting radius parameters to lines") lines = [CARD_7_HEADER] # Format main parameters @@ -411,6 +432,7 @@ def to_lines(self) -> List[str]: # Add trailing blank line lines.append("") + #logger.info(f"{get_current_class_and_function()}: Successfully converted radius parameters to lines") return lines @@ -457,6 +479,7 @@ def is_header_line(cls, line: str) -> bool: Returns: bool: True if line is a valid header """ + #logger.debug(f"{get_current_class_and_function()}: Checking if header line - {line}") return line.strip().upper().startswith("RADIU") @staticmethod @@ -498,6 +521,7 @@ def _parse_spin_groups_and_channels(cls, lines: List[str]) -> Tuple[List[int], O """ spin_groups = [] channels = None + #logger.info(f"{get_current_class_and_function()}: parsing spin groups and channels") for line in lines: # Parse numbers 5 columns each starting at position 35 @@ -864,13 +888,17 @@ def is_header_line(cls, line: str) -> bool: Returns: bool: True if line matches any valid radius header format """ + # set location for logger + location = "RadiusCard.is_header_line()" line_upper = line.strip().upper() + logger.debug(f"{location}: {line_upper}") + # Check all valid header formats valid_headers = [ - "RADIUS PARAMETERS FOLLOW", # Standard/Alternate fixed width - "RADII ARE IN KEY-WORD FORMAT", # Keyword format - "CHANNEL RADIUS PARAMETERS FOLLOW", # Alternative keyword format + "RADIUS PARAMETERS FOLLOW", # Standard/Alternate fixed width + "RADII ARE IN KEY-WORD FORMAT", # Keyword format + "CHANNEL RADIUS PARAMETERS FOLLOW", # Alternative keyword format ] return any(header in line_upper for header in valid_headers) @@ -879,53 +907,83 @@ def is_header_line(cls, line: str) -> bool: def detect_format(cls, lines: List[str]) -> RadiusFormat: """Detect format from input lines.""" if not lines: - raise ValueError("No lines provided") + _log_and_raise_error(logger, "No lines provided", ValueError) + # Grab header from lines (suppose to be the first line) header = lines[0].strip().upper() + logger.debug(f"RadiusCard.detect_format(): Header line: {header}") + if "KEY-WORD" in header: + logger.info("RadiusCard.detect_format(): Detected keyword format") return RadiusFormat.KEYWORD elif "RADIU" in header: # Check format by examining spin group columns content_line = next((l for l in lines[1:] if l.strip()), "") # noqa: E741 + logger.debug(f"RadiusCard.detect_format(): Content line: {content_line}") + if len(content_line) >= 35 and content_line[25:30].strip(): # 5-col format + logger.info("RadiusCard.detect_format(): Detected alternate format") return RadiusFormat.ALTERNATE + + logger.info("RadiusCard.detect_format(): Detected default format") return RadiusFormat.DEFAULT - raise ValueError("Invalid header format") + _log_and_raise_error(logger, "Invalid header format", ValueError) @classmethod def from_lines(cls, lines: List[str]) -> "RadiusCard": """Parse radius card from lines in any format.""" + + logger.info("RadiusCard.from_lines(): Attempting to parse radius card from lines") format_type = cls.detect_format(lines) - if format_type == RadiusFormat.KEYWORD: - keyword_card = RadiusCardKeyword.from_lines(lines) - return cls( - parameters=keyword_card.parameters, - particle_pair=keyword_card.particle_pair, - orbital_momentum=keyword_card.orbital_momentum, - relative_uncertainty=keyword_card.relative_uncertainty, - absolute_uncertainty=keyword_card.absolute_uncertainty, - ) - elif format_type == RadiusFormat.ALTERNATE: - return cls(parameters=RadiusCardAlternate.from_lines(lines).parameters) - else: - return cls(parameters=RadiusCardDefault.from_lines(lines).parameters) + try: + if format_type == RadiusFormat.KEYWORD: + keyword_card = RadiusCardKeyword.from_lines(lines) + logger.info("RadiusCard.from_lines(): Successfully parsed radius card in keyword format") + return cls( + parameters=keyword_card.parameters, + particle_pair=keyword_card.particle_pair, + orbital_momentum=keyword_card.orbital_momentum, + relative_uncertainty=keyword_card.relative_uncertainty, + absolute_uncertainty=keyword_card.absolute_uncertainty, + ) + elif format_type == RadiusFormat.ALTERNATE: + radius_card = cls(parameters=RadiusCardAlternate.from_lines(lines).parameters) + logger.info("RadiusCard.from_lines(): Successfully parsed radius card from lines in alternate format") + return radius_card + else: + radius_card = cls(parameters=RadiusCardDefault.from_lines(lines).parameters) + logger.info("RadiusCard.from_lines(): Successfully parsed radius card from lines in default format") + return radius_card + + except Exception as e: + logger.error(f"Failed to parse radius card: {str(e)}\nLines: {lines}") + raise ValueError(f"Failed to parse radius card: {str(e)}\nLines: {lines}") def to_lines(self, radius_format: RadiusFormat = RadiusFormat.KEYWORD) -> List[str]: """Write radius card in specified format.""" - if radius_format == RadiusFormat.KEYWORD: - return RadiusCardKeyword( - parameters=self.parameters, - particle_pair=self.particle_pair, - orbital_momentum=self.orbital_momentum, - relative_uncertainty=self.relative_uncertainty, - absolute_uncertainty=self.absolute_uncertainty, - ).to_lines() - elif radius_format == RadiusFormat.ALTERNATE: - return RadiusCardAlternate(parameters=self.parameters).to_lines() - else: - return RadiusCardDefault(parameters=self.parameters).to_lines() + logger.info(f"RadiusCard.to_lines(): Writing radius card in format: {radius_format}") + try: + if radius_format == RadiusFormat.KEYWORD: + lines = RadiusCardKeyword( + parameters=self.parameters, + particle_pair=self.particle_pair, + orbital_momentum=self.orbital_momentum, + relative_uncertainty=self.relative_uncertainty, + absolute_uncertainty=self.absolute_uncertainty, + ).to_lines() + elif radius_format == RadiusFormat.ALTERNATE: + lines = RadiusCardAlternate(parameters=self.parameters).to_lines() + else: + lines = RadiusCardDefault(parameters=self.parameters).to_lines() + + logger.info("RadiusCard.to_lines(): Successfully wrote radius card") + return lines + + except Exception as e: + logger.error(f"RadiusCard.to_lines(): Failed to write radius card: {str(e)}") + raise @classmethod def from_values( @@ -956,6 +1014,17 @@ def from_values( Returns: RadiusCard: Created card instance """ + logger.info("RadiusCard.from_values(): Creating RadiusCard from values") + logger.debug(f"RadiusCard.from_values(): Effective radius: {effective_radius}") + logger.debug(f"RadiusCard.from_values(): True radius: {true_radius}") + logger.debug(f"RadiusCard.from_values(): Spin groups: {spin_groups}") + logger.debug(f"RadiusCard.from_values(): Channels: {channels}") + logger.debug(f"RadiusCard.from_values(): Particle pair: {particle_pair}") + logger.debug(f"RadiusCard.from_values(): Orbital momentum: {orbital_momentum}") + logger.debug(f"RadiusCard.from_values(): Relative uncertainty: {relative_uncertainty}") + logger.debug(f"RadiusCard.from_values(): Absolute uncertainty: {absolute_uncertainty}") + logger.debug(f"RadiusCard.from_values(): Additional kwargs: {kwargs}") + # Separate parameters and extras params = { "effective_radius": effective_radius, @@ -967,7 +1036,7 @@ def from_values( params.update(kwargs) # Only parameter-specific kwargs # Create card with both parameters and extras - return cls( + card = cls( parameters=RadiusParameters(**params), particle_pair=particle_pair, orbital_momentum=orbital_momentum, @@ -975,6 +1044,9 @@ def from_values( absolute_uncertainty=absolute_uncertainty, ) + logger.info("RadiusCard.from_values(): Successfully created RadiusCard") + return card + if __name__ == "__main__": # Example usage From b0be53796cee58f263cf47de1da135d8ec52dfcd Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 15:09:16 -0700 Subject: [PATCH 21/47] added class and function finder. --- src/pleiades/utils/logger.py | 66 +++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/src/pleiades/utils/logger.py b/src/pleiades/utils/logger.py index f7fe0d7..9f36b11 100644 --- a/src/pleiades/utils/logger.py +++ b/src/pleiades/utils/logger.py @@ -1,13 +1,47 @@ import logging +import inspect + +def get_current_class_and_function(): + """ + Get the name of the current class and function from the call stack. + + Returns: + str: The name of the class and function in the format 'ClassName.function_name()' + or 'function_name()' if not called within a class. + """ + frame = inspect.currentframe().f_back.f_back + class_name = frame.f_locals.get('self', None).__class__.__name__ if 'self' in frame.f_locals else None + function_name = frame.f_code.co_name + if class_name: + return f"{class_name}.{function_name}()" + else: + return f"{function_name}()" def _log_and_raise_error(logger, message: str, exception_class: Exception): + """ + Log an error message and raise an exception. + + Args: + logger (logging.Logger): The logger instance to use for logging the error. + message (str): The error message to log and raise. + exception_class (Exception): The class of the exception to raise. + + Raises: + exception_class: The exception with the provided message. + """ logger.error(message) raise exception_class(message) class Logger: def __init__(self, name: str, level: int = logging.DEBUG, log_file: str = None): + """ + Initialize a Logger instance. - # + Args: + name (str): The name of the logger. + level (int): The logging level (default is logging.DEBUG). + log_file (str, optional): The file to log messages to (default is None). + """ self.logger = logging.getLogger(name) self.logger.setLevel(level) @@ -22,18 +56,48 @@ def __init__(self, name: str, level: int = logging.DEBUG, log_file: str = None): self.logger.info("logging initialized...") def debug(self, message: str): + """ + Log a debug message. + + Args: + message (str): The debug message to log. + """ self.logger.debug(message) def info(self, message: str): + """ + Log an info message. + + Args: + message (str): The info message to log. + """ self.logger.info(message) def warning(self, message: str): + """ + Log a warning message. + + Args: + message (str): The warning message to log. + """ self.logger.warning(message) def error(self, message: str): + """ + Log an error message. + + Args: + message (str): The error message to log. + """ self.logger.error(message) def critical(self, message: str): + """ + Log a critical message. + + Args: + message (str): The critical message to log. + """ self.logger.critical(message) \ No newline at end of file From d8f0420e47b4b65a02899cde6108366f8178a962 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 16:42:09 -0700 Subject: [PATCH 22/47] reorged radius.py --- src/pleiades/sammy/parameters/radius.py | 181 +++++++++++++++--------- 1 file changed, 114 insertions(+), 67 deletions(-) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 9b89731..49f5792 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -18,14 +18,46 @@ log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') logger = Logger(__name__, log_file=log_file_path) -class OrbitalMomentum(str, Enum): - """Valid values for orbital angular momentum specification.""" +#################################################################################################### +# Header flages and format definitions +#################################################################################################### +CARD_7_HEADER = "RADIUs parameters follow" +CARD_7A_HEADER = "RADII are in KEY-WORD format" +CARD_7_ALT_HEADER = "RADIUs parameters follow" - ODD = "ODD" - EVEN = "EVEN" - ALL = "ALL" +# Format definitions for fixed-width fields +FORMAT_DEFAULT = { + "pareff": slice(0, 10), # Effective radius (Fermi) + "partru": slice(10, 20), # True radius (Fermi) + "ichan": slice(20, 21), # Channel indicator + "ifleff": slice(21, 22), # Flag for PAREFF + "ifltru": slice(22, 24), # Flag for PARTRU + # Spin groups start at col 24, 2 cols each + # After IX=0 marker, channel numbers use 2 cols each +} +# Format definitions for fixed-width fields +FORMAT_ALTERNATE = { + "pareff": slice(0, 10), # Radius for potential scattering + "partru": slice(10, 20), # Radius for penetrabilities + "ichan": slice(20, 25), # Channel indicator (5 cols) + "ifleff": slice(25, 30), # Flag for PAREFF (5 cols) + "ifltru": slice(30, 35), # Flag for PARTRU (5 cols) + # Spin groups start at col 35, 5 cols each + # After IX=0 marker, channel numbers use 5 cols each +} + +class RadiusFormat(Enum): + """Supported formats for radius parameter cards.""" + DEFAULT = "default" # Fixed width (<99 spin groups) + ALTERNATE = "alternate" # Fixed width (>=99 spin groups) + KEYWORD = "keyword" # Keyword based + + +#################################################################################################### +# RadiusParameters class and its corresponding Container class +#################################################################################################### class RadiusParameters(BaseModel): """Container for nuclear radius parameters used in SAMMY calculations. @@ -192,22 +224,51 @@ def validate_true_radius_consistency(self) -> "RadiusParameters": return self + def __repr__(self) -> str: + """Return a string representation of the radius parameters.""" + return ( + f"RadiusParameters(" + f"effective_radius={self.effective_radius}, " + f"true_radius={self.true_radius}, " + f"channel_mode={self.channel_mode}, " + f"vary_effective={self.vary_effective}, " + f"vary_true={self.vary_true}, " + f"spin_groups={self.spin_groups}, " + f"channels={self.channels})" + ) -# Format definitions for fixed-width fields -FORMAT_DEFAULT = { - "pareff": slice(0, 10), # Effective radius (Fermi) - "partru": slice(10, 20), # True radius (Fermi) - "ichan": slice(20, 21), # Channel indicator - "ifleff": slice(21, 22), # Flag for PAREFF - "ifltru": slice(22, 24), # Flag for PARTRU - # Spin groups start at col 24, 2 cols each - # After IX=0 marker, channel numbers use 2 cols each -} -CARD_7_HEADER = "RADIUs parameters follow" +class RadiusParametersContainer: + """Container for multiple RadiusParameters entries.""" + def __init__(self): + self.entries = [] + def add_parameters(self, params: RadiusParameters): + """Add a RadiusParameters entry to the container.""" + self.entries.append(params) + def remove_parameters(self, index: int): + """Remove a RadiusParameters entry from the container by index.""" + if 0 <= index < len(self.entries): + self.entries.pop(index) + else: + raise IndexError("Index out of range") + + def print_all_parameters(self): + """Print all RadiusParameters entries in the container.""" + for i, params in enumerate(self.entries): + print(f"Entry {i + 1}:") + print(params) + print() + + def __repr__(self) -> str: + """Return a string representation of the container.""" + return f"RadiusParametersContainer(entries={self.entries})" + +#################################################################################################### +# Card variant classes for different formats (default, alternate, keyword) +#################################################################################################### class RadiusCardDefault(BaseModel): """Handler for default format radius parameter cards (Card Set 7). @@ -436,20 +497,6 @@ def to_lines(self) -> List[str]: return lines -# Format definitions for fixed-width fields -FORMAT_ALTERNATE = { - "pareff": slice(0, 10), # Radius for potential scattering - "partru": slice(10, 20), # Radius for penetrabilities - "ichan": slice(20, 25), # Channel indicator (5 cols) - "ifleff": slice(25, 30), # Flag for PAREFF (5 cols) - "ifltru": slice(30, 35), # Flag for PARTRU (5 cols) - # Spin groups start at col 35, 5 cols each - # After IX=0 marker, channel numbers use 5 cols each -} - -CARD_7_ALT_HEADER = "RADIUs parameters follow" - - class RadiusCardAlternate(BaseModel): """Handler for alternate format radius parameter cards (Card Set 7 alternate). @@ -664,9 +711,6 @@ def to_lines(self) -> List[str]: return lines -CARD_7A_HEADER = "RADII are in KEY-WORD format" - - class RadiusCardKeyword(BaseModel): """Handler for keyword-based radius parameter cards (Card Set 7a). @@ -839,14 +883,9 @@ def to_lines(self) -> List[str]: return lines -class RadiusFormat(Enum): - """Supported formats for radius parameter cards.""" - - DEFAULT = "default" # Fixed width (<99 spin groups) - ALTERNATE = "alternate" # Fixed width (>=99 spin groups) - KEYWORD = "keyword" # Keyword based - - +#################################################################################################### +# RadiusCard Class (what is called in parfile.py) +#################################################################################################### class RadiusCard(BaseModel): """Main handler for SAMMY radius parameter cards. @@ -889,10 +928,10 @@ def is_header_line(cls, line: str) -> bool: bool: True if line matches any valid radius header format """ # set location for logger - location = "RadiusCard.is_header_line()" + where_am_i = "RadiusCard.is_header_line()" line_upper = line.strip().upper() - logger.debug(f"{location}: {line_upper}") + logger.debug(f"{where_am_i}: {line_upper}") # Check all valid header formats valid_headers = [ @@ -906,26 +945,27 @@ def is_header_line(cls, line: str) -> bool: @classmethod def detect_format(cls, lines: List[str]) -> RadiusFormat: """Detect format from input lines.""" + where_am_i = "RadiusCard.detect_format()" if not lines: _log_and_raise_error(logger, "No lines provided", ValueError) # Grab header from lines (suppose to be the first line) header = lines[0].strip().upper() - logger.debug(f"RadiusCard.detect_format(): Header line: {header}") + logger.debug(f"{where_am_i}: Header line: {header}") if "KEY-WORD" in header: - logger.info("RadiusCard.detect_format(): Detected keyword format") + logger.info(f"{where_am_i}: Detected keyword format") return RadiusFormat.KEYWORD elif "RADIU" in header: # Check format by examining spin group columns content_line = next((l for l in lines[1:] if l.strip()), "") # noqa: E741 - logger.debug(f"RadiusCard.detect_format(): Content line: {content_line}") + logger.debug(f"{where_am_i}: Content line: {content_line}") if len(content_line) >= 35 and content_line[25:30].strip(): # 5-col format - logger.info("RadiusCard.detect_format(): Detected alternate format") + logger.info(f"{where_am_i}: Detected alternate format") return RadiusFormat.ALTERNATE - logger.info("RadiusCard.detect_format(): Detected default format") + logger.info(f"{where_am_i}: Detected default format") return RadiusFormat.DEFAULT _log_and_raise_error(logger, "Invalid header format", ValueError) @@ -933,14 +973,15 @@ def detect_format(cls, lines: List[str]) -> RadiusFormat: @classmethod def from_lines(cls, lines: List[str]) -> "RadiusCard": """Parse radius card from lines in any format.""" + where_am_i = "RadiusCard.from_lines()" - logger.info("RadiusCard.from_lines(): Attempting to parse radius card from lines") + logger.info(f"{where_am_i}: Attempting to parse radius card from lines") format_type = cls.detect_format(lines) try: if format_type == RadiusFormat.KEYWORD: keyword_card = RadiusCardKeyword.from_lines(lines) - logger.info("RadiusCard.from_lines(): Successfully parsed radius card in keyword format") + logger.info(f"{where_am_i}: Successfully parsed radius card in keyword format") return cls( parameters=keyword_card.parameters, particle_pair=keyword_card.particle_pair, @@ -950,20 +991,21 @@ def from_lines(cls, lines: List[str]) -> "RadiusCard": ) elif format_type == RadiusFormat.ALTERNATE: radius_card = cls(parameters=RadiusCardAlternate.from_lines(lines).parameters) - logger.info("RadiusCard.from_lines(): Successfully parsed radius card from lines in alternate format") + logger.info(f"{where_am_i}: Successfully parsed radius card from lines in alternate format") return radius_card else: radius_card = cls(parameters=RadiusCardDefault.from_lines(lines).parameters) - logger.info("RadiusCard.from_lines(): Successfully parsed radius card from lines in default format") + logger.info(f"{where_am_i}: Successfully parsed radius card from lines in default format") return radius_card except Exception as e: - logger.error(f"Failed to parse radius card: {str(e)}\nLines: {lines}") + logger.error(f"{where_am_i}Failed to parse radius card: {str(e)}\nLines: {lines}") raise ValueError(f"Failed to parse radius card: {str(e)}\nLines: {lines}") def to_lines(self, radius_format: RadiusFormat = RadiusFormat.KEYWORD) -> List[str]: """Write radius card in specified format.""" - logger.info(f"RadiusCard.to_lines(): Writing radius card in format: {radius_format}") + where_am_i = "RadiusCard.to_lines()" + logger.info(f"{where_am_i}: Writing radius card in format: {radius_format}") try: if radius_format == RadiusFormat.KEYWORD: lines = RadiusCardKeyword( @@ -978,11 +1020,11 @@ def to_lines(self, radius_format: RadiusFormat = RadiusFormat.KEYWORD) -> List[s else: lines = RadiusCardDefault(parameters=self.parameters).to_lines() - logger.info("RadiusCard.to_lines(): Successfully wrote radius card") + logger.info(f"{where_am_i}: Successfully wrote radius card") return lines except Exception as e: - logger.error(f"RadiusCard.to_lines(): Failed to write radius card: {str(e)}") + logger.error(f"{where_am_i}: Failed to write radius card: {str(e)}") raise @classmethod @@ -1014,16 +1056,7 @@ def from_values( Returns: RadiusCard: Created card instance """ - logger.info("RadiusCard.from_values(): Creating RadiusCard from values") - logger.debug(f"RadiusCard.from_values(): Effective radius: {effective_radius}") - logger.debug(f"RadiusCard.from_values(): True radius: {true_radius}") - logger.debug(f"RadiusCard.from_values(): Spin groups: {spin_groups}") - logger.debug(f"RadiusCard.from_values(): Channels: {channels}") - logger.debug(f"RadiusCard.from_values(): Particle pair: {particle_pair}") - logger.debug(f"RadiusCard.from_values(): Orbital momentum: {orbital_momentum}") - logger.debug(f"RadiusCard.from_values(): Relative uncertainty: {relative_uncertainty}") - logger.debug(f"RadiusCard.from_values(): Absolute uncertainty: {absolute_uncertainty}") - logger.debug(f"RadiusCard.from_values(): Additional kwargs: {kwargs}") + where_am_i = "RadiusCard.from_values()" # Separate parameters and extras params = { @@ -1044,10 +1077,24 @@ def from_values( absolute_uncertainty=absolute_uncertainty, ) - logger.info("RadiusCard.from_values(): Successfully created RadiusCard") + logger.info(f"{where_am_i}: Successfully created RadiusCard") return card +#################################################################################################### +# IDK what this is for! +#################################################################################################### +class OrbitalMomentum(str, Enum): + """Valid values for orbital angular momentum specification.""" + + ODD = "ODD" + EVEN = "EVEN" + ALL = "ALL" + + +#################################################################################################### +# main function +#################################################################################################### if __name__ == "__main__": # Example usage card = RadiusCard.from_values(effective_radius=3.2, true_radius=3.2, spin_groups=[1, 2, 3]) From 32cd3c431fd9ebc504369919b50180583d9fc29d Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 16:42:51 -0700 Subject: [PATCH 23/47] got rid of logger functionality to find class and function being called --- src/pleiades/sammy/parfile.py | 36 +++++++++++++++++++++-------------- src/pleiades/utils/logger.py | 15 +++++++++------ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 28a21cc..736436a 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -108,8 +108,9 @@ def to_string(self) -> str: The output follows the standard card order from Table VI B.2. Each card is separated by appropriate blank lines. """ + where_am_i = "SammyParameterFile.to_string()" - logger.info("SammyParameterFile.to_string(): Attempting to convert parameter file to string format") + logger.info(f"{where_am_i}: Attempting to convert parameter file to string format") lines = [] # Process each card type in standard order @@ -130,11 +131,11 @@ def to_string(self) -> str: card_lines = value.to_lines() if card_lines: # Only add non-empty line lists lines.extend(card_lines) - logger.debug(f"SammyParameterFile.to_string(): Added lines for {card_type.name} card") + logger.debug(f"{where_am_i}: Added lines for {card_type.name} card") # Join all lines with newlines result = "\n".join(lines) - logger.info("SammyParameterFile.to_string(): Successfully converted parameter file to string format") + logger.info(f"{where_am_i}: Successfully converted parameter file to string format") return result @classmethod @@ -147,6 +148,8 @@ def _get_card_class_with_header(cls, line: str): Returns: tuple: (CardOrder enum, card class) if found, or (None, None) """ + where_am_i = "SammyParameterFile._get_card_class_with_header()" + card_checks = [ (CardOrder.BROADENING, BroadeningParameterCard), (CardOrder.DATA_REDUCTION, DataReductionCard), @@ -162,10 +165,10 @@ def _get_card_class_with_header(cls, line: str): for card_type, card_class in card_checks: if hasattr(card_class, "is_header_line") and card_class.is_header_line(line): - logger.debug(f"SammyParameterFile._get_card_class_with_header(): card_type:{card_type}\t card_class:{card_class}") + logger.debug(f"{where_am_i}: card_type:{card_type}\t card_class:{card_class}") return card_type, card_class - logger.info(f"SammyParameterFile._get_card_class_with_header(): No matches found for {line}") + logger.info(f"{where_am_i}: No matches found for {line}") return None, None @classmethod @@ -178,6 +181,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": Returns: SammyParameterFile: Parsed parameter file object. """ + where_am_i = "SammyParameterFile.from_string()" # Split content into lines lines = content.splitlines() @@ -218,7 +222,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Process card with header try: params[CardOrder.get_field_name(card_type)] = card_class.from_lines(group) - logger.info(f"SammyParameterFile.from_string(): Successfully parsed {card_type.name} card") + logger.info(f"{where_am_i}: Successfully parsed {card_type.name} card") except Exception as e: logger.error(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") @@ -228,7 +232,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": if len(group) == 1: try: params["fudge"] = float(group[0]) - logger.info("SammyParameterFile.from_string(): Successfully parsed fudge factor") + logger.info(f"{where_am_i}: Successfully parsed fudge factor") except ValueError as e: logger.error(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") @@ -237,18 +241,20 @@ def from_string(cls, content: str) -> "SammyParameterFile": try: # Try parsing as resonance table params["resonance"] = ResonanceCard.from_lines(group) - logger.info("SammyParameterFile.from_string(): Successfully parsed resonance table") + logger.info(f"{where_am_i}: Successfully parsed resonance table") except Exception as e: logger.error(f"Failed to parse card without header: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") - logger.info("SammyParameterFile.from_string(): Successfully parsed all parameter file content from string") + logger.info(f"{where_am_i}: Successfully parsed all parameter file content from string") return cls(**params) @classmethod def _parse_card(cls, card_type: CardOrder, lines: List[str]): """Parse a card's lines into the appropriate object.""" - logger.info(f"SammyParameterFile._parse_card(): Attempting to parse card of type: {card_type.name}") + where_am_i = "SammyParameterFile._parse_card()" + + logger.info(f"{where_am_i}: Attempting to parse card of type: {card_type.name}") card_class = cls._get_card_class(card_type) @@ -296,15 +302,16 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": FileNotFoundError: If file does not exist ValueError: If file content is invalid """ + where_am_i = "SammyParameterFile.from_file()" filepath = pathlib.Path(filepath) - logger.info(f"SammyParameterFile.from_file(): Attempting to read parameter file from: {filepath}") + logger.info(f"{where_am_i}: Attempting to read parameter file from: {filepath}") if not filepath.exists(): _log_and_raise_error(f"Parameter file not found: {filepath}", FileNotFoundError) try: content = filepath.read_text() - logger.info(f"SammyParameterFile.from_file(): Successfully read content in file: {filepath}") + logger.info(f"{where_am_i}: Successfully read content in file: {filepath}") return cls.from_string(content) except UnicodeDecodeError as e: @@ -322,8 +329,9 @@ def to_file(self, filepath: Union[str, pathlib.Path]) -> None: OSError: If file cannot be written ValueError: If content cannot be formatted """ + where_am_i = "SammyParameterFile.to_file()" filepath = pathlib.Path(filepath) - logger.info(f"SammyParameterFile.to_file(): Attempting to write parameter file to: {filepath}") + logger.info(f"{where_am_i}: Attempting to write parameter file to: {filepath}") # Create parent directories if they don't exist filepath.parent.mkdir(parents=True, exist_ok=True) @@ -331,7 +339,7 @@ def to_file(self, filepath: Union[str, pathlib.Path]) -> None: try: content = self.to_string() filepath.write_text(content) - logger.info(f"SammyParameterFile.to_file(): Successfully wrote parameter file to: {filepath}") + logger.info(f"{where_am_i}: Successfully wrote parameter file to: {filepath}") except OSError as e: _log_and_raise_error(f"Failed to write parameter file: {e}", OSError) diff --git a/src/pleiades/utils/logger.py b/src/pleiades/utils/logger.py index 9f36b11..acfb576 100644 --- a/src/pleiades/utils/logger.py +++ b/src/pleiades/utils/logger.py @@ -3,17 +3,20 @@ def get_current_class_and_function(): """ - Get the name of the current class and function from the call stack. + Get the name of the current function and the parent object from the call stack. Returns: - str: The name of the class and function in the format 'ClassName.function_name()' - or 'function_name()' if not called within a class. + str: The name of the function and the parent object in the format 'ParentObject.function_name()' + or 'function_name()' if not called within an object. """ frame = inspect.currentframe().f_back.f_back - class_name = frame.f_locals.get('self', None).__class__.__name__ if 'self' in frame.f_locals else None + parent_object = None + if 'self' in frame.f_locals: + parent_object = frame.f_locals['self'] function_name = frame.f_code.co_name - if class_name: - return f"{class_name}.{function_name}()" + + if parent_object: + return f"{parent_object}.{function_name}()" else: return f"{function_name}()" From 3d8d65a457ca29f5d81c88150d9ce0a9d08f1667 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 17:03:37 -0700 Subject: [PATCH 24/47] got rid of get_current_class_and_function() --- src/pleiades/utils/logger.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/pleiades/utils/logger.py b/src/pleiades/utils/logger.py index acfb576..d2b0fbf 100644 --- a/src/pleiades/utils/logger.py +++ b/src/pleiades/utils/logger.py @@ -1,25 +1,6 @@ import logging import inspect -def get_current_class_and_function(): - """ - Get the name of the current function and the parent object from the call stack. - - Returns: - str: The name of the function and the parent object in the format 'ParentObject.function_name()' - or 'function_name()' if not called within an object. - """ - frame = inspect.currentframe().f_back.f_back - parent_object = None - if 'self' in frame.f_locals: - parent_object = frame.f_locals['self'] - function_name = frame.f_code.co_name - - if parent_object: - return f"{parent_object}.{function_name}()" - else: - return f"{function_name}()" - def _log_and_raise_error(logger, message: str, exception_class: Exception): """ Log an error message and raise an exception. From 51d2df7f3b7ae5c2226abbc87ab56cf0f65bdf52 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 17:04:09 -0700 Subject: [PATCH 25/47] using where_am_i strings for debugging --- src/pleiades/sammy/parameters/radius.py | 71 +++++++++++++++++-------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 49f5792..143001b 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, Field, field_validator, model_validator from pleiades.sammy.parameters.helper import VaryFlag, format_float, format_vary, parse_keyword_pairs_to_dict, safe_parse -from pleiades.utils.logger import Logger, _log_and_raise_error, get_current_class_and_function +from pleiades.utils.logger import Logger, _log_and_raise_error # Initialize logger with file logging log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') @@ -297,7 +297,8 @@ def is_header_line(cls, line: str) -> bool: Returns: bool: True if line is a valid header """ - #logger.debug(f"{get_current_class_and_function()}: Checking if header line - {line}") + where_am_i = "RadiusCardDefault.is_header_line()" + logger.debug(f"{where_am_i}: Checking if header line - {line}") return line.strip().upper().startswith("RADIU") @staticmethod @@ -312,6 +313,8 @@ def _parse_numbers_from_line(line: str, start_pos: int, width: int) -> List[int] Returns: List[int]: List of parsed integers, stopping at first invalid value """ + where_am_i = "RadiusCardDefault._parse_numbers_from_line()" + logger.info(f"{where_am_i}: Parsing fixed-width integers from line") numbers = [] pos = start_pos while pos + width <= len(line): @@ -337,6 +340,8 @@ def _parse_spin_groups_and_channels(cls, lines: List[str]) -> Tuple[List[int], O Note: Handles continuation lines (-1 marker) and IX=0 marker for channels """ + where_am_i = "RadiusCardDefault._parse_spin_groups_and_channels()" + logger.info(f"{where_am_i}: Parsing spin groups and channels") spin_groups = [] channels = None @@ -376,21 +381,22 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": Raises: ValueError: If lines are invalid or required data is missing """ + where_am_i = "RadiusCardDefault.from_lines()" - #logger.info(f"{get_current_class_and_function()}: Parsing radius parameters from lines") + logger.info(f"{where_am_i}: Parsing radius parameters from lines") if not lines: - logger.error(f"{get_current_class_and_function()}: No lines provided") + logger.error(f"{where_am_i}: No lines provided") raise ValueError("No lines provided") # Validate header if not cls.is_header_line(lines[0]): - logger.error(f"{get_current_class_and_function()}: Invalid header line: {lines[0]}") + logger.error(f"{where_am_i}: Invalid header line: {lines[0]}") raise ValueError(f"Invalid header line: {lines[0]}") # Get content lines (skip header and trailing blank) content_lines = [line for line in lines[1:] if line.strip()] if not content_lines: - logger.error(f"{get_current_class_and_function()}: No parameter lines found") + logger.error(f"{where_am_i}: No parameter lines found") raise ValueError("No parameter lines found") # Parse first line for main parameters @@ -398,7 +404,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": # Ensure line is long enough if len(main_line) < 24: # Minimum length for main parameters - logger.error(f"{get_current_class_and_function()}: Parameter line too short") + logger.error(f"{where_am_i}: Parameter line too short") raise ValueError("Parameter line too short") # Parse main parameters @@ -412,16 +418,16 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": try: params["vary_effective"] = VaryFlag(int(main_line[FORMAT_DEFAULT["ifleff"]].strip() or "0")) params["vary_true"] = VaryFlag(int(main_line[FORMAT_DEFAULT["ifltru"]].strip() or "0")) - #logger.info(f"{get_current_class_and_function()}: Successfully parsed flags") + logger.info(f"{where_am_i}: Successfully parsed flags") except ValueError: - logger.error(f"{get_current_class_and_function()}: Invalid vary flags") + logger.error(f"{where_am_i}: Invalid vary flags") raise ValueError("Invalid vary flags") # Parse spin groups and channels spin_groups, channels = cls._parse_spin_groups_and_channels(content_lines) if not spin_groups: - logger.error(f"{get_current_class_and_function()}: No spin groups found") + logger.error(f"{where_am_i}: No spin groups found") raise ValueError("No spin groups found") params["spin_groups"] = spin_groups @@ -430,13 +436,13 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": # Create parameters object try: parameters = RadiusParameters(**params) - #logger.info(f"{get_current_class_and_function()}: Successfully created radius parameters") + #logger.info(f"{where_am_i}: Successfully created radius parameters") except ValueError as e: - logger.error(f"{get_current_class_and_function()}: Invalid parameter values: {e}") + logger.error(f"{where_am_i}: Invalid parameter values: {e}") raise ValueError(f"Invalid parameter values: {e}") - #logger.info(f"{get_current_class_and_function()}: Successfully parsed radius parameters") + logger.info(f"{where_am_i}: Successfully parsed radius parameters") return cls(parameters=parameters) def to_lines(self) -> List[str]: @@ -445,8 +451,9 @@ def to_lines(self) -> List[str]: Returns: List[str]: Lines including header """ - - #logger.info(f"{get_current_class_and_function()}: Converting radius parameters to lines") + where_am_i = "RadiusCardDefault.to_lines()" + + logger.info(f"{where_am_i}: Converting radius parameters to lines") lines = [CARD_7_HEADER] # Format main parameters @@ -493,7 +500,7 @@ def to_lines(self) -> List[str]: # Add trailing blank line lines.append("") - #logger.info(f"{get_current_class_and_function()}: Successfully converted radius parameters to lines") + logger.info(f"{where_am_i}: Successfully converted radius parameters to lines") return lines @@ -526,7 +533,8 @@ def is_header_line(cls, line: str) -> bool: Returns: bool: True if line is a valid header """ - #logger.debug(f"{get_current_class_and_function()}: Checking if header line - {line}") + where_am_i = "RadiusCardAlternate.is_header_line()" + logger.debug(f"{where_am_i}: Checking if header line - {line}") return line.strip().upper().startswith("RADIU") @staticmethod @@ -541,6 +549,8 @@ def _parse_numbers_from_line(line: str, start_pos: int, width: int) -> List[int] Returns: List[int]: List of parsed integers, stopping at first invalid value """ + where_am_i = "RadiusCardAlternate._parse_numbers_from_line()" + numbers = [] pos = start_pos while pos + width <= len(line): @@ -566,9 +576,11 @@ def _parse_spin_groups_and_channels(cls, lines: List[str]) -> Tuple[List[int], O Note: Handles continuation lines (-1 marker) and IX=0 marker for channels """ + where_am_i = "RadiusCardAlternate._parse_spin_groups_and_channels()" + spin_groups = [] channels = None - #logger.info(f"{get_current_class_and_function()}: parsing spin groups and channels") + logger.info(f"{where_am_i}: parsing spin groups and channels") for line in lines: # Parse numbers 5 columns each starting at position 35 @@ -606,6 +618,8 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardAlternate": Raises: ValueError: If lines are invalid or required data is missing """ + where_am_i = "RadiusCardAlternate.from_lines()" + if not lines: raise ValueError("No lines provided") @@ -662,6 +676,8 @@ def to_lines(self) -> List[str]: Returns: List[str]: Lines including header """ + where_am_i = "RadiusCardAlternate.to_lines()" + lines = [CARD_7_ALT_HEADER] # Format main parameters @@ -727,6 +743,8 @@ class RadiusCardKeyword(BaseModel): """ parameters: RadiusParameters + + # Optional keyword format extras particle_pair: Optional[str] = None orbital_momentum: Optional[List[Union[int, str]]] = None relative_uncertainty: Optional[float] = None @@ -735,11 +753,13 @@ class RadiusCardKeyword(BaseModel): @classmethod def is_header_line(cls, line: str) -> bool: """Check if line is a valid header line.""" + where_am_i = "RadiusCardKeyword.is_header_line()" return "RADII" in line.upper() and "KEY-WORD" in line.upper() @staticmethod def _parse_values(value_str: str) -> List[str]: """Parse space/comma separated values.""" + where_am_i = "RadiusCardKeyword._parse_values()" return [v for v in re.split(r"[,\s]+", value_str.strip()) if v] @classmethod @@ -755,6 +775,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardKeyword": Raises: ValueError: If lines are invalid or required data is missing """ + where_am_i = "RadiusCardKeyword.from_lines()" if not lines: raise ValueError("No lines provided") @@ -846,6 +867,7 @@ def to_lines(self) -> List[str]: Returns: List[str]: Lines in keyword format """ + where_am_i = "RadiusCardKeyword.to_lines()" lines = [CARD_7A_HEADER] # Add radius values @@ -911,6 +933,7 @@ class RadiusCard(BaseModel): """ parameters: RadiusParameters + # Optional keyword format extras particle_pair: Optional[str] = None orbital_momentum: Optional[List[Union[int, str]]] = None @@ -929,6 +952,7 @@ def is_header_line(cls, line: str) -> bool: """ # set location for logger where_am_i = "RadiusCard.is_header_line()" + line_upper = line.strip().upper() logger.debug(f"{where_am_i}: {line_upper}") @@ -946,6 +970,7 @@ def is_header_line(cls, line: str) -> bool: def detect_format(cls, lines: List[str]) -> RadiusFormat: """Detect format from input lines.""" where_am_i = "RadiusCard.detect_format()" + if not lines: _log_and_raise_error(logger, "No lines provided", ValueError) @@ -954,7 +979,7 @@ def detect_format(cls, lines: List[str]) -> RadiusFormat: logger.debug(f"{where_am_i}: Header line: {header}") if "KEY-WORD" in header: - logger.info(f"{where_am_i}: Detected keyword format") + logger.info(f"{where_am_i}: Detected keyword format!") return RadiusFormat.KEYWORD elif "RADIU" in header: # Check format by examining spin group columns @@ -962,13 +987,13 @@ def detect_format(cls, lines: List[str]) -> RadiusFormat: logger.debug(f"{where_am_i}: Content line: {content_line}") if len(content_line) >= 35 and content_line[25:30].strip(): # 5-col format - logger.info(f"{where_am_i}: Detected alternate format") + logger.info(f"{where_am_i}: Detected alternate format!") return RadiusFormat.ALTERNATE - logger.info(f"{where_am_i}: Detected default format") + logger.info(f"{where_am_i}: Detected default format!") return RadiusFormat.DEFAULT - _log_and_raise_error(logger, "Invalid header format", ValueError) + _log_and_raise_error(logger, "Invalid header format...", ValueError) @classmethod def from_lines(cls, lines: List[str]) -> "RadiusCard": @@ -1025,7 +1050,7 @@ def to_lines(self, radius_format: RadiusFormat = RadiusFormat.KEYWORD) -> List[s except Exception as e: logger.error(f"{where_am_i}: Failed to write radius card: {str(e)}") - raise + raise ValueError(f"Failed to write radius card: {str(e)}") @classmethod def from_values( From f505fd611e373461e99a1e87bf4d130bbe7e8ef7 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 17:04:29 -0700 Subject: [PATCH 26/47] idk! --- src/pleiades/sammy/parfile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 736436a..b38d0d5 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -182,6 +182,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": SammyParameterFile: Parsed parameter file object. """ where_am_i = "SammyParameterFile.from_string()" + # Split content into lines lines = content.splitlines() @@ -303,7 +304,9 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": ValueError: If file content is invalid """ where_am_i = "SammyParameterFile.from_file()" + filepath = pathlib.Path(filepath) + logger.info(f"{where_am_i}: Attempting to read parameter file from: {filepath}") if not filepath.exists(): From eba8fd96489f4b2a5174b290db05457522fd7408 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 18:15:18 -0700 Subject: [PATCH 27/47] debugging default card reading --- src/pleiades/sammy/parameters/radius.py | 82 +++++++++++++++---------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 143001b..92dcef3 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -399,50 +399,61 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": logger.error(f"{where_am_i}: No parameter lines found") raise ValueError("No parameter lines found") - # Parse first line for main parameters - main_line = content_lines[0] - - # Ensure line is long enough - if len(main_line) < 24: # Minimum length for main parameters - logger.error(f"{where_am_i}: Parameter line too short") - raise ValueError("Parameter line too short") - - # Parse main parameters + # Initialize parameters params = { - "effective_radius": safe_parse(main_line[FORMAT_DEFAULT["pareff"]]), - "true_radius": safe_parse(main_line[FORMAT_DEFAULT["partru"]]), - "channel_mode": safe_parse(main_line[FORMAT_DEFAULT["ichan"]], as_int=True) or 0, + "effective_radius": None, + "true_radius": None, + "channel_mode": None, + "vary_effective": None, + "vary_true": None, + "spin_groups": [], + "channels": None, } - # Parse flags - try: - params["vary_effective"] = VaryFlag(int(main_line[FORMAT_DEFAULT["ifleff"]].strip() or "0")) - params["vary_true"] = VaryFlag(int(main_line[FORMAT_DEFAULT["ifltru"]].strip() or "0")) - logger.info(f"{where_am_i}: Successfully parsed flags") - except ValueError: - logger.error(f"{where_am_i}: Invalid vary flags") - raise ValueError("Invalid vary flags") - - # Parse spin groups and channels - spin_groups, channels = cls._parse_spin_groups_and_channels(content_lines) - - if not spin_groups: + # Parse each line for main parameters and spin groups + for line in content_lines: + # Ensure line is long enough + if len(line) < 24: # Minimum length for main parameters + logger.error(f"{where_am_i}: Parameter line too short") + raise ValueError("Parameter line too short") + + # Parse main parameters if not already set + if params["effective_radius"] is None: + params["effective_radius"] = safe_parse(line[FORMAT_DEFAULT["pareff"]]) + params["true_radius"] = safe_parse(line[FORMAT_DEFAULT["partru"]]) + params["channel_mode"] = safe_parse(line[FORMAT_DEFAULT["ichan"]], as_int=True) or 0 + + # Parse flags + try: + params["vary_effective"] = VaryFlag(int(line[FORMAT_DEFAULT["ifleff"]].strip() or "0")) + params["vary_true"] = VaryFlag(int(line[FORMAT_DEFAULT["ifltru"]].strip() or "0")) + logger.info(f"{where_am_i}: Successfully parsed flags") + except ValueError: + logger.error(f"{where_am_i}: Invalid vary flags") + raise ValueError("Invalid vary flags") + + # Parse spin groups and channels + spin_groups, channels = cls._parse_spin_groups_and_channels([line]) + params["spin_groups"].extend(spin_groups) + if channels: + params["channels"] = channels + + if not params["spin_groups"]: logger.error(f"{where_am_i}: No spin groups found") raise ValueError("No spin groups found") - params["spin_groups"] = spin_groups - params["channels"] = channels - + print(params) # Create parameters object try: parameters = RadiusParameters(**params) - #logger.info(f"{where_am_i}: Successfully created radius parameters") + logger.info(f"{where_am_i}: Successfully created radius parameters") except ValueError as e: logger.error(f"{where_am_i}: Invalid parameter values: {e}") raise ValueError(f"Invalid parameter values: {e}") logger.info(f"{where_am_i}: Successfully parsed radius parameters") + return cls(parameters=parameters) def to_lines(self) -> List[str]: @@ -978,19 +989,23 @@ def detect_format(cls, lines: List[str]) -> RadiusFormat: header = lines[0].strip().upper() logger.debug(f"{where_am_i}: Header line: {header}") + # Check for valid header formats + # If KEY-WORD is in the header, it is a keyword format if "KEY-WORD" in header: logger.info(f"{where_am_i}: Detected keyword format!") return RadiusFormat.KEYWORD + + # If DEFAULT or ALTERNATE card formats then "RADIUS" should be in the header elif "RADIU" in header: # Check format by examining spin group columns content_line = next((l for l in lines[1:] if l.strip()), "") # noqa: E741 - logger.debug(f"{where_am_i}: Content line: {content_line}") - + + # Check for alternate format (5-column integer values) if len(content_line) >= 35 and content_line[25:30].strip(): # 5-col format - logger.info(f"{where_am_i}: Detected alternate format!") + logger.info(f"{where_am_i}: Detected ALTERNATE format based on {content_line}") return RadiusFormat.ALTERNATE - logger.info(f"{where_am_i}: Detected default format!") + logger.info(f"{where_am_i}: Detected DEFAULT format based on {content_line}") return RadiusFormat.DEFAULT _log_and_raise_error(logger, "Invalid header format...", ValueError) @@ -1003,6 +1018,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCard": logger.info(f"{where_am_i}: Attempting to parse radius card from lines") format_type = cls.detect_format(lines) + # Try reading in the radius card based on the determined format try: if format_type == RadiusFormat.KEYWORD: keyword_card = RadiusCardKeyword.from_lines(lines) From e4e7ea4d4c5aa6b23ee6fb3ba446e1db00b8b842 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 21:00:03 -0700 Subject: [PATCH 28/47] fixed bug to allow RadiusCard to handle multiple RadiusParameters entries --- src/pleiades/sammy/parameters/radius.py | 224 ++++++++++++------------ 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 92dcef3..ed3eadc 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -285,7 +285,7 @@ class RadiusCardDefault(BaseModel): For larger systems, use RadiusCardAlternate. """ - parameters: RadiusParameters + parameters: List[RadiusParameters] @classmethod def is_header_line(cls, line: str) -> bool: @@ -326,11 +326,11 @@ def _parse_numbers_from_line(line: str, start_pos: int, width: int) -> List[int] return numbers @classmethod - def _parse_spin_groups_and_channels(cls, lines: List[str]) -> Tuple[List[int], Optional[List[int]]]: - """Parse spin groups and optional channels from lines. + def _parse_spin_groups_and_channels(cls, line: str) -> Tuple[List[int], Optional[List[int]]]: + """Parse spin groups and optional channels from a line. Args: - lines: List of input lines containing spin groups/channels + line: Input line containing spin groups/channels Returns: Tuple containing: @@ -341,30 +341,29 @@ def _parse_spin_groups_and_channels(cls, lines: List[str]) -> Tuple[List[int], O Handles continuation lines (-1 marker) and IX=0 marker for channels """ where_am_i = "RadiusCardDefault._parse_spin_groups_and_channels()" - logger.info(f"{where_am_i}: Parsing spin groups and channels") + logger.info(f"{where_am_i}: Parsing spin groups and channels from line: {line}") + spin_groups = [] channels = None - for line in lines: - # Parse numbers 2 columns each starting at position 24 - numbers = cls._parse_numbers_from_line(line, 24, 2) + # Parse numbers 2 columns each starting at position 24 + numbers = cls._parse_numbers_from_line(line, 24, 2) + logger.info(f"{where_am_i}: Parsed numbers: {numbers}") - if not numbers: - continue - - # Check for continuation marker (-1) - if numbers[-1] == -1: - spin_groups.extend(numbers[:-1]) - continue + if not numbers: + return spin_groups, channels + # Check for continuation marker (-1) + if numbers[-1] == -1: + spin_groups.extend(numbers[:-1]) + else: # Check for IX=0 marker (indicates channels follow) if 0 in numbers: zero_index = numbers.index(0) spin_groups.extend(numbers[:zero_index]) channels = numbers[zero_index + 1 :] - break - - spin_groups.extend(numbers) + else: + spin_groups.extend(numbers) return spin_groups, channels @@ -399,16 +398,8 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": logger.error(f"{where_am_i}: No parameter lines found") raise ValueError("No parameter lines found") - # Initialize parameters - params = { - "effective_radius": None, - "true_radius": None, - "channel_mode": None, - "vary_effective": None, - "vary_true": None, - "spin_groups": [], - "channels": None, - } + # Initialize list to hold RadiusParameters objects + radius_parameters_list = [] # Parse each line for main parameters and spin groups for line in content_lines: @@ -417,44 +408,44 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": logger.error(f"{where_am_i}: Parameter line too short") raise ValueError("Parameter line too short") - # Parse main parameters if not already set - if params["effective_radius"] is None: - params["effective_radius"] = safe_parse(line[FORMAT_DEFAULT["pareff"]]) - params["true_radius"] = safe_parse(line[FORMAT_DEFAULT["partru"]]) - params["channel_mode"] = safe_parse(line[FORMAT_DEFAULT["ichan"]], as_int=True) or 0 - - # Parse flags - try: - params["vary_effective"] = VaryFlag(int(line[FORMAT_DEFAULT["ifleff"]].strip() or "0")) - params["vary_true"] = VaryFlag(int(line[FORMAT_DEFAULT["ifltru"]].strip() or "0")) - logger.info(f"{where_am_i}: Successfully parsed flags") - except ValueError: - logger.error(f"{where_am_i}: Invalid vary flags") - raise ValueError("Invalid vary flags") + # Parse main parameters + params = { + "effective_radius": safe_parse(line[FORMAT_DEFAULT["pareff"]]), + "true_radius": safe_parse(line[FORMAT_DEFAULT["partru"]]), + "channel_mode": safe_parse(line[FORMAT_DEFAULT["ichan"]], as_int=True) or 0, + } + + # Parse flags + try: + params["vary_effective"] = VaryFlag(int(line[FORMAT_DEFAULT["ifleff"]].strip() or "0")) + params["vary_true"] = VaryFlag(int(line[FORMAT_DEFAULT["ifltru"]].strip() or "0")) + logger.info(f"{where_am_i}: Successfully parsed flags") + except ValueError: + logger.error(f"{where_am_i}: Invalid vary flags") + raise ValueError("Invalid vary flags") # Parse spin groups and channels - spin_groups, channels = cls._parse_spin_groups_and_channels([line]) - params["spin_groups"].extend(spin_groups) - if channels: - params["channels"] = channels + spin_groups, channels = cls._parse_spin_groups_and_channels(line) - if not params["spin_groups"]: - logger.error(f"{where_am_i}: No spin groups found") - raise ValueError("No spin groups found") + if not spin_groups: + logger.error(f"{where_am_i}: No spin groups found") + raise ValueError("No spin groups found") - print(params) - # Create parameters object - try: - parameters = RadiusParameters(**params) - logger.info(f"{where_am_i}: Successfully created radius parameters") + params["spin_groups"] = spin_groups + params["channels"] = channels - except ValueError as e: - logger.error(f"{where_am_i}: Invalid parameter values: {e}") - raise ValueError(f"Invalid parameter values: {e}") + # Create parameters object + try: + parameters = RadiusParameters(**params) + logger.info(f"{where_am_i}: Successfully created radius parameters") + radius_parameters_list.append(parameters) + except ValueError as e: + logger.error(f"{where_am_i}: Invalid parameter values: {e}") + raise ValueError(f"Invalid parameter values: {e}") logger.info(f"{where_am_i}: Successfully parsed radius parameters") - return cls(parameters=parameters) + return cls(parameters=radius_parameters_list) def to_lines(self) -> List[str]: """Convert the card to fixed-width format lines. @@ -467,46 +458,47 @@ def to_lines(self) -> List[str]: logger.info(f"{where_am_i}: Converting radius parameters to lines") lines = [CARD_7_HEADER] - # Format main parameters - main_parts = [ - format_float(self.parameters.effective_radius, width=10), - format_float(self.parameters.true_radius, width=10), - str(self.parameters.channel_mode), - format_vary(self.parameters.vary_effective), - format_vary(self.parameters.vary_true), - ] - - # Add spin groups (up to 28 per line) - spin_groups = self.parameters.spin_groups - spin_group_lines = [] - - current_line = [] - for group in spin_groups: - current_line.append(f"{group:2d}") - if len(current_line) == 28: # Max groups per line + for params in self.parameters: + # Format main parameters + main_parts = [ + format_float(params.effective_radius, width=10), + format_float(params.true_radius, width=10), + str(params.channel_mode), + format_vary(params.vary_effective), + format_vary(params.vary_true), + ] + + # Add spin groups (up to 28 per line) + spin_groups = params.spin_groups + spin_group_lines = [] + + current_line = [] + for group in spin_groups: + current_line.append(f"{group:2d}") + if len(current_line) == 28: # Max groups per line + spin_group_lines.append("".join(current_line)) + current_line = [] + + # Add any remaining groups + if current_line: spin_group_lines.append("".join(current_line)) - current_line = [] - - # Add any remaining groups - if current_line: - spin_group_lines.append("".join(current_line)) - # Combine main parameters with first line of spin groups - if spin_group_lines: - first_line = "".join(main_parts) - if len(spin_group_lines[0]) > 0: - first_line += spin_group_lines[0] - lines.append(first_line) + # Combine main parameters with first line of spin groups + if spin_group_lines: + first_line = "".join(main_parts) + if len(spin_group_lines[0]) > 0: + first_line += spin_group_lines[0] + lines.append(first_line) - # Add remaining spin group lines - lines.extend(spin_group_lines[1:]) + # Add remaining spin group lines + lines.extend(spin_group_lines[1:]) - # Add channels if present - if self.parameters.channels: - channel_line = "0" # IX=0 marker - for channel in self.parameters.channels: - channel_line += f"{channel:2d}" - lines.append(channel_line) + # Add channels if present + if params.channels: + channel_line = "0" # IX=0 marker + for channel in params.channels: + channel_line += f"{channel:2d}" + lines.append(channel_line) # Add trailing blank line lines.append("") @@ -929,9 +921,13 @@ class RadiusCard(BaseModel): Example: # Create new parameters card = RadiusCard( - effective_radius=3.2, - true_radius=3.2, - spin_groups=[1, 2, 3] + parameters=[ + RadiusParameters( + effective_radius=3.2, + true_radius=3.2, + spin_groups=[1, 2, 3] + ) + ] ) # Write in desired format @@ -939,11 +935,11 @@ class RadiusCard(BaseModel): # Or read from template and modify card = RadiusCard.from_lines(template_lines) - card.parameters.effective_radius = 3.5 + card.parameters[0].effective_radius = 3.5 lines = card.to_lines(format=RadiusFormat.DEFAULT) """ - parameters: RadiusParameters + parameters: List[RadiusParameters] # Optional keyword format extras particle_pair: Optional[str] = None @@ -1024,14 +1020,14 @@ def from_lines(cls, lines: List[str]) -> "RadiusCard": keyword_card = RadiusCardKeyword.from_lines(lines) logger.info(f"{where_am_i}: Successfully parsed radius card in keyword format") return cls( - parameters=keyword_card.parameters, + parameters=[keyword_card.parameters], particle_pair=keyword_card.particle_pair, orbital_momentum=keyword_card.orbital_momentum, relative_uncertainty=keyword_card.relative_uncertainty, absolute_uncertainty=keyword_card.absolute_uncertainty, ) elif format_type == RadiusFormat.ALTERNATE: - radius_card = cls(parameters=RadiusCardAlternate.from_lines(lines).parameters) + radius_card = cls(parameters=[RadiusCardAlternate.from_lines(lines).parameters]) logger.info(f"{where_am_i}: Successfully parsed radius card from lines in alternate format") return radius_card else: @@ -1049,17 +1045,23 @@ def to_lines(self, radius_format: RadiusFormat = RadiusFormat.KEYWORD) -> List[s logger.info(f"{where_am_i}: Writing radius card in format: {radius_format}") try: if radius_format == RadiusFormat.KEYWORD: - lines = RadiusCardKeyword( - parameters=self.parameters, - particle_pair=self.particle_pair, - orbital_momentum=self.orbital_momentum, - relative_uncertainty=self.relative_uncertainty, - absolute_uncertainty=self.absolute_uncertainty, - ).to_lines() + lines = [] + for param in self.parameters: + lines.extend(RadiusCardKeyword( + parameters=param, + particle_pair=self.particle_pair, + orbital_momentum=self.orbital_momentum, + relative_uncertainty=self.relative_uncertainty, + absolute_uncertainty=self.absolute_uncertainty, + ).to_lines()) elif radius_format == RadiusFormat.ALTERNATE: - lines = RadiusCardAlternate(parameters=self.parameters).to_lines() + lines = [] + for param in self.parameters: + lines.extend(RadiusCardAlternate(parameters=param).to_lines()) else: - lines = RadiusCardDefault(parameters=self.parameters).to_lines() + lines = [] + for param in self.parameters: + lines.extend(RadiusCardDefault(parameters=[param]).to_lines()) logger.info(f"{where_am_i}: Successfully wrote radius card") return lines @@ -1111,7 +1113,7 @@ def from_values( # Create card with both parameters and extras card = cls( - parameters=RadiusParameters(**params), + parameters=[RadiusParameters(**params)], particle_pair=particle_pair, orbital_momentum=orbital_momentum, relative_uncertainty=relative_uncertainty, From 7860595d07926a3d981a095ff645d481ae9bf839 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 21:00:16 -0700 Subject: [PATCH 29/47] deleted example. --- src/pleiades/sammy/parameters/isotope.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/pleiades/sammy/parameters/isotope.py b/src/pleiades/sammy/parameters/isotope.py index 67917fc..2cb2135 100644 --- a/src/pleiades/sammy/parameters/isotope.py +++ b/src/pleiades/sammy/parameters/isotope.py @@ -108,14 +108,6 @@ class IsotopeParameters(BaseModel): flag (VaryFlag): Treatment flag for abundance (-2=use input, 0=fixed, 1=vary, 3=PUP) spin_groups (List[int]): List of spin group numbers (negative values indicate omitted resonances) - Example: - Standard format line: - "16.000 0.99835 0.00002 0 1 2 3" - ^ ^ ^ ^ spin groups (2 cols each) - ^^ flag - ^^^^^^^ uncertainty - ^^^^^^^ abundance - ^^^^^^^ mass """ mass: float = Field(description="Atomic mass in amu", gt=0) From d528bc1b98f988ccb24cd8180fa0f8fd03a22739 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 21:00:51 -0700 Subject: [PATCH 30/47] added attribustes in doc string of ResonanceEntry --- src/pleiades/sammy/parameters/resonance.py | 31 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/pleiades/sammy/parameters/resonance.py b/src/pleiades/sammy/parameters/resonance.py index f399a50..5e54e6c 100644 --- a/src/pleiades/sammy/parameters/resonance.py +++ b/src/pleiades/sammy/parameters/resonance.py @@ -36,20 +36,45 @@ class UnsupportedFormatError(ValueError): class ResonanceEntry(BaseModel): - """Single resonance entry for SAMMY parameter file (strict format)""" + """This class handles the parameters for a single resonance entry in Card Set 1 of a SAMMY parameter file. + + Single resonance entry for SAMMY parameter file (strict format) + + Attributes: + resonance_energy: Resonance energy Eλ (eV) + capture_width: Capture width Γγ (milli-eV) + + channel1_width: Particle width for channel 1 (milli-eV) + channel2_width: Particle width for channel 2 (milli-eV) + channel3_width: Particle width for channel 3 (milli-eV) + NOTE: If any particle width Γ is negative, SAMMY uses abs(Γ) + for the width and set the associated amplitude γ to be negative. + + vary_energy: Flag indicating if resonance energy is varied + vary_capture_width: Flag indicating if capture width is varied + vary_channel1: Flag indicating if channel 1 width is varied + vary_channel2: Flag indicating if channel 2 width is varied + vary_channel3: Flag indicating if channel 3 width is varied + NOTE: 0 = no, 1 = yes, 3 = PUP (PUP = Partially Unknown Parameter) + + igroup: Quantum numbers group number (or spin_groups) + NOTE: If IGROUP is negative or greater than 50, this resonance will be + omitted from the calculation. + + x_value: Special value used to detect multi-line entries (unsupported) + + """ resonance_energy: float = Field(description="Resonance energy Eλ (eV)") capture_width: float = Field(description="Capture width Γγ (milli-eV)") channel1_width: Optional[float] = Field(None, description="Particle width for channel 1 (milli-eV)") channel2_width: Optional[float] = Field(None, description="Particle width for channel 2 (milli-eV)") channel3_width: Optional[float] = Field(None, description="Particle width for channel 3 (milli-eV)") - vary_energy: VaryFlag = Field(default=VaryFlag.NO) vary_capture_width: VaryFlag = Field(default=VaryFlag.NO) vary_channel1: VaryFlag = Field(default=VaryFlag.NO) vary_channel2: VaryFlag = Field(default=VaryFlag.NO) vary_channel3: VaryFlag = Field(default=VaryFlag.NO) - igroup: int = Field(description="Quantum numbers group number") @classmethod From d128137341fe58c76e2698cacab45a000c85dbf2 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 21:28:15 -0700 Subject: [PATCH 31/47] finally fixed bugs in radius with printing to file! --- src/pleiades/sammy/parameters/radius.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index ed3eadc..da67bd9 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -456,7 +456,7 @@ def to_lines(self) -> List[str]: where_am_i = "RadiusCardDefault.to_lines()" logger.info(f"{where_am_i}: Converting radius parameters to lines") - lines = [CARD_7_HEADER] + lines = [] for params in self.parameters: # Format main parameters @@ -500,9 +500,6 @@ def to_lines(self) -> List[str]: channel_line += f"{channel:2d}" lines.append(channel_line) - # Add trailing blank line - lines.append("") - logger.info(f"{where_am_i}: Successfully converted radius parameters to lines") return lines @@ -681,7 +678,7 @@ def to_lines(self) -> List[str]: """ where_am_i = "RadiusCardAlternate.to_lines()" - lines = [CARD_7_ALT_HEADER] + lines = [] # Format main parameters main_parts = [ @@ -871,7 +868,7 @@ def to_lines(self) -> List[str]: List[str]: Lines in keyword format """ where_am_i = "RadiusCardKeyword.to_lines()" - lines = [CARD_7A_HEADER] + lines = [] # Add radius values if self.parameters.true_radius == self.parameters.effective_radius: @@ -1039,13 +1036,17 @@ def from_lines(cls, lines: List[str]) -> "RadiusCard": logger.error(f"{where_am_i}Failed to parse radius card: {str(e)}\nLines: {lines}") raise ValueError(f"Failed to parse radius card: {str(e)}\nLines: {lines}") - def to_lines(self, radius_format: RadiusFormat = RadiusFormat.KEYWORD) -> List[str]: + def to_lines(self, radius_format: RadiusFormat = RadiusFormat.DEFAULT) -> List[str]: """Write radius card in specified format.""" where_am_i = "RadiusCard.to_lines()" logger.info(f"{where_am_i}: Writing radius card in format: {radius_format}") + + # Print out the radius parameters + print(f"{where_am_i}: Radius parameters: {self.parameters}") + try: if radius_format == RadiusFormat.KEYWORD: - lines = [] + lines = [CARD_7A_HEADER] for param in self.parameters: lines.extend(RadiusCardKeyword( parameters=param, @@ -1055,14 +1056,17 @@ def to_lines(self, radius_format: RadiusFormat = RadiusFormat.KEYWORD) -> List[s absolute_uncertainty=self.absolute_uncertainty, ).to_lines()) elif radius_format == RadiusFormat.ALTERNATE: - lines = [] + lines = [CARD_7_ALT_HEADER] for param in self.parameters: lines.extend(RadiusCardAlternate(parameters=param).to_lines()) else: - lines = [] + lines = [CARD_7_HEADER] for param in self.parameters: lines.extend(RadiusCardDefault(parameters=[param]).to_lines()) + # Add trailing blank line + lines.append("") + logger.info(f"{where_am_i}: Successfully wrote radius card") return lines From e71c678ba6e8ee9c2f052dbd815c363a6dbb2164 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 21:28:45 -0700 Subject: [PATCH 32/47] expanded print function for parameters --- src/pleiades/sammy/parfile.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index b38d0d5..0910c6e 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -110,14 +110,17 @@ def to_string(self) -> str: """ where_am_i = "SammyParameterFile.to_string()" - logger.info(f"{where_am_i}: Attempting to convert parameter file to string format") + logger.info(f"{where_am_i}: Attempting to convert parameters to string format") lines = [] # Process each card type in standard order for card_type in CardOrder: + field_name = CardOrder.get_field_name(card_type) value = getattr(self, field_name) + #print(f"{where_am_i}: Processing card type: {card_type.name} with field name: {field_name} and value: {value}") + # Skip None values (optional cards not present) if value is None: continue @@ -372,5 +375,14 @@ def print_parameters(self) -> None: else: print(f" {value}") + # Print format and header if available + if hasattr(value, 'to_lines') and hasattr(value, 'detect_format'): + lines = value.to_lines() + format_type = value.detect_format(lines) + print(f" Format: {format_type}") + print(f" Header: {lines[0]}") + else: + print(" No format detection available for this card.") + if __name__ == "__main__": print("TODO: usage example for SAMMY parameter file handling") From f305335c0abf6f153fa06384362dfc0d1d152183 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 22:23:45 -0700 Subject: [PATCH 33/47] added notes on parameter libraries. --- src/pleiades/sammy/notes/parfile_notes.md | 63 +++++++++++ .../sammy/parameters/notes/isotope_notes.md | 51 +++++++++ .../sammy/parameters/notes/radius_notes.md | 103 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 src/pleiades/sammy/notes/parfile_notes.md create mode 100644 src/pleiades/sammy/parameters/notes/isotope_notes.md create mode 100644 src/pleiades/sammy/parameters/notes/radius_notes.md diff --git a/src/pleiades/sammy/notes/parfile_notes.md b/src/pleiades/sammy/notes/parfile_notes.md new file mode 100644 index 0000000..6063cb1 --- /dev/null +++ b/src/pleiades/sammy/notes/parfile_notes.md @@ -0,0 +1,63 @@ +# parfile.py + +## Imports +- pathlib +- os +- Enum +- auto +- List +- Optional +- Union +- BaseModel +- Field +- BroadeningParameterCard +- DataReductionCard +- ExternalREntry +- ExternalRFunction +- IsotopeCard +- NormalizationBackgroundCard +- ORRESCard +- ParamagneticParameters +- RadiusCard +- ResonanceCard +- UnusedCorrelatedCard +- UserResolutionParameters +- Logger +- _log_and_raise_error + +## Logger Initialization +- log_file_path +- logger + +## Enums +- CardOrder + - Methods + - get_field_name + +## Classes +- SammyParameterFile + - Attributes + - fudge + - resonance + - external_r + - broadening + - unused_correlated + - normalization + - radius + - data_reduction + - orres + - paramagnetic + - user_resolution + - isotope + - Methods + - to_string + - _get_card_class_with_header + - from_string + - _parse_card + - _get_card_class + - from_file + - to_file + - print_parameters + +## Main Function +- Example usage \ No newline at end of file diff --git a/src/pleiades/sammy/parameters/notes/isotope_notes.md b/src/pleiades/sammy/parameters/notes/isotope_notes.md new file mode 100644 index 0000000..645cadd --- /dev/null +++ b/src/pleiades/sammy/parameters/notes/isotope_notes.md @@ -0,0 +1,51 @@ +# isotope.py + +## File Header +- Shebang: `#!/usr/bin/env python` +- Docstring: Description of the module and format specifications for Card Set 10 + +## Imports +- List +- Optional +- BaseModel +- Field +- model_validator +- VaryFlag +- format_float +- format_vary +- safe_parse +- Logger +- _log_and_raise_error +- os + +## Constants +- FORMAT_STANDARD +- SPIN_GROUP_STANDARD +- FORMAT_EXTENDED +- SPIN_GROUP_EXTENDED +- CARD_10_HEADERS + +## Classes +- IsotopeParameters + - Attributes + - mass + - abundance + - uncertainty + - flag + - spin_groups + - Methods + - validate_groups + - from_lines + - to_lines + +- IsotopeCard + - Attributes + - isotopes + - extended + - Methods + - is_header_line + - from_lines + - to_lines + +## Main Function +- Example usage \ No newline at end of file diff --git a/src/pleiades/sammy/parameters/notes/radius_notes.md b/src/pleiades/sammy/parameters/notes/radius_notes.md new file mode 100644 index 0000000..9453b4a --- /dev/null +++ b/src/pleiades/sammy/parameters/notes/radius_notes.md @@ -0,0 +1,103 @@ +# radius.py + +## Imports +- re +- os +- Enum +- List +- Optional +- Tuple +- Union +- BaseModel +- Field +- field_validator +- model_validator +- VaryFlag +- format_float +- format_vary +- parse_keyword_pairs_to_dict +- safe_parse +- Logger +- _log_and_raise_error + +## Logger Initialization +- log_file_path +- logger + +## Constants +- CARD_7_HEADER +- CARD_7A_HEADER +- CARD_7_ALT_HEADER +- FORMAT_DEFAULT +- FORMAT_ALTERNATE + +## Enums +- RadiusFormat +- OrbitalMomentum + +## Classes +- RadiusParameters + - Attributes + - effective_radius + - true_radius + - channel_mode + - vary_effective + - vary_true + - spin_groups + - channels + - Methods + - validate_spin_groups + - validate_vary_true + - validate_channels + - validate_true_radius_consistency + - __repr__ + +- RadiusCardDefault + - Attributes + - parameters + - Methods + - is_header_line + - _parse_numbers_from_line + - _parse_spin_groups_and_channels + - from_lines + - to_lines + +- RadiusCardAlternate + - Attributes + - parameters + - Methods + - is_header_line + - _parse_numbers_from_line + - _parse_spin_groups_and_channels + - from_lines + - to_lines + +- RadiusCardKeyword + - Attributes + - parameters + - particle_pair + - orbital_momentum + - relative_uncertainty + - absolute_uncertainty + - Methods + - is_header_line + - _parse_values + - from_lines + - to_lines + +- RadiusCard + - Attributes + - parameters + - particle_pair + - orbital_momentum + - relative_uncertainty + - absolute_uncertainty + - Methods + - is_header_line + - detect_format + - from_lines + - to_lines + - from_values + +## Main Function +- Example usage \ No newline at end of file From 842af1f9cf0ffd6612084d816bf30acd7a2a6d37 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 22:24:03 -0700 Subject: [PATCH 34/47] updated debug to info for logging --- src/pleiades/sammy/parfile.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 0910c6e..b395920 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -134,7 +134,7 @@ def to_string(self) -> str: card_lines = value.to_lines() if card_lines: # Only add non-empty line lists lines.extend(card_lines) - logger.debug(f"{where_am_i}: Added lines for {card_type.name} card") + logger.info(f"{where_am_i}: Added lines for {card_type.name} card") # Join all lines with newlines result = "\n".join(lines) @@ -168,7 +168,7 @@ def _get_card_class_with_header(cls, line: str): for card_type, card_class in card_checks: if hasattr(card_class, "is_header_line") and card_class.is_header_line(line): - logger.debug(f"{where_am_i}: card_type:{card_type}\t card_class:{card_class}") + logger.info(f"{where_am_i}: card_type:{card_type}\t card_class:{card_class}") return card_type, card_class logger.info(f"{where_am_i}: No matches found for {line}") @@ -226,7 +226,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Process card with header try: params[CardOrder.get_field_name(card_type)] = card_class.from_lines(group) - logger.info(f"{where_am_i}: Successfully parsed {card_type.name} card") + logger.info(f"{where_am_i}: Successfully parsed {card_type.name} card\n {'-'*80}") except Exception as e: logger.error(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") @@ -236,7 +236,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": if len(group) == 1: try: params["fudge"] = float(group[0]) - logger.info(f"{where_am_i}: Successfully parsed fudge factor") + logger.info(f"{where_am_i}: Successfully parsed fudge factor\n {'-'*80}") except ValueError as e: logger.error(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") @@ -245,12 +245,12 @@ def from_string(cls, content: str) -> "SammyParameterFile": try: # Try parsing as resonance table params["resonance"] = ResonanceCard.from_lines(group) - logger.info(f"{where_am_i}: Successfully parsed resonance table") + logger.info(f"{where_am_i}: Successfully parsed resonance table\n {'-'*80}") except Exception as e: logger.error(f"Failed to parse card without header: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") - logger.info(f"{where_am_i}: Successfully parsed all parameter file content from string") + logger.info(f"{where_am_i}: Successfully parsed all parameter file content from string\n {'='*80}") return cls(**params) @classmethod From b7b6bf4005061f51973adb67ecd0fb44ce994ad6 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 22:24:17 -0700 Subject: [PATCH 35/47] added loggign --- src/pleiades/sammy/parameters/isotope.py | 57 ++++++++++++------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/pleiades/sammy/parameters/isotope.py b/src/pleiades/sammy/parameters/isotope.py index 2cb2135..3c2280a 100644 --- a/src/pleiades/sammy/parameters/isotope.py +++ b/src/pleiades/sammy/parameters/isotope.py @@ -45,13 +45,17 @@ - Masses must be > 0 - Spin groups must be valid for the model """ - -import logging +import os from typing import List, Optional from pydantic import BaseModel, Field, model_validator from pleiades.sammy.parameters.helper import VaryFlag, format_float, format_vary, safe_parse +from pleiades.utils.logger import Logger, _log_and_raise_error + +# Initialize logger with file logging +log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') +logger = Logger(__name__, log_file=log_file_path) # Format definitions for standard format (<99 spin groups) # Each numeric field has specific width requirements @@ -131,15 +135,16 @@ def validate_groups(self) -> "IsotopeParameters": Raises: ValueError: If spin group validation fails """ + where_am_i = "IsotopeParameters.validate_groups()" max_standard = 99 # Maximum group number for standard format for group in self.spin_groups: if group == 0: - raise ValueError("Spin group number cannot be 0") + _log_and_raise_error(logger, f"Spin group number cannot be 0",ValueError) # Check if we need extended format if abs(group) > max_standard: - logging.debug(f"Group number {group} requires extended format") + logger.info(f"{where_am_i}:Group number {group} requires extended format") return self @@ -167,8 +172,12 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet ... ] >>> params = IsotopeParameters.from_lines(lines) """ + where_am_i = "IsotopeParameters.from_lines()" + + logger.info(f"{where_am_i}: Attempting to parse isotope parameters from lines") + if not lines or not lines[0].strip(): - raise ValueError("No valid parameter line provided") + _log_and_raise_error(logger, "No valid parameter line provided", ValueError) # Set format to standard. format_dict = FORMAT_STANDARD # NOTE: EXTENDED format is currently not supported. @@ -181,7 +190,7 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet for field in ["mass", "abundance"]: value = safe_parse(main_line[format_dict[field]]) if value is None: - raise ValueError(f"Failed to parse required field: {field}") + _log_and_raise_error(logger, f"Failed to parse required field: {field}", ValueError) params[field] = value # Parse optional uncertainty @@ -193,6 +202,7 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet try: params["flag"] = VaryFlag(int(flag_str)) except (ValueError, TypeError): + _log_and_raise_error(logger, f"Invalid flag value: {flag_str}", ValueError) params["flag"] = VaryFlag.NO # Parse spin groups @@ -254,15 +264,10 @@ def to_lines(self, extended: bool = False) -> List[str]: Returns: List[str]: Formatted lines with proper fixed-width spacing - - Example: - >>> params = IsotopeParameters(mass=16.0, abundance=0.99835, - ... uncertainty=0.00002, flag=VaryFlag.NO, - ... spin_groups=[1, 2, 3, 4, 5, 6]) - >>> lines = params.to_lines() - >>> print(lines[0]) - "16.000 0.99835 0.00002 0 1 2 3" """ + where_am_i = "IsotopeParameters.to_lines()" + logger.info(f"{where_am_i}: Attempting to convert isotope parameters to lines") + # Select format based on mode group_format = SPIN_GROUP_EXTENDED if extended else SPIN_GROUP_STANDARD format_dict = FORMAT_EXTENDED if extended else FORMAT_STANDARD @@ -316,17 +321,8 @@ class IsotopeCard(BaseModel): isotopes (List[IsotopeParameters]): List of isotope parameter sets extended (bool): Whether to use extended format for >99 spin groups - NOTE: Fixed formats for both standard and extended are defined in the IsotopeParameters class. - - - Example: - >>> lines = [ - ... "ISOTOpic abundances and masses", - ... "16.000 0.99835 0.00002 0 1 2 3", - ... "17.000 0.00165 0.00001 0 4 5 6", - ... "" - ... ] - >>> card = IsotopeCard.from_lines(lines) + NOTE: Fixed formats for both standard and extended are defined in the + IsotopeParameters class. But only using the standard format for now. """ isotopes: List[IsotopeParameters] = Field(default_factory=list) @@ -342,6 +338,8 @@ def is_header_line(cls, line: str) -> bool: Returns: bool: True if the first 5 characters of the line are 'ISOTO' """ + where_am_i = "IsotopeCard.is_header_line()" + logger.info(f"{where_am_i}: Checking if valid header line: {line}") return line.strip().upper().startswith("ISOTO") @classmethod @@ -357,17 +355,20 @@ def from_lines(cls, lines: List[str]) -> "IsotopeCard": Raises: ValueError: If no valid header found or invalid format """ + where_am_i = "IsotopeCard.from_lines()" + logger.info(f"{where_am_i}: Attempting to parse isotope card from lines") + if not lines: - raise ValueError("No lines provided") + _log_and_raise_error(logger, "No lines provided", ValueError) # Validate header if not cls.is_header_line(lines[0]): - raise ValueError(f"Invalid header line: {lines[0]}") + _log_and_raise_error(logger, f"Invalid header line: {lines[0]}", ValueError) # Remove header and trailing blank lines content_lines = [line for line in lines[1:] if line.strip()] if not content_lines: - raise ValueError("No parameter lines found") + _log_and_raise_error(logger, "No parameter lines found", ValueError) # Check if we need extended format extended = False From 429418829c141eeb6c4ce022bdc4742581936b29 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 22:24:44 -0700 Subject: [PATCH 36/47] got rid of RadiusParametersContainer class. --- src/pleiades/sammy/parameters/radius.py | 39 +++---------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index da67bd9..2d5359a 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -238,34 +238,6 @@ def __repr__(self) -> str: ) -class RadiusParametersContainer: - """Container for multiple RadiusParameters entries.""" - - def __init__(self): - self.entries = [] - - def add_parameters(self, params: RadiusParameters): - """Add a RadiusParameters entry to the container.""" - self.entries.append(params) - - def remove_parameters(self, index: int): - """Remove a RadiusParameters entry from the container by index.""" - if 0 <= index < len(self.entries): - self.entries.pop(index) - else: - raise IndexError("Index out of range") - - def print_all_parameters(self): - """Print all RadiusParameters entries in the container.""" - for i, params in enumerate(self.entries): - print(f"Entry {i + 1}:") - print(params) - print() - - def __repr__(self) -> str: - """Return a string representation of the container.""" - return f"RadiusParametersContainer(entries={self.entries})" - #################################################################################################### # Card variant classes for different formats (default, alternate, keyword) #################################################################################################### @@ -298,7 +270,7 @@ def is_header_line(cls, line: str) -> bool: bool: True if line is a valid header """ where_am_i = "RadiusCardDefault.is_header_line()" - logger.debug(f"{where_am_i}: Checking if header line - {line}") + logger.info(f"{where_am_i}: Checking if header line - {line}") return line.strip().upper().startswith("RADIU") @staticmethod @@ -534,7 +506,7 @@ def is_header_line(cls, line: str) -> bool: bool: True if line is a valid header """ where_am_i = "RadiusCardAlternate.is_header_line()" - logger.debug(f"{where_am_i}: Checking if header line - {line}") + logger.info(f"{where_am_i}: Checking if header line - {line}") return line.strip().upper().startswith("RADIU") @staticmethod @@ -959,7 +931,7 @@ def is_header_line(cls, line: str) -> bool: line_upper = line.strip().upper() - logger.debug(f"{where_am_i}: {line_upper}") + logger.info(f"{where_am_i}: {line_upper}") # Check all valid header formats valid_headers = [ @@ -980,7 +952,7 @@ def detect_format(cls, lines: List[str]) -> RadiusFormat: # Grab header from lines (suppose to be the first line) header = lines[0].strip().upper() - logger.debug(f"{where_am_i}: Header line: {header}") + logger.info(f"{where_am_i}: Header line: {header}") # Check for valid header formats # If KEY-WORD is in the header, it is a keyword format @@ -1041,9 +1013,6 @@ def to_lines(self, radius_format: RadiusFormat = RadiusFormat.DEFAULT) -> List[s where_am_i = "RadiusCard.to_lines()" logger.info(f"{where_am_i}: Writing radius card in format: {radius_format}") - # Print out the radius parameters - print(f"{where_am_i}: Radius parameters: {self.parameters}") - try: if radius_format == RadiusFormat.KEYWORD: lines = [CARD_7A_HEADER] From 40068a628b93b1b853db315c4f1fc1fb85a5c47e Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 22:35:08 -0700 Subject: [PATCH 37/47] adding more detials to parfile_notes.md --- src/pleiades/sammy/notes/parfile_notes.md | 73 +++++++++++++++-------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/src/pleiades/sammy/notes/parfile_notes.md b/src/pleiades/sammy/notes/parfile_notes.md index 6063cb1..0edfcc6 100644 --- a/src/pleiades/sammy/notes/parfile_notes.md +++ b/src/pleiades/sammy/notes/parfile_notes.md @@ -31,33 +31,58 @@ ## Enums - CardOrder - - Methods - - get_field_name + - Methods + - get_field_name ## Classes - SammyParameterFile - - Attributes - - fudge - - resonance - - external_r - - broadening - - unused_correlated - - normalization - - radius - - data_reduction - - orres - - paramagnetic - - user_resolution - - isotope - - Methods - - to_string - - _get_card_class_with_header - - from_string - - _parse_card - - _get_card_class - - from_file - - to_file - - print_parameters + - Attributes + - fudge + - resonance + - external_r + - broadening + - unused_correlated + - normalization + - radius + - data_reduction + - orres + - paramagnetic + - user_resolution + - isotope + - Methods + - to_string + - Returns: str + - _get_card_class_with_header + @classmethod + - Parameters: + - header: str + - Returns: Type[BaseModel] + - from_string + @classmethod + - Parameters: + - data: str + - Returns: SammyParameterFile + - _parse_card + @classmethod + - Parameters: + - card_data: str + - Returns: BaseModel + - _get_card_class + @classmethod + - Parameters: + - card_name: str + - Returns: Type[BaseModel] + - from_file + @classmethod + - Parameters: + - file_path: Union[str, pathlib.Path] + - Returns: SammyParameterFile + - to_file + - Parameters: + - file_path: Union[str, pathlib.Path] + - print_parameters + - Parameters: + - None ## Main Function - Example usage \ No newline at end of file From c25dc03e43bb462bef873c25e163e40f4a60cb71 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Fri, 21 Feb 2025 22:36:02 -0700 Subject: [PATCH 38/47] updated notebook for example 12. --- notebook/samexm/ex012/ex012a.ipynb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/notebook/samexm/ex012/ex012a.ipynb b/notebook/samexm/ex012/ex012a.ipynb index 9b6d7cf..9ecbe55 100644 --- a/notebook/samexm/ex012/ex012a.ipynb +++ b/notebook/samexm/ex012/ex012a.ipynb @@ -29,13 +29,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# reading in SAMMY parameters from a parameter file \"./ex012a.par\"\n", - "sammy_params = SammyParameterFile.from_file(\"./ex012a.par\")\n", - "sammy_params.print_parameters()" + "sammy_params = SammyParameterFile.from_file(\"./ex012a.par\")" ] }, { @@ -53,13 +52,6 @@ "source": [ "sammy_params.to_file(\"./ex012b.par\")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 75f9c51675fa11e1f4e91fa240615f9d8e005ea2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 22 Feb 2025 05:38:44 +0000 Subject: [PATCH 39/47] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/repo.map | 6 +-- notebook/samexm/ex012/ex012a.ipynb | 3 -- notebook/samexm/ex012/ex012a.par | 6 +-- src/pleiades/sammy/notes/parfile_notes.md | 6 +-- src/pleiades/sammy/parameters/isotope.py | 39 +++++++------- .../sammy/parameters/notes/isotope_notes.md | 2 +- .../sammy/parameters/notes/radius_notes.md | 2 +- src/pleiades/sammy/parameters/radius.py | 52 ++++++++++--------- src/pleiades/sammy/parameters/resonance.py | 14 ++--- src/pleiades/sammy/parfile.py | 38 +++++++------- src/pleiades/utils/logger.py | 25 +++++---- 11 files changed, 97 insertions(+), 96 deletions(-) diff --git a/docs/repo.map b/docs/repo.map index f8184e8..6823430 100644 --- a/docs/repo.map +++ b/docs/repo.map @@ -29,7 +29,7 @@ PLEIADES | ├── __init__.py | ├── factory.py | ├── parfile.py -| ├── backends/ +| ├── backends/ | │ ├── nova_ornl.py | │ ├── __init__.py | │ ├── local.py @@ -65,7 +65,7 @@ PLEIADES │ │ └── conftest.py │ └── data/ │ ├── config -│ └── ex012 +│ └── ex012 └── notebook/ ├── sammy.param/ │ └── example.ipynb @@ -74,4 +74,4 @@ PLEIADES │ ├── ex012.ipynb │ └── ex012a.par └── ex027/ - └── ex027a.endf \ No newline at end of file + └── ex027a.endf diff --git a/notebook/samexm/ex012/ex012a.ipynb b/notebook/samexm/ex012/ex012a.ipynb index 9ecbe55..71bebb3 100644 --- a/notebook/samexm/ex012/ex012a.ipynb +++ b/notebook/samexm/ex012/ex012a.ipynb @@ -13,9 +13,6 @@ "metadata": {}, "outputs": [], "source": [ - "import pathlib # Importing pathlib to work with file system paths\n", - "import tempfile # Importing tempfile to create temporary files and directories\n", - "\n", "# Importing specific classes from the pleiades.sammy module\n", "from pleiades.sammy.parfile import SammyParameterFile" ] diff --git a/notebook/samexm/ex012/ex012a.par b/notebook/samexm/ex012/ex012a.par index c60437d..a3b0b8c 100755 --- a/notebook/samexm/ex012/ex012a.par +++ b/notebook/samexm/ex012/ex012a.par @@ -160,16 +160,16 @@ 10934820.00 1.2000E+03 2.7611E+05 0 0 0 23 15050371.00 1.2000E+03 6.3499E+05 0 0 0 23 25004488.00 1.2000E+03 6.9186E+03 0 0 0 23 - + 0.1 - + RADIUS PARAMETERS FOLLOW 4.13642 4.13642 0-1 1 4 5 4.94372 4.94372 0-1 2 3 6 7 4.40000 4.40000 0-1 8 91011121314151617 4.20000 4.20000 0-11819202122 4.20000 4.20000 0-123242526272829 - + ISOTOPIC MASSES AND ABUNDANCES FOLLOW 27.976929 1.0000000 .9200 1 1 2 3 4 5 6 7 28.976496 .0467000 .0500 1 8 91011121314151617 diff --git a/src/pleiades/sammy/notes/parfile_notes.md b/src/pleiades/sammy/notes/parfile_notes.md index 0edfcc6..aeeb50f 100644 --- a/src/pleiades/sammy/notes/parfile_notes.md +++ b/src/pleiades/sammy/notes/parfile_notes.md @@ -57,7 +57,7 @@ - Parameters: - header: str - Returns: Type[BaseModel] - - from_string + - from_string @classmethod - Parameters: - data: str @@ -72,7 +72,7 @@ - Parameters: - card_name: str - Returns: Type[BaseModel] - - from_file + - from_file @classmethod - Parameters: - file_path: Union[str, pathlib.Path] @@ -85,4 +85,4 @@ - None ## Main Function -- Example usage \ No newline at end of file +- Example usage diff --git a/src/pleiades/sammy/parameters/isotope.py b/src/pleiades/sammy/parameters/isotope.py index 3c2280a..0ae1171 100644 --- a/src/pleiades/sammy/parameters/isotope.py +++ b/src/pleiades/sammy/parameters/isotope.py @@ -45,6 +45,7 @@ - Masses must be > 0 - Spin groups must be valid for the model """ + import os from typing import List, Optional @@ -54,25 +55,25 @@ from pleiades.utils.logger import Logger, _log_and_raise_error # Initialize logger with file logging -log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') +log_file_path = os.path.join(os.getcwd(), "pleiades-par.log") logger = Logger(__name__, log_file=log_file_path) # Format definitions for standard format (<99 spin groups) # Each numeric field has specific width requirements FORMAT_STANDARD = { - "mass": slice(0, 10), # AMUISO: Atomic mass (amu) - "abundance": slice(10, 20), # PARISO: Fractional abundance - "uncertainty": slice(20, 30), # DELISO: Uncertainty on abundance - "flag": slice(30, 32), # IFLISO: Treatment flag + "mass": slice(0, 10), # AMUISO: Atomic mass (amu) + "abundance": slice(10, 20), # PARISO: Fractional abundance + "uncertainty": slice(20, 30), # DELISO: Uncertainty on abundance + "flag": slice(30, 32), # IFLISO: Treatment flag } # Spin group number positions # Standard format: 2 columns per group starting at col 33 SPIN_GROUP_STANDARD = { - "width": 2, # Character width of each group number - "start": 32, # Start of first group - "per_line": 24, # Max groups per line - "cont_marker": slice(78, 80), # "-1" indicates continuation + "width": 2, # Character width of each group number + "start": 32, # Start of first group + "per_line": 24, # Max groups per line + "cont_marker": slice(78, 80), # "-1" indicates continuation } # Format for extended format (>99 spin groups) @@ -140,7 +141,7 @@ def validate_groups(self) -> "IsotopeParameters": for group in self.spin_groups: if group == 0: - _log_and_raise_error(logger, f"Spin group number cannot be 0",ValueError) + _log_and_raise_error(logger, "Spin group number cannot be 0", ValueError) # Check if we need extended format if abs(group) > max_standard: @@ -179,8 +180,8 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet if not lines or not lines[0].strip(): _log_and_raise_error(logger, "No valid parameter line provided", ValueError) - # Set format to standard. - format_dict = FORMAT_STANDARD # NOTE: EXTENDED format is currently not supported. + # Set format to standard. + format_dict = FORMAT_STANDARD # NOTE: EXTENDED format is currently not supported. main_line = f"{lines[0]:<80}" # Pad to full width @@ -207,7 +208,7 @@ def from_lines(cls, lines: List[str], extended: bool = False) -> "IsotopeParamet # Parse spin groups spin_groups = [] - group_format = SPIN_GROUP_STANDARD #NOTE: EXTENDED format is currently not supported. + group_format = SPIN_GROUP_STANDARD # NOTE: EXTENDED format is currently not supported. # Helper function to parse groups from a line def parse_groups(line: str, start_pos: int = None, continuation: bool = False) -> List[int]: @@ -253,7 +254,7 @@ def parse_groups(line: str, start_pos: int = None, continuation: bool = False) - spin_groups.extend(parse_groups(line, start_pos=0, continuation=True)) params["spin_groups"] = spin_groups - + return cls(**params) def to_lines(self, extended: bool = False) -> List[str]: @@ -321,7 +322,7 @@ class IsotopeCard(BaseModel): isotopes (List[IsotopeParameters]): List of isotope parameter sets extended (bool): Whether to use extended format for >99 spin groups - NOTE: Fixed formats for both standard and extended are defined in the + NOTE: Fixed formats for both standard and extended are defined in the IsotopeParameters class. But only using the standard format for now. """ @@ -378,12 +379,12 @@ def from_lines(cls, lines: List[str]) -> "IsotopeCard": current_lines = [] for line in content_lines: - current_lines.append(line) - + # check if characters 79-80 is a "-1". this means that there are more spin groups in the next line. - if line[78:80] == "-1": continue - + if line[78:80] == "-1": + continue + # Otherwise the are no more lines for spin groups, so process the current lines. else: isotopes.append(IsotopeParameters.from_lines(current_lines, extended=extended)) diff --git a/src/pleiades/sammy/parameters/notes/isotope_notes.md b/src/pleiades/sammy/parameters/notes/isotope_notes.md index 645cadd..7681e13 100644 --- a/src/pleiades/sammy/parameters/notes/isotope_notes.md +++ b/src/pleiades/sammy/parameters/notes/isotope_notes.md @@ -48,4 +48,4 @@ - to_lines ## Main Function -- Example usage \ No newline at end of file +- Example usage diff --git a/src/pleiades/sammy/parameters/notes/radius_notes.md b/src/pleiades/sammy/parameters/notes/radius_notes.md index 9453b4a..9b5d6eb 100644 --- a/src/pleiades/sammy/parameters/notes/radius_notes.md +++ b/src/pleiades/sammy/parameters/notes/radius_notes.md @@ -100,4 +100,4 @@ - from_values ## Main Function -- Example usage \ No newline at end of file +- Example usage diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 2d5359a..0265790 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -5,7 +5,8 @@ for radius parameters in SAMMY parameter files. """ -import re, os +import os +import re from enum import Enum from typing import List, Optional, Tuple, Union @@ -15,7 +16,7 @@ from pleiades.utils.logger import Logger, _log_and_raise_error # Initialize logger with file logging -log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') +log_file_path = os.path.join(os.getcwd(), "pleiades-par.log") logger = Logger(__name__, log_file=log_file_path) #################################################################################################### @@ -47,6 +48,7 @@ # After IX=0 marker, channel numbers use 5 cols each } + class RadiusFormat(Enum): """Supported formats for radius parameter cards.""" @@ -314,7 +316,7 @@ def _parse_spin_groups_and_channels(cls, line: str) -> Tuple[List[int], Optional """ where_am_i = "RadiusCardDefault._parse_spin_groups_and_channels()" logger.info(f"{where_am_i}: Parsing spin groups and channels from line: {line}") - + spin_groups = [] channels = None @@ -416,7 +418,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardDefault": raise ValueError(f"Invalid parameter values: {e}") logger.info(f"{where_am_i}: Successfully parsed radius parameters") - + return cls(parameters=radius_parameters_list) def to_lines(self) -> List[str]: @@ -426,7 +428,7 @@ def to_lines(self) -> List[str]: List[str]: Lines including header """ where_am_i = "RadiusCardDefault.to_lines()" - + logger.info(f"{where_am_i}: Converting radius parameters to lines") lines = [] @@ -928,16 +930,16 @@ def is_header_line(cls, line: str) -> bool: """ # set location for logger where_am_i = "RadiusCard.is_header_line()" - + line_upper = line.strip().upper() logger.info(f"{where_am_i}: {line_upper}") # Check all valid header formats valid_headers = [ - "RADIUS PARAMETERS FOLLOW", # Standard/Alternate fixed width - "RADII ARE IN KEY-WORD FORMAT", # Keyword format - "CHANNEL RADIUS PARAMETERS FOLLOW", # Alternative keyword format + "RADIUS PARAMETERS FOLLOW", # Standard/Alternate fixed width + "RADII ARE IN KEY-WORD FORMAT", # Keyword format + "CHANNEL RADIUS PARAMETERS FOLLOW", # Alternative keyword format ] return any(header in line_upper for header in valid_headers) @@ -959,17 +961,17 @@ def detect_format(cls, lines: List[str]) -> RadiusFormat: if "KEY-WORD" in header: logger.info(f"{where_am_i}: Detected keyword format!") return RadiusFormat.KEYWORD - + # If DEFAULT or ALTERNATE card formats then "RADIUS" should be in the header elif "RADIU" in header: # Check format by examining spin group columns content_line = next((l for l in lines[1:] if l.strip()), "") # noqa: E741 - + # Check for alternate format (5-column integer values) if len(content_line) >= 35 and content_line[25:30].strip(): # 5-col format logger.info(f"{where_am_i}: Detected ALTERNATE format based on {content_line}") return RadiusFormat.ALTERNATE - + logger.info(f"{where_am_i}: Detected DEFAULT format based on {content_line}") return RadiusFormat.DEFAULT @@ -983,7 +985,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCard": logger.info(f"{where_am_i}: Attempting to parse radius card from lines") format_type = cls.detect_format(lines) - # Try reading in the radius card based on the determined format + # Try reading in the radius card based on the determined format try: if format_type == RadiusFormat.KEYWORD: keyword_card = RadiusCardKeyword.from_lines(lines) @@ -1003,7 +1005,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCard": radius_card = cls(parameters=RadiusCardDefault.from_lines(lines).parameters) logger.info(f"{where_am_i}: Successfully parsed radius card from lines in default format") return radius_card - + except Exception as e: logger.error(f"{where_am_i}Failed to parse radius card: {str(e)}\nLines: {lines}") raise ValueError(f"Failed to parse radius card: {str(e)}\nLines: {lines}") @@ -1017,13 +1019,15 @@ def to_lines(self, radius_format: RadiusFormat = RadiusFormat.DEFAULT) -> List[s if radius_format == RadiusFormat.KEYWORD: lines = [CARD_7A_HEADER] for param in self.parameters: - lines.extend(RadiusCardKeyword( - parameters=param, - particle_pair=self.particle_pair, - orbital_momentum=self.orbital_momentum, - relative_uncertainty=self.relative_uncertainty, - absolute_uncertainty=self.absolute_uncertainty, - ).to_lines()) + lines.extend( + RadiusCardKeyword( + parameters=param, + particle_pair=self.particle_pair, + orbital_momentum=self.orbital_momentum, + relative_uncertainty=self.relative_uncertainty, + absolute_uncertainty=self.absolute_uncertainty, + ).to_lines() + ) elif radius_format == RadiusFormat.ALTERNATE: lines = [CARD_7_ALT_HEADER] for param in self.parameters: @@ -1032,13 +1036,13 @@ def to_lines(self, radius_format: RadiusFormat = RadiusFormat.DEFAULT) -> List[s lines = [CARD_7_HEADER] for param in self.parameters: lines.extend(RadiusCardDefault(parameters=[param]).to_lines()) - + # Add trailing blank line lines.append("") - + logger.info(f"{where_am_i}: Successfully wrote radius card") return lines - + except Exception as e: logger.error(f"{where_am_i}: Failed to write radius card: {str(e)}") raise ValueError(f"Failed to write radius card: {str(e)}") diff --git a/src/pleiades/sammy/parameters/resonance.py b/src/pleiades/sammy/parameters/resonance.py index 5e54e6c..07991ab 100644 --- a/src/pleiades/sammy/parameters/resonance.py +++ b/src/pleiades/sammy/parameters/resonance.py @@ -37,9 +37,9 @@ class UnsupportedFormatError(ValueError): class ResonanceEntry(BaseModel): """This class handles the parameters for a single resonance entry in Card Set 1 of a SAMMY parameter file. - + Single resonance entry for SAMMY parameter file (strict format) - + Attributes: resonance_energy: Resonance energy Eλ (eV) capture_width: Capture width Γγ (milli-eV) @@ -47,10 +47,10 @@ class ResonanceEntry(BaseModel): channel1_width: Particle width for channel 1 (milli-eV) channel2_width: Particle width for channel 2 (milli-eV) channel3_width: Particle width for channel 3 (milli-eV) - NOTE: If any particle width Γ is negative, SAMMY uses abs(Γ) + NOTE: If any particle width Γ is negative, SAMMY uses abs(Γ) for the width and set the associated amplitude γ to be negative. - vary_energy: Flag indicating if resonance energy is varied + vary_energy: Flag indicating if resonance energy is varied vary_capture_width: Flag indicating if capture width is varied vary_channel1: Flag indicating if channel 1 width is varied vary_channel2: Flag indicating if channel 2 width is varied @@ -58,11 +58,11 @@ class ResonanceEntry(BaseModel): NOTE: 0 = no, 1 = yes, 3 = PUP (PUP = Partially Unknown Parameter) igroup: Quantum numbers group number (or spin_groups) - NOTE: If IGROUP is negative or greater than 50, this resonance will be + NOTE: If IGROUP is negative or greater than 50, this resonance will be omitted from the calculation. - + x_value: Special value used to detect multi-line entries (unsupported) - + """ resonance_energy: float = Field(description="Resonance energy Eλ (eV)") diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index b395920..a81b841 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -1,7 +1,8 @@ #!/usr/bin/env python """Top level parameter file handler for SAMMY.""" -import pathlib, os +import os +import pathlib from enum import Enum, auto from typing import List, Optional, Union @@ -21,13 +22,13 @@ UnusedCorrelatedCard, UserResolutionParameters, ) - from pleiades.utils.logger import Logger, _log_and_raise_error # Initialize logger with file logging -log_file_path = os.path.join(os.getcwd(), 'pleiades-par.log') +log_file_path = os.path.join(os.getcwd(), "pleiades-par.log") logger = Logger(__name__, log_file=log_file_path) + class CardOrder(Enum): """Defines the standard order of cards in SAMMY parameter files. @@ -115,11 +116,10 @@ def to_string(self) -> str: # Process each card type in standard order for card_type in CardOrder: - field_name = CardOrder.get_field_name(card_type) value = getattr(self, field_name) - #print(f"{where_am_i}: Processing card type: {card_type.name} with field name: {field_name} and value: {value}") + # print(f"{where_am_i}: Processing card type: {card_type.name} with field name: {field_name} and value: {value}") # Skip None values (optional cards not present) if value is None: @@ -185,7 +185,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": SammyParameterFile: Parsed parameter file object. """ where_am_i = "SammyParameterFile.from_string()" - + # Split content into lines lines = content.splitlines() @@ -205,18 +205,18 @@ def from_string(cls, content: str) -> "SammyParameterFile": current_group.append(line) else: # Only add non-empty groups - if current_group: + if current_group: card_groups.append(current_group) current_group = [] # Don't forget last group - if current_group: card_groups.append(current_group) + if current_group: + card_groups.append(current_group) # Process each group of lines for group in card_groups: - # Skip empty groups - if not group: + if not group: continue # Check first line for header to determine card type @@ -231,7 +231,6 @@ def from_string(cls, content: str) -> "SammyParameterFile": logger.error(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") else: - # check if group if fudge factor if len(group) == 1: try: @@ -241,7 +240,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": logger.error(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") else: - #check if it's a resonance table + # check if it's a resonance table try: # Try parsing as resonance table params["resonance"] = ResonanceCard.from_lines(group) @@ -259,9 +258,9 @@ def _parse_card(cls, card_type: CardOrder, lines: List[str]): where_am_i = "SammyParameterFile._parse_card()" logger.info(f"{where_am_i}: Attempting to parse card of type: {card_type.name}") - + card_class = cls._get_card_class(card_type) - + if not card_class: _log_and_raise_error(f"No parser implemented for card type: {card_type}", ValueError) @@ -309,7 +308,7 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": where_am_i = "SammyParameterFile.from_file()" filepath = pathlib.Path(filepath) - + logger.info(f"{where_am_i}: Attempting to read parameter file from: {filepath}") if not filepath.exists(): @@ -319,7 +318,7 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": content = filepath.read_text() logger.info(f"{where_am_i}: Successfully read content in file: {filepath}") return cls.from_string(content) - + except UnicodeDecodeError as e: _log_and_raise_error(f"Failed to read parameter file - invalid encoding: {e}", ValueError) except Exception as e: @@ -354,9 +353,9 @@ def to_file(self, filepath: Union[str, pathlib.Path]) -> None: def print_parameters(self) -> None: """Print the details of the parameter file.""" - + logger.info("SammyParameterFile.print_parameters(): Printing out Sammy Parameter Detials") - + print("Sammy Parameter File Details:") # check if any cards are present @@ -376,7 +375,7 @@ def print_parameters(self) -> None: print(f" {value}") # Print format and header if available - if hasattr(value, 'to_lines') and hasattr(value, 'detect_format'): + if hasattr(value, "to_lines") and hasattr(value, "detect_format"): lines = value.to_lines() format_type = value.detect_format(lines) print(f" Format: {format_type}") @@ -384,5 +383,6 @@ def print_parameters(self) -> None: else: print(" No format detection available for this card.") + if __name__ == "__main__": print("TODO: usage example for SAMMY parameter file handling") diff --git a/src/pleiades/utils/logger.py b/src/pleiades/utils/logger.py index d2b0fbf..fb0198c 100644 --- a/src/pleiades/utils/logger.py +++ b/src/pleiades/utils/logger.py @@ -1,26 +1,27 @@ import logging -import inspect + def _log_and_raise_error(logger, message: str, exception_class: Exception): """ Log an error message and raise an exception. - + Args: logger (logging.Logger): The logger instance to use for logging the error. message (str): The error message to log and raise. exception_class (Exception): The class of the exception to raise. - + Raises: exception_class: The exception with the provided message. """ logger.error(message) raise exception_class(message) + class Logger: def __init__(self, name: str, level: int = logging.DEBUG, log_file: str = None): """ Initialize a Logger instance. - + Args: name (str): The name of the logger. level (int): The logging level (default is logging.DEBUG). @@ -28,11 +29,11 @@ def __init__(self, name: str, level: int = logging.DEBUG, log_file: str = None): """ self.logger = logging.getLogger(name) self.logger.setLevel(level) - + # Create file handler if log_file is specified if log_file: file_handler = logging.FileHandler(log_file) - file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") file_handler.setFormatter(file_formatter) self.logger.addHandler(file_handler) @@ -42,7 +43,7 @@ def __init__(self, name: str, level: int = logging.DEBUG, log_file: str = None): def debug(self, message: str): """ Log a debug message. - + Args: message (str): The debug message to log. """ @@ -51,7 +52,7 @@ def debug(self, message: str): def info(self, message: str): """ Log an info message. - + Args: message (str): The info message to log. """ @@ -60,7 +61,7 @@ def info(self, message: str): def warning(self, message: str): """ Log a warning message. - + Args: message (str): The warning message to log. """ @@ -69,7 +70,7 @@ def warning(self, message: str): def error(self, message: str): """ Log an error message. - + Args: message (str): The error message to log. """ @@ -78,10 +79,8 @@ def error(self, message: str): def critical(self, message: str): """ Log a critical message. - + Args: message (str): The critical message to log. """ self.logger.critical(message) - - \ No newline at end of file From c4d7cb5735fb9921aaeff875b2f584bcfc3ef86a Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Sat, 22 Feb 2025 09:48:43 -0700 Subject: [PATCH 40/47] got rid of testing abundance validation, since this is no longer true. Deleted extended isotope format since this is unsupported. --- tests/unit/pleiades/sammy/parameters/test_isotope.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/unit/pleiades/sammy/parameters/test_isotope.py b/tests/unit/pleiades/sammy/parameters/test_isotope.py index 4bb0a29..da17a82 100644 --- a/tests/unit/pleiades/sammy/parameters/test_isotope.py +++ b/tests/unit/pleiades/sammy/parameters/test_isotope.py @@ -111,17 +111,6 @@ def test_multiple_isotopes(self, multi_isotope_lines): assert len(card.isotopes) == 2 assert sum(iso.abundance for iso in card.isotopes) <= 1.0 - def test_extended_format(self, extended_format_lines): - """Test parsing extended format data.""" - card = IsotopeCard.from_lines(extended_format_lines) - assert card.extended - assert card.isotopes[0].spin_groups[0] == 101 - - def test_abundance_validation(self): - """Test total abundance validation.""" - with pytest.raises(ValueError, match="Total abundance .* exceeds 1.0"): - IsotopeCard(isotopes=[IsotopeParameters(mass=16.0, abundance=0.6), IsotopeParameters(mass=17.0, abundance=0.6)]) - def test_roundtrip(self, single_isotope_lines): """Test parsing and re-generating gives equivalent results.""" card = IsotopeCard.from_lines(single_isotope_lines) From b0d97d871924f07b77e6909d2a64e93b4cd1ef78 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Sat, 22 Feb 2025 09:49:45 -0700 Subject: [PATCH 41/47] Fixed bug in IsotopeCard.is_header_line(). Can now identify both "ISOTO" and "NUCLI" --- src/pleiades/sammy/parameters/isotope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pleiades/sammy/parameters/isotope.py b/src/pleiades/sammy/parameters/isotope.py index 3c2280a..9204b8c 100644 --- a/src/pleiades/sammy/parameters/isotope.py +++ b/src/pleiades/sammy/parameters/isotope.py @@ -340,7 +340,7 @@ def is_header_line(cls, line: str) -> bool: """ where_am_i = "IsotopeCard.is_header_line()" logger.info(f"{where_am_i}: Checking if valid header line: {line}") - return line.strip().upper().startswith("ISOTO") + return line.strip().upper().startswith("ISOTO") or line.strip().upper().startswith("NUCLI") @classmethod def from_lines(cls, lines: List[str]) -> "IsotopeCard": From 15281393da53d82762e1fbd8603beb28ea4b5373 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Sat, 22 Feb 2025 10:14:30 -0700 Subject: [PATCH 42/47] adjusted functions test_radius.py for new list object for RadiusCard. --- .../pleiades/sammy/parameters/test_radius.py | 83 +++++++++++-------- 1 file changed, 50 insertions(+), 33 deletions(-) diff --git a/tests/unit/pleiades/sammy/parameters/test_radius.py b/tests/unit/pleiades/sammy/parameters/test_radius.py index f25ab85..2ef0efe 100644 --- a/tests/unit/pleiades/sammy/parameters/test_radius.py +++ b/tests/unit/pleiades/sammy/parameters/test_radius.py @@ -73,14 +73,21 @@ def test_basic_fixed_width_format(): # Parse the input card = RadiusCard.from_lines(input_str.splitlines()) + # Verify that there is only one set of parameters in the card + assert len(card.parameters) == 1 + + # Access the RadiusParameters object in the parameters list + params = card.parameters[0] + + # Verify parsed values - assert card.parameters.effective_radius == pytest.approx(3.200) - assert card.parameters.true_radius == pytest.approx(3.200) - assert card.parameters.channel_mode == 0 - assert card.parameters.vary_effective == VaryFlag.YES # 1 - assert card.parameters.vary_true == VaryFlag.USE_FROM_PARFILE # -1 - assert card.parameters.spin_groups == [1, 2, 3] - assert card.parameters.channels is None # No channels specified when mode=0 + assert params.effective_radius == pytest.approx(3.200) + assert params.true_radius == pytest.approx(3.200) + assert params.channel_mode == 0 + assert params.vary_effective == VaryFlag.YES # 1 + assert params.vary_true == VaryFlag.USE_FROM_PARFILE # -1 + assert params.spin_groups == [1, 2, 3] + assert params.channels is None # No channels specified when mode=0 # Test writing back to fixed-width format output_lines = card.to_lines(radius_format=RadiusFormat.DEFAULT) @@ -100,7 +107,7 @@ def test_basic_fixed_width_format(): assert content_line[26:28] == " 2" # Second spin group assert content_line[28:30] == " 3" # Third spin group - +@pytest.mark.skip(reason="Alternate fixed-width format is unsupported at the moment") def test_alternate_fixed_width_format(): """Test alternate fixed-width format parsing (for >=99 spin groups).""" @@ -152,7 +159,7 @@ def test_alternate_fixed_width_format(): assert content_line[40:45] == " 102" # Second spin group (5 cols) assert content_line[45:50] == " 103" # Third spin group (5 cols) - +@pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_basic_radius_keyword_format(): """Test basic keyword format parsing with single radius value.""" @@ -182,7 +189,7 @@ def test_basic_radius_keyword_format(): assert any(line.startswith("Radius= 3.2") for line in output_lines) assert any(line.startswith("Group= 1") for line in output_lines) - +@pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_separate_radii_keyword_format(): """Test keyword format parsing with different effective/true radius values.""" @@ -206,7 +213,7 @@ def test_separate_radii_keyword_format(): print("\nGenerated output:") print("\n".join(output_lines)) - +@pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_uncertainties_keyword_format(): """Test keyword format parsing with uncertainty specifications.""" @@ -232,7 +239,7 @@ def test_uncertainties_keyword_format(): assert any(line.startswith("Relative= 0.05") for line in output_lines) assert any(line.startswith("Absolute= 0.002") for line in output_lines) - +@pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_particle_pair_keyword_format(): """Test keyword format parsing with particle pair and orbital momentum.""" @@ -256,7 +263,7 @@ def test_particle_pair_keyword_format(): assert any(line.startswith("PP= n+16O") for line in output_lines) assert any("L= all" in line for line in output_lines) - +@pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_groups_channels_keyword_format(): """Test keyword format parsing with group and channel specifications.""" @@ -280,7 +287,7 @@ def test_groups_channels_keyword_format(): print("\nGenerated output:") print("\n".join(output_lines)) - +@pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_invalid_keyword_format(): """Test error handling for invalid keyword format.""" @@ -308,16 +315,19 @@ def test_minimal_radius_creation(): # Create with just effective radius and spin groups card = RadiusCard.from_values(effective_radius=3.200, spin_groups=[1, 2, 3]) - print(f"Card parameters: {card.parameters}") + # Verify that there is only one set of parameters in the card + assert len(card.parameters) == 1 + + print(f"Card parameters: {card.parameters[0]}") # Verify defaults - assert card.parameters.effective_radius == pytest.approx(3.200) - assert card.parameters.true_radius == pytest.approx(3.200) # Should equal effective_radius - assert card.parameters.spin_groups == [1, 2, 3] - assert card.parameters.channel_mode == 0 # Default - assert card.parameters.vary_effective == VaryFlag.NO # Default - assert card.parameters.vary_true == VaryFlag.NO # Default - assert card.parameters.channels is None # Default + assert card.parameters[0].effective_radius == pytest.approx(3.200) + assert card.parameters[0].true_radius == pytest.approx(3.200) # Should equal effective_radius + assert card.parameters[0].spin_groups == [1, 2, 3] + assert card.parameters[0].channel_mode == 0 # Default + assert card.parameters[0].vary_effective == VaryFlag.NO # Default + assert card.parameters[0].vary_true == VaryFlag.NO # Default + assert card.parameters[0].channels is None # Default def test_full_radius_creation(): @@ -333,16 +343,19 @@ def test_full_radius_creation(): vary_true=VaryFlag.PUP, ) - print(f"Card parameters: {card.parameters}") + # Verify that there is only one set of parameters in the card + assert len(card.parameters) == 1 + + print(f"Card parameters: {card.parameters[0]}") # Verify all parameters - assert card.parameters.effective_radius == pytest.approx(3.200) - assert card.parameters.true_radius == pytest.approx(3.400) - assert card.parameters.spin_groups == [1, 2] - assert card.parameters.channel_mode == 1 # Auto-set when channels provided - assert card.parameters.channels == [1, 2, 3] - assert card.parameters.vary_effective == VaryFlag.YES - assert card.parameters.vary_true == VaryFlag.PUP + assert card.parameters[0].effective_radius == pytest.approx(3.200) + assert card.parameters[0].true_radius == pytest.approx(3.400) + assert card.parameters[0].spin_groups == [1, 2] + assert card.parameters[0].channel_mode == 1 # Auto-set when channels provided + assert card.parameters[0].channels == [1, 2, 3] + assert card.parameters[0].vary_effective == VaryFlag.YES + assert card.parameters[0].vary_true == VaryFlag.PUP def test_radius_with_extras(): @@ -365,10 +378,14 @@ def test_radius_with_extras(): f"uncertainties={card.relative_uncertainty}, {card.absolute_uncertainty}" ) + # Verify that there is only one set of parameters in the card + assert len(card.parameters) == 1 + + # Verify core parameters - assert card.parameters.effective_radius == pytest.approx(3.200) - assert card.parameters.true_radius == pytest.approx(3.200) - assert card.parameters.spin_groups == [1] + assert card.parameters[0].effective_radius == pytest.approx(3.200) + assert card.parameters[0].true_radius == pytest.approx(3.200) + assert card.parameters[0].spin_groups == [1] # Verify extras assert card.particle_pair == "n+16O" From 9cd925324bb3bff5f7d6d4b996cd6cc0859c42ff Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Sat, 22 Feb 2025 14:37:10 -0700 Subject: [PATCH 43/47] adjusted test_full_roundtrip to handle list of radiusParameters --- tests/unit/pleiades/sammy/test_parfile.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/unit/pleiades/sammy/test_parfile.py b/tests/unit/pleiades/sammy/test_parfile.py index f866eca..5c5de31 100644 --- a/tests/unit/pleiades/sammy/test_parfile.py +++ b/tests/unit/pleiades/sammy/test_parfile.py @@ -59,6 +59,8 @@ def test_parse_single_card(single_card_input): """Test parsing file with fudge factor and single broadening card.""" parfile = SammyParameterFile.from_string(single_card_input) + print(parfile) + # Check fudge factor assert parfile.fudge == pytest.approx(0.1) @@ -267,10 +269,15 @@ def test_full_roundtrip(self, temp_dir): print(loaded_parfile.radius) assert loaded_parfile.radius is not None assert orig_parfile.radius is not None - assert loaded_parfile.radius.parameters.effective_radius == pytest.approx(orig_parfile.radius.parameters.effective_radius) - assert loaded_parfile.radius.parameters.true_radius == pytest.approx(orig_parfile.radius.parameters.true_radius) - assert loaded_parfile.radius.parameters.spin_groups == orig_parfile.radius.parameters.spin_groups - assert loaded_parfile.radius.parameters.vary_effective == orig_parfile.radius.parameters.vary_effective + + # both original and loaded radius parameters should be length 1 + assert len(loaded_parfile.radius.parameters) == 1 + assert len(orig_parfile.radius.parameters) == 1 + + assert loaded_parfile.radius.parameters[0].effective_radius == pytest.approx(orig_parfile.radius.parameters[0].effective_radius) + assert loaded_parfile.radius.parameters[0].true_radius == pytest.approx(orig_parfile.radius.parameters[0].true_radius) + assert loaded_parfile.radius.parameters[0].spin_groups == orig_parfile.radius.parameters[0].spin_groups + assert loaded_parfile.radius.parameters[0].vary_effective == orig_parfile.radius.parameters[0].vary_effective # Compare data reduction parameters assert loaded_parfile.data_reduction is not None From fa9bd689b584c5095a9dba83cc9f2ee28adc678e Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Sat, 22 Feb 2025 14:37:37 -0700 Subject: [PATCH 44/47] fixed bug in reading in parFile strings. --- src/pleiades/sammy/parfile.py | 144 ++++++++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 26 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index a81b841..7977f09 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -28,6 +28,40 @@ log_file_path = os.path.join(os.getcwd(), "pleiades-par.log") logger = Logger(__name__, log_file=log_file_path) +# Header lines for card sets in the PARameter file +# NOTE: If a card is not implemented, the value is None +parFile_header_card_class = { + "EXTERnal R-function parameters follow": ExternalRFunction, + "R-EXTernal parameters follow": ExternalRFunction, + "BROADening parameters may be varied": BroadeningParameterCard, + "UNUSEd but correlated variables": UnusedCorrelatedCard, + "NORMAlization and background": NormalizationBackgroundCard, + "RADIUs parameters follow": RadiusCard, + "RADII are in KEY-WORD format": RadiusCard, + "CHANNel radius parameters follow": RadiusCard, + "DATA reduction parameters are next": DataReductionCard, + "ORRES": ORRESCard, + "ISOTOpic abundances and masses": IsotopeCard, + "NUCLIde abundances and masses": IsotopeCard, + "MISCEllaneous parameters follow": None, # Not implemented + "PARAMagnetic cross section parameters follow": ParamagneticParameters, + "BACKGround functions": None, # Not implemented + "RPI Resolution function": None, # Not implemented + "GEEL resolution function": None, # Not implemented + "GELINa resolution": None, # Not implemented + "NTOF resolution function": None, # Not implemented + "RPI Transmission resolution function": None, # Not implemented + "RPI Capture resolution function": None, # Not implemented + "GEEL DEFAUlts": None, # Not implemented + "GELINa DEFAUlts": None, # Not implemented + "NTOF DEFAUlts": None, # Not implemented + "DETECtor efficiencies": None, # Not implemented + "USER-Defined resolution function": UserResolutionParameters, + "COVARiance matrix is in binary form in another file": None, # Not implemented + "EXPLIcit uncertainties and correlations follow": None, # Not implemented + "RELATive uncertainties follow": None, # Not implemented + "PRIOR uncertainties follow in key-word format": None, # Not implemented + } class CardOrder(Enum): """Defines the standard order of cards in SAMMY parameter files. @@ -111,7 +145,7 @@ def to_string(self) -> str: """ where_am_i = "SammyParameterFile.to_string()" - logger.info(f"{where_am_i}: Attempting to convert parameters to string format") + #logger.info(f"{where_am_i}: Attempting to convert parameters to string format") lines = [] # Process each card type in standard order @@ -119,7 +153,7 @@ def to_string(self) -> str: field_name = CardOrder.get_field_name(card_type) value = getattr(self, field_name) - # print(f"{where_am_i}: Processing card type: {card_type.name} with field name: {field_name} and value: {value}") + print(f"{where_am_i}: Processing card type: {card_type.name} with field name: {field_name} and value: {value}") # Skip None values (optional cards not present) if value is None: @@ -153,6 +187,7 @@ def _get_card_class_with_header(cls, line: str): """ where_am_i = "SammyParameterFile._get_card_class_with_header()" + # Check each card class for header line match card_checks = [ (CardOrder.BROADENING, BroadeningParameterCard), (CardOrder.DATA_REDUCTION, DataReductionCard), @@ -189,13 +224,76 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Split content into lines lines = content.splitlines() + logger.info(f"{where_am_i}: Attempting to parse parameter file content from string {lines}") + # Early exit for empty content if not lines: - _log_and_raise_error("Empty parameter file content", ValueError) + logger.error(f"{where_am_i}: Empty parameter file content") + raise ValueError("Empty parameter file content") # Initialize parameters params = {} + resonance_or_fudge_factor_content = [] + + # read and append lines to resonance_or_fudge_factor_content until we find a header line + for line in lines: + + # Check if line matches any keys in parFile_header_card_class + if line in parFile_header_card_class.keys(): + card_type, card_class = cls._get_card_class_with_header(line) + + # if so then you have pasted the resonances and fudge factor + if card_class: + break + # if not then append the line to resonance_or_fudge_factor_content + else: + resonance_or_fudge_factor_content.append(line) + + logger.info(f"{where_am_i}: res_fudge_content: {resonance_or_fudge_factor_content}") + + # need to split further into resonances and fudge factor + resonances_entries = [] + fudge_factor = None + + for line in resonance_or_fudge_factor_content: + # check if anycharacters exsist beyound 1-11 + if line[11:].strip(): + # if so, then it is a resonance entry + resonances_entries.append(line) + # check if line is just newline char + elif not line.strip(): + continue + + # Otherwise it is a fudge factor + else: + # strip line of "\n" and convert to float + fudge_factor = line.strip() + + logger.info(f"{where_am_i}: resonances_entries: {resonances_entries}") + logger.info(f"{where_am_i}: fudge_factor: {fudge_factor}") + + # attempt to assign fudge factor to params + if fudge_factor: + try: + params["fudge"] = float(fudge_factor) + logger.info(f"{where_am_i}: Successfully parsed fudge factor\n {'-'*80}") + except ValueError as e: + logger.error(f"Failed to parse fudge factor: {str(e)}\nLines: {fudge_factor}") + raise ValueError(f"Failed to parse fudge factor: {str(e)}\nLines: {fudge_factor}") + + # if resonance_entries is not empty then attempt to assign to params + if resonances_entries: + try: + params["resonance"] = ResonanceCard.from_lines(resonances_entries) + logger.info(f"{where_am_i}: Successfully parsed resonance table\n {'-'*80}") + except Exception as e: + logger.error(f"Failed to parse resonance table: {str(e)}\nLines: {resonances_entries}") + raise ValueError(f"Failed to parse resonance table: {str(e)}\nLines: {resonances_entries}") + + # remove the resonance_or_fudge_factor_content from the lines + lines = lines[len(resonance_or_fudge_factor_content):] + # Second, partition lines into group of lines based on blank lines card_groups = [] current_group = [] @@ -222,6 +320,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Check first line for header to determine card type card_type, card_class = cls._get_card_class_with_header(group[0]) + # Check if card type is implemented if card_class: # Process card with header try: @@ -230,24 +329,11 @@ def from_string(cls, content: str) -> "SammyParameterFile": except Exception as e: logger.error(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") + + # If card type is not implemented, then throw an error stating card type is not implemented yet else: - # check if group if fudge factor - if len(group) == 1: - try: - params["fudge"] = float(group[0]) - logger.info(f"{where_am_i}: Successfully parsed fudge factor\n {'-'*80}") - except ValueError as e: - logger.error(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") - raise ValueError(f"Failed to parse fudge factor: {str(e)}\nLines: {group}") - else: - # check if it's a resonance table - try: - # Try parsing as resonance table - params["resonance"] = ResonanceCard.from_lines(group) - logger.info(f"{where_am_i}: Successfully parsed resonance table\n {'-'*80}") - except Exception as e: - logger.error(f"Failed to parse card without header: {str(e)}\nLines: {group}") - raise ValueError(f"Failed to parse card without header: {str(e)}\nLines: {group}") + logger.error(f"{where_am_i}: Card type not implemented: {group[0]}") + raise ValueError(f"Card type not implemented: {group[0]}") logger.info(f"{where_am_i}: Successfully parsed all parameter file content from string\n {'='*80}") return cls(**params) @@ -262,7 +348,8 @@ def _parse_card(cls, card_type: CardOrder, lines: List[str]): card_class = cls._get_card_class(card_type) if not card_class: - _log_and_raise_error(f"No parser implemented for card type: {card_type}", ValueError) + logger.error(f"{where_am_i}: No parser implemented for card type: {card_type}") + raise ValueError(f"No parser implemented for card type: {card_type}") try: parsed_card = card_class.from_lines(lines) @@ -312,7 +399,8 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": logger.info(f"{where_am_i}: Attempting to read parameter file from: {filepath}") if not filepath.exists(): - _log_and_raise_error(f"Parameter file not found: {filepath}", FileNotFoundError) + logger.error(f"{where_am_i}: Parameter file not found: {filepath}") + raise FileNotFoundError(f"Parameter file not found: {filepath}") try: content = filepath.read_text() @@ -320,9 +408,11 @@ def from_file(cls, filepath: Union[str, pathlib.Path]) -> "SammyParameterFile": return cls.from_string(content) except UnicodeDecodeError as e: - _log_and_raise_error(f"Failed to read parameter file - invalid encoding: {e}", ValueError) + logger.error(f"{where_am_i}: Failed to read parameter file - invalid encoding: {e}") + raise ValueError(f"Failed to read parameter file - invalid encoding: {e}") except Exception as e: - _log_and_raise_error(f"Failed to parse parameter file: {filepath}\n {e}", ValueError) + logger.error(f"{where_am_i}: Failed to parse parameter file: {filepath}\n {e}") + raise ValueError(f"Failed to parse parameter file: {filepath}\n {e}") def to_file(self, filepath: Union[str, pathlib.Path]) -> None: """Write parameter file to disk. @@ -347,9 +437,11 @@ def to_file(self, filepath: Union[str, pathlib.Path]) -> None: logger.info(f"{where_am_i}: Successfully wrote parameter file to: {filepath}") except OSError as e: - _log_and_raise_error(f"Failed to write parameter file: {e}", OSError) + logger.error(f"{where_am_i}: Failed to write parameter file: {e}") + raise OSError(f"Failed to write parameter file: {e}") except Exception as e: - _log_and_raise_error(f"Failed to format parameter file content: {e}", ValueError) + logger.error(f"{where_am_i}: Failed to format parameter file content: {e}") + raise ValueError(f"Failed to format parameter file content: {e}") def print_parameters(self) -> None: """Print the details of the parameter file.""" From aeecdb8d029c622bad1c70bfd4a75354a5259753 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 22 Feb 2025 21:37:49 +0000 Subject: [PATCH 45/47] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pleiades/sammy/parfile.py | 80 +++++++++---------- .../pleiades/sammy/parameters/test_radius.py | 9 ++- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/pleiades/sammy/parfile.py b/src/pleiades/sammy/parfile.py index 7977f09..ae3e424 100644 --- a/src/pleiades/sammy/parfile.py +++ b/src/pleiades/sammy/parfile.py @@ -22,46 +22,47 @@ UnusedCorrelatedCard, UserResolutionParameters, ) -from pleiades.utils.logger import Logger, _log_and_raise_error +from pleiades.utils.logger import Logger # Initialize logger with file logging log_file_path = os.path.join(os.getcwd(), "pleiades-par.log") logger = Logger(__name__, log_file=log_file_path) # Header lines for card sets in the PARameter file -# NOTE: If a card is not implemented, the value is None +# NOTE: If a card is not implemented, the value is None parFile_header_card_class = { - "EXTERnal R-function parameters follow": ExternalRFunction, - "R-EXTernal parameters follow": ExternalRFunction, - "BROADening parameters may be varied": BroadeningParameterCard, - "UNUSEd but correlated variables": UnusedCorrelatedCard, - "NORMAlization and background": NormalizationBackgroundCard, - "RADIUs parameters follow": RadiusCard, - "RADII are in KEY-WORD format": RadiusCard, - "CHANNel radius parameters follow": RadiusCard, - "DATA reduction parameters are next": DataReductionCard, - "ORRES": ORRESCard, - "ISOTOpic abundances and masses": IsotopeCard, - "NUCLIde abundances and masses": IsotopeCard, - "MISCEllaneous parameters follow": None, # Not implemented - "PARAMagnetic cross section parameters follow": ParamagneticParameters, - "BACKGround functions": None, # Not implemented - "RPI Resolution function": None, # Not implemented - "GEEL resolution function": None, # Not implemented - "GELINa resolution": None, # Not implemented - "NTOF resolution function": None, # Not implemented - "RPI Transmission resolution function": None, # Not implemented - "RPI Capture resolution function": None, # Not implemented - "GEEL DEFAUlts": None, # Not implemented - "GELINa DEFAUlts": None, # Not implemented - "NTOF DEFAUlts": None, # Not implemented - "DETECtor efficiencies": None, # Not implemented - "USER-Defined resolution function": UserResolutionParameters, - "COVARiance matrix is in binary form in another file": None, # Not implemented - "EXPLIcit uncertainties and correlations follow": None, # Not implemented - "RELATive uncertainties follow": None, # Not implemented - "PRIOR uncertainties follow in key-word format": None, # Not implemented - } + "EXTERnal R-function parameters follow": ExternalRFunction, + "R-EXTernal parameters follow": ExternalRFunction, + "BROADening parameters may be varied": BroadeningParameterCard, + "UNUSEd but correlated variables": UnusedCorrelatedCard, + "NORMAlization and background": NormalizationBackgroundCard, + "RADIUs parameters follow": RadiusCard, + "RADII are in KEY-WORD format": RadiusCard, + "CHANNel radius parameters follow": RadiusCard, + "DATA reduction parameters are next": DataReductionCard, + "ORRES": ORRESCard, + "ISOTOpic abundances and masses": IsotopeCard, + "NUCLIde abundances and masses": IsotopeCard, + "MISCEllaneous parameters follow": None, # Not implemented + "PARAMagnetic cross section parameters follow": ParamagneticParameters, + "BACKGround functions": None, # Not implemented + "RPI Resolution function": None, # Not implemented + "GEEL resolution function": None, # Not implemented + "GELINa resolution": None, # Not implemented + "NTOF resolution function": None, # Not implemented + "RPI Transmission resolution function": None, # Not implemented + "RPI Capture resolution function": None, # Not implemented + "GEEL DEFAUlts": None, # Not implemented + "GELINa DEFAUlts": None, # Not implemented + "NTOF DEFAUlts": None, # Not implemented + "DETECtor efficiencies": None, # Not implemented + "USER-Defined resolution function": UserResolutionParameters, + "COVARiance matrix is in binary form in another file": None, # Not implemented + "EXPLIcit uncertainties and correlations follow": None, # Not implemented + "RELATive uncertainties follow": None, # Not implemented + "PRIOR uncertainties follow in key-word format": None, # Not implemented +} + class CardOrder(Enum): """Defines the standard order of cards in SAMMY parameter files. @@ -145,7 +146,7 @@ def to_string(self) -> str: """ where_am_i = "SammyParameterFile.to_string()" - #logger.info(f"{where_am_i}: Attempting to convert parameters to string format") + # logger.info(f"{where_am_i}: Attempting to convert parameters to string format") lines = [] # Process each card type in standard order @@ -224,7 +225,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": # Split content into lines lines = content.splitlines() - logger.info(f"{where_am_i}: Attempting to parse parameter file content from string {lines}") + logger.info(f"{where_am_i}: Attempting to parse parameter file content from string {lines}") # Early exit for empty content if not lines: @@ -238,11 +239,10 @@ def from_string(cls, content: str) -> "SammyParameterFile": # read and append lines to resonance_or_fudge_factor_content until we find a header line for line in lines: - # Check if line matches any keys in parFile_header_card_class if line in parFile_header_card_class.keys(): card_type, card_class = cls._get_card_class_with_header(line) - + # if so then you have pasted the resonances and fudge factor if card_class: break @@ -290,9 +290,9 @@ def from_string(cls, content: str) -> "SammyParameterFile": except Exception as e: logger.error(f"Failed to parse resonance table: {str(e)}\nLines: {resonances_entries}") raise ValueError(f"Failed to parse resonance table: {str(e)}\nLines: {resonances_entries}") - + # remove the resonance_or_fudge_factor_content from the lines - lines = lines[len(resonance_or_fudge_factor_content):] + lines = lines[len(resonance_or_fudge_factor_content) :] # Second, partition lines into group of lines based on blank lines card_groups = [] @@ -329,7 +329,7 @@ def from_string(cls, content: str) -> "SammyParameterFile": except Exception as e: logger.error(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") raise ValueError(f"Failed to parse {card_type.name} card: {str(e)}\nLines: {group}") - + # If card type is not implemented, then throw an error stating card type is not implemented yet else: logger.error(f"{where_am_i}: Card type not implemented: {group[0]}") diff --git a/tests/unit/pleiades/sammy/parameters/test_radius.py b/tests/unit/pleiades/sammy/parameters/test_radius.py index 2ef0efe..975785d 100644 --- a/tests/unit/pleiades/sammy/parameters/test_radius.py +++ b/tests/unit/pleiades/sammy/parameters/test_radius.py @@ -79,7 +79,6 @@ def test_basic_fixed_width_format(): # Access the RadiusParameters object in the parameters list params = card.parameters[0] - # Verify parsed values assert params.effective_radius == pytest.approx(3.200) assert params.true_radius == pytest.approx(3.200) @@ -107,6 +106,7 @@ def test_basic_fixed_width_format(): assert content_line[26:28] == " 2" # Second spin group assert content_line[28:30] == " 3" # Third spin group + @pytest.mark.skip(reason="Alternate fixed-width format is unsupported at the moment") def test_alternate_fixed_width_format(): """Test alternate fixed-width format parsing (for >=99 spin groups).""" @@ -159,6 +159,7 @@ def test_alternate_fixed_width_format(): assert content_line[40:45] == " 102" # Second spin group (5 cols) assert content_line[45:50] == " 103" # Third spin group (5 cols) + @pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_basic_radius_keyword_format(): """Test basic keyword format parsing with single radius value.""" @@ -189,6 +190,7 @@ def test_basic_radius_keyword_format(): assert any(line.startswith("Radius= 3.2") for line in output_lines) assert any(line.startswith("Group= 1") for line in output_lines) + @pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_separate_radii_keyword_format(): """Test keyword format parsing with different effective/true radius values.""" @@ -213,6 +215,7 @@ def test_separate_radii_keyword_format(): print("\nGenerated output:") print("\n".join(output_lines)) + @pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_uncertainties_keyword_format(): """Test keyword format parsing with uncertainty specifications.""" @@ -239,6 +242,7 @@ def test_uncertainties_keyword_format(): assert any(line.startswith("Relative= 0.05") for line in output_lines) assert any(line.startswith("Absolute= 0.002") for line in output_lines) + @pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_particle_pair_keyword_format(): """Test keyword format parsing with particle pair and orbital momentum.""" @@ -263,6 +267,7 @@ def test_particle_pair_keyword_format(): assert any(line.startswith("PP= n+16O") for line in output_lines) assert any("L= all" in line for line in output_lines) + @pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_groups_channels_keyword_format(): """Test keyword format parsing with group and channel specifications.""" @@ -287,6 +292,7 @@ def test_groups_channels_keyword_format(): print("\nGenerated output:") print("\n".join(output_lines)) + @pytest.mark.skip(reason="Alternate keyword format is unsupported at the moment") def test_invalid_keyword_format(): """Test error handling for invalid keyword format.""" @@ -381,7 +387,6 @@ def test_radius_with_extras(): # Verify that there is only one set of parameters in the card assert len(card.parameters) == 1 - # Verify core parameters assert card.parameters[0].effective_radius == pytest.approx(3.200) assert card.parameters[0].true_radius == pytest.approx(3.200) From 3b589a66df0ad3fc10de8bcebed0184b41a253b3 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Sat, 22 Feb 2025 15:13:21 -0700 Subject: [PATCH 46/47] using unused variables in RadiusCardKeyword class to make overlord "pre-commit.ci" happy --- src/pleiades/sammy/parameters/radius.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 0265790..1fbfdf1 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -728,12 +728,14 @@ class RadiusCardKeyword(BaseModel): def is_header_line(cls, line: str) -> bool: """Check if line is a valid header line.""" where_am_i = "RadiusCardKeyword.is_header_line()" + logger.info(f"{where_am_i}: Checking if header line - {line}") return "RADII" in line.upper() and "KEY-WORD" in line.upper() @staticmethod def _parse_values(value_str: str) -> List[str]: """Parse space/comma separated values.""" where_am_i = "RadiusCardKeyword._parse_values()" + logger.info(f"{where_am_i}: Parsing values from string: {value_str}") return [v for v in re.split(r"[,\s]+", value_str.strip()) if v] @classmethod @@ -751,10 +753,12 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardKeyword": """ where_am_i = "RadiusCardKeyword.from_lines()" if not lines: + logger.error(f"{where_am_i}: No lines provided") raise ValueError("No lines provided") # Validate header if not cls.is_header_line(lines[0]): + logger.error(f"{where_am_i}: Invalid header line: {lines[0]}") raise ValueError(f"Invalid header line: {lines[0]}") # Parse parameters for RadiusParameters @@ -842,6 +846,7 @@ def to_lines(self) -> List[str]: List[str]: Lines in keyword format """ where_am_i = "RadiusCardKeyword.to_lines()" + logger.info(f"{where_am_i}: Converting radius parameters to lines") lines = [] # Add radius values From b48eed137b140ad7928146043c0c30fa684d7718 Mon Sep 17 00:00:00 2001 From: Alexander Long Date: Sat, 22 Feb 2025 15:19:51 -0700 Subject: [PATCH 47/47] more making overlord happy --- src/pleiades/sammy/parameters/radius.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pleiades/sammy/parameters/radius.py b/src/pleiades/sammy/parameters/radius.py index 1fbfdf1..c43cdf2 100644 --- a/src/pleiades/sammy/parameters/radius.py +++ b/src/pleiades/sammy/parameters/radius.py @@ -524,6 +524,7 @@ def _parse_numbers_from_line(line: str, start_pos: int, width: int) -> List[int] List[int]: List of parsed integers, stopping at first invalid value """ where_am_i = "RadiusCardAlternate._parse_numbers_from_line()" + logger.info(f"{where_am_i}: Parsing fixed-width integers from line") numbers = [] pos = start_pos @@ -551,6 +552,7 @@ def _parse_spin_groups_and_channels(cls, lines: List[str]) -> Tuple[List[int], O Handles continuation lines (-1 marker) and IX=0 marker for channels """ where_am_i = "RadiusCardAlternate._parse_spin_groups_and_channels()" + logger.info(f"{where_am_i}: Parsing spin groups and channels from lines") spin_groups = [] channels = None @@ -593,17 +595,20 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardAlternate": ValueError: If lines are invalid or required data is missing """ where_am_i = "RadiusCardAlternate.from_lines()" + logger.info(f"{where_am_i}: Parsing radius parameters from lines") if not lines: raise ValueError("No lines provided") # Validate header if not cls.is_header_line(lines[0]): + logger.error(f"{where_am_i}: Invalid header line: {lines[0]}") raise ValueError(f"Invalid header line: {lines[0]}") # Get content lines (skip header and trailing blank) content_lines = [line for line in lines[1:] if line.strip()] if not content_lines: + logger.error(f"{where_am_i}: No parameter lines found") raise ValueError("No parameter lines found") # Parse first line for main parameters @@ -611,6 +616,7 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardAlternate": # Ensure line is long enough if len(main_line) < 35: # Minimum length for main parameters + logger.error(f"{where_am_i}: Parameter line too short") raise ValueError("Parameter line too short") # Parse main parameters @@ -625,12 +631,14 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardAlternate": params["vary_effective"] = VaryFlag(int(main_line[FORMAT_ALTERNATE["ifleff"]].strip() or "0")) params["vary_true"] = VaryFlag(int(main_line[FORMAT_ALTERNATE["ifltru"]].strip() or "0")) except ValueError: + logger.error(f"{where_am_i}: Invalid vary flags") raise ValueError("Invalid vary flags") # Parse spin groups and channels spin_groups, channels = cls._parse_spin_groups_and_channels(content_lines) if not spin_groups: + logger.error(f"{where_am_i}: No spin groups found") raise ValueError("No spin groups found") params["spin_groups"] = spin_groups @@ -640,8 +648,10 @@ def from_lines(cls, lines: List[str]) -> "RadiusCardAlternate": try: parameters = RadiusParameters(**params) except ValueError as e: + logger.error(f"{where_am_i}: Invalid parameter values: {e}") raise ValueError(f"Invalid parameter values: {e}") + logger.info(f"{where_am_i}: Successfully parsed radius parameters") return cls(parameters=parameters) def to_lines(self) -> List[str]: