Warning, /tutorial-developing-benchmarks/episodes/01-analysis_scripts_and_snakemake.md is written in an unsupported language. File is not indexed.
0001 ---
0002 title: "Exercise 1: Analysis Scripts and Snakemake"
0003 teaching: 20
0004 exercises: 10
0005 ---
0006
0007 ::::::::::::::::::::::::::::::::::::::::::::::: questions
0008
0009 - How does one set up data analysis workflows?
0010
0011 :::::::::::::::::::::::::::::::::::::::::::::::
0012
0013 ::::::::::::::::::::::::::::::::::::::::::::::: objectives
0014
0015 - Learn basics of creating Snakemake workflows
0016
0017 :::::::::::::::::::::::::::::::::::::::::::::::
0018
0019 In this exercise we start with a ready-made analysis script that we're running locally. We'll practice using [Snakemake](https://snakemake.github.io/) workflow management system to define data analysis pipelines.
0020
0021 Snakemake comes with a great [documentation](https://snakemake.readthedocs.io), you are encouraged to read it. For now, let's cover its suggested use for needs of defining ePIC benchmarks.
0022
0023 ## Starting from an Analysis Script
0024
0025 We're going to go all the way from an analysis script to a fully-integragrated benchmark with GitLab's continuous integration (CI).
0026
0027 First launch eic-shell
0028
0029 ```bash
0030 ./eic-shell
0031 ```
0032
0033 then create a working directory
0034
0035 ```bash
0036 mkdir tutorial_directory
0037 mkdir tutorial_directory/starting_script
0038 cd tutorial_directory/starting_script
0039 ```
0040
0041 Copy the following files to this working directory:
0042
0043 - this analysis script: [`uchannelrho.cxx`](files/uchannelrho.cxx)
0044 - this plotting macro: [`plot_rho_physics_benchmark.C`](files/prefinal/plot_rho_physics_benchmark.C)
0045 - this style header: [`RiceStyle.h`](files/RiceStyle.h)
0046
0047 We will also start by running over a file from the simulation campaign. Download it to your workspace:
0048
0049 ```bash
0050 xrdcp root://dtn-eic.jlab.org//volatile/eic/EPIC/RECO/25.10.2/epic_craterlake/EXCLUSIVE/UCHANNEL_RHO/10x100/rho_10x100_uChannel_Q2of0to10_hiDiv.0020.eicrecon.edm4eic.root ./
0051 ```
0052
0053 Organize files into `analysis` and `macros` directories:
0054
0055 ```bash
0056 mkdir analysis
0057 mv uchannelrho.cxx analysis/
0058 mkdir macros
0059 mv plot_rho_physics_benchmark.C macros/
0060 mv RiceStyle.h macros/
0061 ```
0062
0063 Run the analysis script over the simulation campaign output:
0064
0065 ```bash
0066 root -l -b -q 'analysis/uchannelrho.cxx+("rho_10x100_uChannel_Q2of0to10_hiDiv.0020.eicrecon.edm4eic.root","output.root")'
0067 ```
0068
0069 Now make a directory to contain the benchmark figures, and run the plotting macro:
0070
0071 ```bash
0072 mkdir output_figures/
0073 root -l -b -q 'macros/plot_rho_physics_benchmark.C("output.root")'
0074 ```
0075
0076 You should see some errors like
0077
0078 ```output
0079 Error in <TTF::SetTextSize>: error in FT_Set_Char_Size
0080 ```
0081
0082 but the figures should be produced just fine. If everything's run correctly, then we have a working analysis!
0083
0084 With this analysis as a starting point, we'll next explore using Snakemake to define an analysis workflow.
0085
0086 ## Getting started with Snakemake
0087
0088 We'll now use a tool called Snakemake to define an analysis workflow that will come in handy when building analysis pipelines.
0089
0090 In order to demonstrate the advantages of using snakefiles, let's start using them for our analysis.
0091 First let's use snakemake to grab some simulation campaign files from the online storage space. In your `tutorial_directory/starting_script/` directory make a new file called `Snakefile`.
0092 Open the file and add these lines:
0093
0094 ```python
0095 rule your_benchmark_campaign_reco_get:
0096 output:
0097 f"sim_output/rho_10x100_uChannel_Q2of0to10_hiDiv.{{INDEX}}.eicrecon.edm4eic.root",
0098 shell: """
0099 xrdcp root://dtn-eic.jlab.org//volatile/eic/EPIC/RECO/25.10.2/epic_craterlake/EXCLUSIVE/UCHANNEL_RHO/10x100/rho_10x100_uChannel_Q2of0to10_hiDiv.{wildcards.INDEX}.eicrecon.edm4eic.root {output}
0100 """
0101 ```
0102
0103 If you're having trouble copying and pasting, you can also copy from [the ready-made Snakefile](files/Snakefile).
0104
0105 We also defined a new rule: `your_benchmark_campaign_reco_get`. This rule defines how to download a single file from the JLab servers to the location `sim_output`.
0106
0107 After saving the Snakefile, let's try running it.
0108
0109 The important thing to remember about Snakemake is that Snakemake commands behave like requests. So if I want Snakemake to produce a file called `output.root`, I would type `snakemake --cores 2 output.root`. If there is a rule for producing `output.root`, then Snakemake will find that rule and execute it. We've defined a rule to produce a file called `../../sim_output/rho_10x100_uChannel_Q2of0to10_hiDiv.{INDEX}.eicrecon.edm4eic.root`, but really we can see from the construction of our rule that the `{INDEX}` is a wildcard, so we should put a number there instead. Checking out the [files on S3](https://dtn01.sdcc.bnl.gov:9001/buckets/eictest/browse/RVBJQy9SRUNPLzI1LjEwLjIvZXBpY19jcmF0ZXJsYWtlL0VYQ0xVU0lWRS9VQ0hBTk5FTF9SSE8vMTB4MTAwLw==), we see files with indices from `0000` up to `0116`. Let's request that Snakemake download the file `rho_10x100_uChannel_Q2of0to10_hiDiv.0005.eicrecon.edm4eic.root`:
0110
0111 ```bash
0112 snakemake --cores 2 sim_output/rho_10x100_uChannel_Q2of0to10_hiDiv.0000.eicrecon.edm4eic.root
0113 ```
0114
0115 Snakemake now looks for the rule it needs to produce that file. It finds the rule we wrote, and it downloads the file. Check for the file:
0116
0117 ```bash
0118 ls sim_output/
0119 rho_10x100_uChannel_Q2of0to10_hiDiv.0000.eicrecon.edm4eic.root
0120 ```
0121
0122 Okay whatever... so we download a file. It doesn't look like Snakemake really adds anything at this point.
0123
0124 But the benefits from using Snakemake become more apparent as the number of tasks we want to do grows! Let's now add a new rule below the last one:
0125
0126 ```python
0127 rule your_benchmark_analysis:
0128 input:
0129 script=workflow.source_path("analysis/uchannelrho.cxx"),
0130 data=f"sim_output/rho_10x100_uChannel_Q2of0to10_hiDiv.{{INDEX}}.eicrecon.edm4eic.root",
0131 output:
0132 plots=f"sim_output/campaign_25.10.2_{{INDEX}}.eicrecon.edm4eic/plots.root",
0133 shell:
0134 """
0135 mkdir -p $(dirname "{output.plots}")
0136 root -l -b -q '{input.script}+("{input.data}","{output.plots}")'
0137 """
0138 ```
0139
0140 This rule runs an analysis script to create ROOT files containing plots. The rule uses the simulation campaign file downloaded from JLab as input data, and it runs the analysis script `uchannelrho.cxx`. Note that we use `workflow.source_path()` to reference the script - this function returns the correct path to the script relative to the Snakefile's location in the benchmark directory.
0141
0142 Now let's request the output file `"sim_output/campaign_25.10.2_0005.eicrecon.edm4eic/plots.root"`. When we request this, Snakemake will identify that it needs to run the new `your_benchmark_analysis` rule. But in order to do this, it now needs a file we don't have: `sim_output/rho_10x100_uChannel_Q2of0to10_hiDiv.0005.eicrecon.edm4eic.root` because we only downloaded the file with index `0000` already. What Snakemake will do automatically is recognize that in order to get that file, it first needs to run the `your_benchmark_campaign_reco_get` rule. It will do this first, and then circle back to the `your_benchmark_analysis` rule.
0143
0144 Let's try it out:
0145
0146 ```bash
0147 snakemake --cores 2 sim_output/campaign_25.10.2_0005.eicrecon.edm4eic/plots.root
0148 ```
0149
0150 You should see something like this:
0151 
0152 Check for the output file:
0153
0154 ```bash
0155 ls sim_output/campaign_25.10.2_0005.eicrecon.edm4eic/
0156 ```
0157
0158 You should see `plots.root`.
0159
0160 That's still not very impressive. Snakemake gets more useful when we want to run the analysis code over a lot of files. Let's add a rule to do this:
0161
0162 ```python
0163 rule your_benchmark_combine:
0164 input:
0165 lambda wildcards: expand(
0166 f"sim_output/campaign_25.10.2_{{INDEX:04d}}.eicrecon.edm4eic/plots.root",
0167 INDEX=range(int(wildcards.N)),
0168 ),
0169 wildcard_constraints:
0170 N="\d+",
0171 output:
0172 f"sim_output/campaign_25.10.2_combined_{{N}}files.eicrecon.edm4eic.plots.root",
0173 shell:
0174 """
0175 hadd {output} {input}
0176 """
0177 ```
0178
0179 On its face, this rule just adds root files using the `hadd` command. But by specifying the number of files you want to add, Snakemake will realize those files don't exist, and will go back to the `your_benchmark_campaign_reco_get` rule and the `your_benchmark_analysis` rule to create them.
0180
0181 Let's test it out by requesting it combine 10 files:
0182
0183 ```bash
0184 snakemake --cores 2 sim_output/campaign_25.10.2_combined_10files.eicrecon.edm4eic.plots.root
0185 ```
0186
0187 It will spend some time downloading files and running the analysis code. Then it should hadd the files:
0188 
0189
0190 Once it's done running, check that the file was produced:
0191
0192 ```bash
0193 ls sim_output/campaign_25.10.2_combined_10files*
0194 ```
0195
0196 Now let's add one more rule to create benchmark plots:
0197
0198 ```python
0199 rule your_benchmark_plots:
0200 input:
0201 script=workflow.source_path("macros/plot_rho_physics_benchmark.C"),
0202 plots=f"sim_output/campaign_25.10.2_combined_{{N}}files.eicrecon.edm4eic.plots.root",
0203 output:
0204 f"sim_output/campaign_25.10.2_combined_{{N}}files.eicrecon.edm4eic.plots_figures/benchmark_rho_mass.pdf",
0205 shell:
0206 """
0207 if [ ! -d "{input.plots}_figures" ]; then
0208 mkdir "{input.plots}_figures"
0209 echo "{input.plots}_figures directory created successfully."
0210 else
0211 echo "{input.plots}_figures directory already exists."
0212 fi
0213 root -l -b -q '{input.script}("{input.plots}")'
0214 """
0215 ```
0216
0217 Now run the new rule by requesting a benchmark figure made from 10 simulation campaign files:
0218
0219 ```bash
0220 snakemake --cores 2 sim_output/campaign_25.10.2_combined_10files.eicrecon.edm4eic.plots_figures/benchmark_rho_mass.pdf
0221 ```
0222
0223 Now check that the three benchmark figures were created:
0224
0225 ```bash
0226 ls sim_output/campaign_25.10.2_combined_10files.eicrecon.edm4eic.plots_figures/*.pdf
0227 ```
0228
0229 You should see three pdfs.
0230 We did it!
0231
0232 Now that our Snakefile is totally set up, the big advantage of Snakemake is how it manages your workflow.
0233 If you edit the plotting macro and then rerun:
0234
0235 ```bash
0236 snakemake --cores 2 sim_output/campaign_25.10.2_combined_10files.eicrecon.edm4eic.plots_figures/benchmark_rho_mass.pdf
0237 ```
0238
0239 Snakemake will recognize that simulation campaign files have already been downloaded, that the analysis scripts have already run, and the files have already been combined. It will only run the last step, the plotting macro, if that's the only thing that needs to be re-run.
0240
0241 If the analysis script changes, Snakemake will only re-run the analysis script and everything after.
0242
0243 If we want to scale up the plots to include 15 simulation campaign files instead of just 10, then for those 5 extra files only Snakemake will rerun all the steps, and combine with the existing 10 files.
0244
0245 The final Snakefile should look like this:
0246
0247 ```python
0248 rule your_benchmark_campaign_reco_get:
0249 output:
0250 f"sim_output/rho_10x100_uChannel_Q2of0to10_hiDiv.{{INDEX}}.eicrecon.edm4eic.root",
0251 retries: 3
0252 shell: """
0253 xrdcp root://dtn-eic.jlab.org//volatile/eic/EPIC/RECO/25.10.2/epic_craterlake/EXCLUSIVE/UCHANNEL_RHO/10x100/rho_10x100_uChannel_Q2of0to10_hiDiv.{wildcards.INDEX}.eicrecon.edm4eic.root {output}
0254 """
0255
0256 rule your_benchmark_analysis:
0257 input:
0258 script=workflow.source_path("analysis/uchannelrho.cxx"),
0259 data=f"sim_output/rho_10x100_uChannel_Q2of0to10_hiDiv.{{INDEX}}.eicrecon.edm4eic.root",
0260 output:
0261 plots=f"sim_output/campaign_25.10.2_{{INDEX}}.eicrecon.edm4eic/plots.root",
0262 shell:
0263 """
0264 mkdir -p $(dirname "{output.plots}")
0265 root -l -b -q '{input.script}+("{input.data}","{output.plots}")'
0266 """
0267
0268 rule your_benchmark_combine:
0269 input:
0270 lambda wildcards: expand(
0271 f"sim_output/campaign_25.10.2_{{INDEX:04d}}.eicrecon.edm4eic/plots.root",
0272 INDEX=range(int(wildcards.N)),
0273 ),
0274 wildcard_constraints:
0275 N="\d+",
0276 output:
0277 f"sim_output/campaign_25.10.2_combined_{{N}}files.eicrecon.edm4eic.plots.root",
0278 shell:
0279 """
0280 hadd {output} {input}
0281 """
0282
0283 rule your_benchmark_plots:
0284 input:
0285 script=workflow.source_path("macros/plot_rho_physics_benchmark.C"),
0286 plots=f"sim_output/campaign_25.10.2_combined_{{N}}files.eicrecon.edm4eic.plots.root",
0287 output:
0288 f"sim_output/campaign_25.10.2_combined_{{N}}files.eicrecon.edm4eic.plots_figures/benchmark_rho_mass.pdf",
0289 shell:
0290 """
0291 if [ ! -d "{input.plots}_figures" ]; then
0292 mkdir "{input.plots}_figures"
0293 echo "{input.plots}_figures directory created successfully."
0294 else
0295 echo "{input.plots}_figures directory already exists."
0296 fi
0297 root -l -b -q '{input.script}("{input.plots}")'
0298 """
0299
0300 ```
0301
0302 ## Conclusion
0303
0304 In this exercise we've built an analysis workflow using Snakemake. That required us to think about the flow of the data and come up with a file naming scheme to reflect it. This approach can be scaled between local testing with handful of files and largely parallel analyses on full datasets.
0305
0306 ::::::::::::::::::::::::::::::::::::::::::::::: keypoints
0307
0308 - Snakemake allows one to run their data analyses and share them with others
0309
0310 :::::::::::::::::::::::::::::::::::::::::::::::