Skip to content

Commit 67b80a7

Browse files
committed
Update documentation and improve code clarity; disable display_version in conf.py, correct module reference in index.rst, enhance example_fake_tbt fixture in conftest.py, refactor MAD-NG and xtrack_line modules for better error handling and type hints.
1 parent 612f762 commit 67b80a7

File tree

6 files changed

+65
-79
lines changed

6 files changed

+65
-79
lines changed

doc/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def about_package(init_posixpath: pathlib.Path) -> dict:
149149
#
150150
html_theme_options = {
151151
'collapse_navigation': False,
152-
'display_version': True,
152+
# 'display_version': True, # I get a warning about this, so I disable it
153153
'logo_only': True,
154154
'navigation_depth': 1,
155155
}

doc/readers/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@
3636
:members:
3737
:noindex:
3838

39-
.. automodule:: turn_by_turn.xtrack
39+
.. automodule:: turn_by_turn.xtrack_line
4040
:members:
4141
:noindex:

tests/conftest.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66
@pytest.fixture(scope="session")
77
def example_fake_tbt():
88
"""
9-
Returns a TbtData object with the original simulation data.
10-
This fixture is session-scoped and will only be created once per test run.
9+
Returns a TbtData object using simulation data taken from MAD-NG.
10+
This data is also used for the tests in xtrack, so change the numbers
11+
at your own risk.
12+
13+
It is possible to run the MAD-NG in the inputs folder to regenerate the data.
14+
Also, xtrack produces the same data, so you can use the xtrack test fixture
15+
`example_line`.
1116
"""
1217
names = np.array(["BPM1", "BPM3", "BPM2"])
1318
# First BPM

turn_by_turn/io.py

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
from turn_by_turn.structures import TbtData
3030
from turn_by_turn.utils import add_noise_to_tbt
3131

32+
LOGGER = logging.getLogger(__name__)
33+
3234
if TYPE_CHECKING:
3335
from pandas import DataFrame
3436
from xtrack import Line
3537

