Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions carsus/io/literature/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
Literature data readers for the TARDIS/Carsus codebase.
"""

from carsus.io.literature.seaton import get_seaton_opacity_df

__all__ = [
'get_seaton_opacity_df',
]
70 changes: 70 additions & 0 deletions carsus/io/literature/seaton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
Reader for Seaton opacity data (Seaton 1992).

This module provides functionality to read continuum opacity data from
the Seaton archive (VI/80 at CDS).
"""

import pandas as pd
import requests
import io
import gzip


def get_seaton_opacity_df(file_name):
"""
Load and parse a Seaton opacity table from the CDS archive.

Parameters
----------
file_name : str
The name of the gzip-compressed file to load from the Seaton
archive (e.g., 's92.201.gz').

Returns
-------
pandas.DataFrame
A DataFrame with columns:
- logT : float
Log10 of temperature in Kelvin
- logNe : float
Log10 of electron density
- kappa_planck : float
Planck-weighted mean opacity
- kappa_rosseland : float
Rosseland-weighted mean opacity
"""
url = f"https://cdsarc.cds.unistra.fr/ftp/VI/80/{file_name}"
response = requests.get(url)

with gzip.GzipFile(fileobj=io.BytesIO(response.content)) as f:
lines = [line.decode('utf-8').strip() for line in f.readlines() if line.strip()]

data_rows = []
current_log_t = None

# Standard Seaton format: logT = Index / 40
for line in lines[1:]: # Skip metadata line
parts = line.split()
if not parts:
continue

# Detect Block Header: (e.g., 140 14 68 2)
# These define the constant log(T) for the following rows
if len(parts) == 4 and float(parts[0]) >= 140 and '.' not in parts[0]:
current_log_t = float(parts[0]) / 40.0
continue

# Parse Data Rows: [Index] [logNe] [Planck_Opacity] [Rosseland_Opacity]
if current_log_t is not None and len(parts) >= 4:
try:
data_rows.append({
'logT': current_log_t,
'logNe': float(parts[1]),
'kappa_planck': float(parts[2]),
'kappa_rosseland': float(parts[3])
})
except ValueError:
continue

return pd.DataFrame(data_rows)
42 changes: 42 additions & 0 deletions carsus/io/literature/tests/test_seaton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Tests for Seaton opacity reader.
"""

import gzip
import io
from unittest import mock

import pandas as pd
import pytest

from carsus.io.literature import get_seaton_opacity_df


def create_sample_data():
"""Create gzip-compressed sample Seaton data."""
sample_lines = [
"OP Version 2.0 S92 201",
"140 14 68 2",
"14 -4.0 1.23e-1 1.15e-1",
"16 -3.5 1.45e-1 1.32e-1",
]
buffer = io.BytesIO()
with gzip.GzipFile(fileobj=buffer, mode='wb') as f:
f.write("\n".join(sample_lines).encode('utf-8'))
return buffer.getvalue()


@mock.patch('carsus.io.literature.seaton.requests.get')
def test_get_seaton_opacity_df(mock_get):
"""Test reading Seaton opacity data."""
mock_get.return_value.content = create_sample_data()

df = get_seaton_opacity_df("s92.201.gz")

assert isinstance(df, pd.DataFrame)
assert len(df) == 2
assert list(df.columns) == ['logT', 'logNe', 'kappa_planck', 'kappa_rosseland']
assert (df['kappa_planck'] > 0).all()
assert (df['kappa_rosseland'] > 0).all()


Loading