Skip to content

Conversation

@mattiastefanelli
Copy link

Adding a script to generate .tfs and .txt tables with the summary of the KMOD measurement imported with kmod_importer. It will generate one .tfs and one .txt table for each beam.
The .txt table has been made to be easily copy/pasted in the logbook keeping a readable format.

@mattiastefanelli mattiastefanelli requested a review from a team as a code owner November 26, 2025 16:52
@mattiastefanelli mattiastefanelli marked this pull request as draft November 26, 2025 16:54
@mattiastefanelli mattiastefanelli marked this pull request as ready for review November 27, 2025 08:37
@mattiastefanelli
Copy link
Author

The output .txt table for the Logbook looks like this without Tabulate package

B1_summary_logbook.txt

@github-actions
Copy link

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  omc3
  kmod_importer.py 344-346
Project Total  

This report was generated by python-coverage-comment-action

Copy link
Member

@fsoubelet fsoubelet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR Mattia, this is going to be useful to have.

I left comments here and there about inconsistencies / code clarity etc. See below.

One thing I'm wondering is: could this be a stand-alone script? And then it could be run on previous folders with kmod measurements from before we had it (and the relevant part just imported and called into kmob_importer.py)?

# return [round(bstar, 3) for bstar in df_model.loc[ip, [f"{BETA}X", f"{BETA}Y"]]]
return df_model.loc[ip, [f"{BETA}X", f"{BETA}Y"]].tolist()