36-
LOGGER = logging.getLogger(__name__)
37-
3838
TBT_MODULES = dict(
3939
lhc=lhc,
4040
doros=doros,
@@ -57,42 +57,7 @@
5757
WRITERS = ("lhc", "sps", "doros", "doros_positions", "doros_oscillations", "ascii", "madng")
5858

5959
write_lhc_ascii = write_ascii # Backwards compatibility <0.4
60-
61-
def load_tbt_data(
62-
tbt_input: str | Path | Line | DataFrame,
63-
datatype: str = "lhc"
64-
) -> TbtData:
65-
"""
66-
Get a TbtData object from various input types. Explicitly does not infer the datatype from the input.
67-
68-
Args:
69-
tbt_input (str | Path | Line | DataFrame):
70-
The input data object or path to a file.
71-
datatype (str):
72-
Defaults to "lhc".
73-
74-
Returns:
75-
TbtData: The resulting TbtData object.
76-
77-
Raises:
78-
DataTypeError: If the datatype is None and the input type cannot be inferred.
79-
TypeError: If the input type is not supported for the given datatype.
80-
"""
81-
datatype = datatype.lower()
82-
83-
try:
84-
module = TBT_MODULES[datatype]
85-
except KeyError as error:
86-
LOGGER.exception(
87-
f"Unsupported datatype '{datatype}'. Supported types: {list(TBT_MODULES.keys())}"
88-
)
89-
raise DataTypeError(datatype) from error
90-
91-
if isinstance(tbt_input, (str, Path)):
92-
return module.read_tbt(Path(tbt_input), **additional_args(datatype))
93-
return module.convert_to_tbt(tbt_input)
94-
95-
60+
9661
def read_tbt(file_path: str | Path, datatype: str = "lhc") -> TbtData:
9762
"""
9863
Calls the appropriate loader for the provided matrices type and returns a ``TbtData`` object of the

turn_by_turn/madng.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
allowing easy post-processing and conversion between formats.
1111
1212
Dependencies:
13-
- Requires the ``tfs-pandas >= 4.0.0`` package to read or write TFS files.
13+
- Requires the ``tfs-pandas >= 4.0.0`` package for compatibility with MAD-NG
14+
features. Earlier versions does not support MAD-NG TFS files.
1415
"""
1516

1617

@@ -23,14 +24,9 @@
2324
import pandas as pd
2425

2526
from typing import TYPE_CHECKING
26-
27-
try:
28-
import tfs
2927

30-
if TYPE_CHECKING:
31-
from tfs import TfsDataFrame
32-
except ImportError:
33-
tfs = None
28+
if TYPE_CHECKING:
29+
import tfs
3430

3531
from turn_by_turn.structures import TbtData, TransverseData
3632

@@ -66,17 +62,22 @@ def read_tbt(file_path: str | Path) -> TbtData:
6662
Raises:
6763
ImportError: If the ``tfs-pandas`` package is not installed.
6864
"""
69-
if not tfs:
70-
raise ImportError(
65+
try:
66+
import tfs
67+
except ImportError as e:
68+
LOGGER.exception(
7169
"The 'tfs' package is required to read MAD-NG TFS files. Install it with: pip install 'turn_by_turn[madng]'"
7270
)
71+
raise ImportError(
72+
"The 'tfs' package is required to read MAD-NG TFS files. Install it with: pip install 'turn_by_turn[madng]'"
73+
) from e
7374

7475
LOGGER.debug("Starting to read TBT data from dataframe")
7576
df = tfs.read(file_path)
7677
return convert_to_tbt(df)
7778

7879

79-
def convert_to_tbt(df: pd.DataFrame | 'TfsDataFrame') -> TbtData:
80+
def convert_to_tbt(df: pd.DataFrame | 'tfs.TfsDataFrame') -> TbtData:
8081
"""
8182
Convert a TFS or pandas DataFrame to a ``TbtData`` object.
8283
@@ -97,16 +98,21 @@ def convert_to_tbt(df: pd.DataFrame | 'TfsDataFrame') -> TbtData:
9798
"""
9899

99100
# Get the date and time from the headers (return None if not found)
100-
if tfs and isinstance(df, tfs.TfsDataFrame):
101+
try:
102+
import tfs
103+
is_tfs_df = isinstance(df, tfs.TfsDataFrame)
104+
except ImportError:
105+
LOGGER.debug(
106+
"The 'tfs' package is not installed. Assuming a pandas DataFrame."
107+
)
108+
is_tfs_df = False
109+
110+
if is_tfs_df:
101111
date_str = df.headers.get(DATE)
102112
time_str = df.headers.get(TIME)
103-
elif isinstance(df, pd.DataFrame):
113+
else:
104114
date_str = df.attrs.get(DATE)
105115
time_str = df.attrs.get(TIME)
106-
else:
107-
raise TypeError(
108-
"Input DataFrame must be a Pandas DataFrame or a TFS DataFrame."
109-
)
110116

111117
# Combine the date and time into a datetime object
112118
date = None

turn_by_turn/xtrack_line.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,35 @@
66
into the standardized ``TbtData`` format used by ``turn_by_turn``.
77
88
Prerequisites for using ``convert_to_tbt``:
9+
910
1. The input ``Line`` must contain one or more ``ParticlesMonitor`` elements
1011
positioned at each location where turn-by-turn data is required (e.g., all BPMs).
12+
1113
A valid monitor setup involves:
12-
• Placing a ``xt.ParticlesMonitor`` instance in the line's element sequence
14+
15+
- Placing a ``xt.ParticlesMonitor`` instance in the line's element sequence
1316
at all the places you would like to observe.
14-
• Configuring each monitor with identical settings:
15-
- ``start_at_turn`` (first turn to record, usually 0)
16-
- ``stop_at_turn`` (The total number of turns to record, e.g., 100)
17-
- ``num_particles`` (number of tracked particles)
18-
If any monitor is configured with different parameters, ``convert_to_tbt``
19-
will either find no data or raise a inconsistency error.
17+
- Configuring each monitor with identical settings:
18+
19+
* ``start_at_turn`` (first turn to record, usually 0)
20+
* ``stop_at_turn`` (The total number of turns to record, e.g., 100)
21+
* ``num_particles`` (number of tracked particles)
22+
23+
If any monitor is configured with different parameters, ``convert_to_tbt``
24+
will either find no data or raise an inconsistency error.
25+
26+
Also, if you specify more turns than were actually tracked, the resulting
27+
TBT data will include all turns up to the monitor's configured limit.
28+
This may result in extra rows filled with zeros for turns where no real
29+
data was recorded, which might not be desirable for your analysis.
30+
2031
2. Before conversion, you must:
21-
• Build particles with the desired initial coordinates
32+
33+
- Build particles with the desired initial coordinates
2234
(using ``line.build_particles(...)``).
23-
Track those particles through the line for the intended number of turns
35+
- Track those particles through the line for the intended number of turns
2436
(using ``line.track(..., num_turns=num_turns)``).
25-
37+
2638
Once these conditions are met, pass the tracked ``Line`` to ``convert_to_tbt`` to
2739
extract the data from each particle monitor into a ``TbtData`` object.
2840
"""
@@ -39,18 +51,13 @@
3951

4052
from turn_by_turn.structures import TbtData, TransverseData
4153

42-
try:
54+
if TYPE_CHECKING:
4355
import xtrack as xt
44-
if TYPE_CHECKING:
45-
from xtrack import Line
46-
47-
except ImportError:
48-
xt = None
4956

5057
LOGGER = logging.getLogger(__name__)
5158

5259

53-
def convert_to_tbt(xline: Line) -> TbtData:
60+
def convert_to_tbt(xline: xt.Line) -> TbtData:
5461
"""
5562
Convert tracking results from an ``xtrack`` Line into a ``TbtData`` object.
5663
@@ -70,10 +77,13 @@ def convert_to_tbt(xline: Line) -> TbtData:
7077
TypeError: If the input is not a valid ``xtrack.Line``.
7178
ValueError: If no monitors are found or data is inconsistent.
7279
"""
73-
if not xt:
80+
try:
81+
import xtrack as xt
82+
except ImportError as e:
7483
raise ImportError(
7584
"The 'xtrack' package is required to convert xtrack Line objects. Install it with: pip install 'turn_by_turn[xtrack]'"
76-
)
85+
) from e
86+
7787
if not isinstance(xline, xt.Line):
7888
raise TypeError(f"Expected an xtrack Line object, got {type(xline)} instead.")
7989

@@ -106,7 +116,7 @@ def convert_to_tbt(xline: Line) -> TbtData:
106116
)
107117
npart = npart_set.pop()
108118

109-
# Precompute masks for each monitor and particle_id using vectorized broadcasting for speed
119+
# Precompute masks for each monitor and particle_id
110120
monitor_particle_masks = [
111121
mon.data.particle_id[:, None] == np.arange(npart)[None, :]
112122
for mon in monitors

0 commit comments

Comments
 (0)