def import_kmod_summary_table(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This writes out things so the naming feels off. Maybe something like output_kmod_summary_tables?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize import_kmod_data suffers the same problem. We should look to fix that as well.

Copy link
Member

@JoschD JoschD Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(removed - updated below)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import_kmod_data makes a bit more sense, because it is importing it into the omc3 analysis folder in the same format as the other files there. Whereas this function really only outputs some text files, which actually do not really need to go into the omc3 results folder. But if there is a good naming suggestion, I am not strictly against renaming the import-data function as well.


def import_kmod_summary_table(
meas_paths: Sequence[Path | str],
averaged_meas_paths: Sequence[tfs.TfsDataFrame],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above at function call this is passed as averaged_results which is definitely not a Sequence[tfs.TfsDataFrame].

"""
Write the KMOD summary table from each results.tfs file.
It writes down the following files for each beam:
{beam}_kmod_sum_X.tfs: BETSTARX, ERRBETSTARX, BETWAISTX, ERRBETWAISTX, WAISTX, ERRWAISTX.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{beam}_kmod_sum_X.tfs: BETSTARX, ERRBETSTARX, BETWAISTX, ERRBETWAISTX, WAISTX, ERRWAISTX.
B{beam}_kmod_sum_X.tfs: BETSTARX, ERRBETSTARX, BETWAISTX, ERRBETWAISTX, WAISTX, ERRWAISTX.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none of the filenames here are correct actually anyway. There is no X/Y it's all in one file.
sum is actually summary and tables is summary_logbook. I prefer tables though.
And make them all lower-case.

Write the KMOD summary table from each results.tfs file.
It writes down the following files for each beam:
{beam}_kmod_sum_X.tfs: BETSTARX, ERRBETSTARX, BETWAISTX, ERRBETWAISTX, WAISTX, ERRWAISTX.
{beam}_kmod_sum_X.tfs: BETSTARY, ERRBETSTARY, BETWAISTY, ERRBETWAISTY, WAISTY, ERRWAISTY.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{beam}_kmod_sum_X.tfs: BETSTARY, ERRBETSTARY, BETWAISTY, ERRBETWAISTY, WAISTY, ERRWAISTY.
B{beam}_kmod_sum_Y.tfs: BETSTARY, ERRBETSTARY, BETWAISTY, ERRBETWAISTY, WAISTY, ERRWAISTY.

return df_x, df_y

grouped = {beam: [] for beam in beams}
for elem in meas_paths:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer representative variable names. This elem is a path

"""

if not av_res_flag:
file_name = data_sngl_file_raw.parent.parent.name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not going to work is data_sngl_file_raw is a str, which the type hint says is accepted.

f.write("\n".join(txt_output))
tfs.write(output_dir/f"{beam}_kmod_summary.tfs", big_df)

LOG.info('KMOD summary outputs correctly saved.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LOG.info('KMOD summary outputs correctly saved.')
LOG.info(f"KMOD summary outputs saved at {output_dir/f'{beam}_kmod_summary.tfs'}")

even better is the full output path is stored in a variable before

lumi_filename = _get_lumi_filename(betas, ip_a=ips[0], ip_b=ips[1])
assert (average_dir / lumi_filename).exists()


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this line

lumi_filename = _get_lumi_filename(betas, ip_a=ips[0], ip_b=ips[1])
assert (average_dir / lumi_filename).exists()


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a little check that the written files are as expected?

Copy link
Member

@JoschD JoschD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments, but my main point is also, that this should be a standalone script, in case someone only wants the table without importing it anywhere.

When refactoring, also separate the output from the reading/transforming. There should be one function that creates the tables and simply returns them. Then another funtion/outer function that writes them out.

In my opinion the script/main function of the script should also have the following parameters:

  • outputdir -> optional, write the files ou (always return the tables)
  • average -> true/false, calls the functions to average the data (could also be boolean | path-to-the-file | averaged-dataframes)
  • logbook -> optional, specify a logbook (LHC_OMC, LHC_OP, true->LHC_OMC) uses the logbook uploader to create a new logbook entry.

And then you can import the function here and re-use it to create the table automatically on import.

When putting it into another script: consider which parts might benefit from being in their own little function as well, to make the code more readable/self-explainatory.

Comment on lines +338 to +344
try:
label = data_sngl_file["LABEL"].iloc[0]
second_magnet = label.split("-")[1]
relevant = second_magnet.split(".")[-1]
ip_number = relevant[1:]
ip_name = f"IP{ip_number}" # --- it extracts the IP from the magnet label, check for typos
except KeyError as e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the try-part as short as possible (usually only one line), to make sure the Key error originates where you expect it to come from.

Comment on lines +322 to +323
cols_x = ["BETSTARX", "ERRBETSTARX", "BETWAISTX", "ERRBETWAISTX", "WAISTX", "ERRWAISTX"]
cols_y = ["BETSTARY", "ERRBETSTARY", "BETWAISTY", "ERRBETWAISTY", "WAISTY", "ERRWAISTY"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll mark it here, but this is a general comment for the whole function: Use the constants for the columns. Check the other kmod-related files for how it is done there.

Comment on lines +367 to +372
all_av_x, all_av_y = [], []
for key, value in averaged_meas_paths.items():
LOG.debug(f"Reading averaged results: {key}")
df_av_x, df_av_y = output_table_single_beam(value[0], av_res_flag=True)
all_av_x.append(df_av_x)
all_av_y.append(df_av_y)
Copy link
Member

@JoschD JoschD Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename key and value to what the variables actually mean. (e.g. I have no idea what value[0] is supposed to be.

# return [round(bstar, 3) for bstar in df_model.loc[ip, [f"{BETA}X", f"{BETA}Y"]]]
return df_model.loc[ip, [f"{BETA}X", f"{BETA}Y"]].tolist()

def import_kmod_summary_table(
Copy link
Member

@JoschD JoschD Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(removed - updated below)

cols_y = ["BETSTARY", "ERRBETSTARY", "BETWAISTY", "ERRBETWAISTY", "WAISTY", "ERRWAISTY"]
beams = [f"{BEAM_DIR}{1}", f"{BEAM_DIR}{2}"]

def output_table_single_beam(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to have this as a nested function.

Co-authored-by: Felix Soubelet <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants