From 64fe60f6e062e892fd12a94f50d74307a3814a52 Mon Sep 17 00:00:00 2001 From: Junghyun <66112883+Avendus@users.noreply.github.com> Date: Thu, 18 Sep 2025 23:42:27 +0900 Subject: [PATCH 1/2] Add Korean guide to repository structure --- README.md | 69 ++++++++++++++++++++++++- bin/mkanalyzer.py | 128 +++++++++++++++++++++++++--------------------- tnm/tnm.cc | 25 ++++++--- tnm/tnm.h | 1 + 4 files changed, 157 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index d4b5263..9a3a0bd 100644 --- a/README.md +++ b/README.md @@ -35,5 +35,72 @@ ANALYZER UTILITIES 1. __mkvariables.py__ reads a Root file and creates the file __variables.txt__ containing a description of (by default) the first tree it finds. -2. __mkanalyzer.py__ reads __variables.txt__ and creates the skeleton of an C++ +2. __mkanalyzer.py__ reads __variables.txt__ and creates the skeleton of an C++ and Python analyzer program for the Root tree. + +## 코드 구조 쉽게 이해하기 (Korean Guide) + +### 전체 흐름 한눈에 보기 +이 저장소는 CMS 실험에서 자주 사용하는 NanoAOD, miniAOD와 같은 다양한 형태의 ntuple을 빠르게 분석할 수 있도록 *뼈대 코드(skeleton)*를 생성하고, 공통으로 재사용할 수 있는 보조 함수들을 제공합니다. + +1. **`bin/mkanalyzer.py`** – 분석기(Analyzer) 뼈대 생성기 + - `variables.txt`를 읽고, C++/Python으로 된 예제 분석기를 만들어 줍니다. + - 실행 옵션을 다루는 코드, 이벤트 루프, 출력 파일 관리, 그리고 선택한 브랜치(branch)만 불러오는 기능까지 기본 구조를 모두 갖춘 템플릿을 만들어 줍니다. + - `--debug` 플래그가 켜지면 처리 속도나 주요 단계에 대한 로그를 더 자세히 출력하도록 설계되어 있습니다. + +2. **`tnm/tnm.h` & `tnm/tnm.cc`** – 분석기에서 공용으로 사용하는 유틸리티 모음 + - 출력 파일 관리(`outputFile`), 명령줄 파싱(`commandLine`), 사전적인 물리 계산 함수(`deltaR`, `setStyle` 등)를 제공합니다. + - `commandLine` 구조체는 `--debug` 옵션을 인식하여 분석기가 디버그 로그를 활성화할 수 있게 합니다. + +3. **기타 디렉터리** + - `src`, `include`: ROOT 기반 스트리밍 기능에 필요한 핵심 소스와 헤더가 들어 있습니다. + - `test`: 제공되는 테스트 프로그램을 통해 treestream이 올바르게 설치되었는지 확인할 수 있습니다. + +### `eventBuffer`와 브랜치 선택 이해하기 +분석기가 만들어 내는 C++ 코드의 중심에는 `eventBuffer` 클래스가 있습니다. 이 클래스는 다음과 같은 일을 합니다. + +- **브랜치 연결(SetBranchAddress)**: ROOT 트리에서 사용할 변수(브랜치)를 미리 연결해 두고, 이벤트를 읽어 들일 때 값이 자동으로 채워지도록 합니다. +- **선택한 브랜치만 불러오기**: 사용자가 `--branches muon`처럼 특정 접두어(prefix)를 주면, `muon`으로 시작하는 브랜치만 활성화해서 메모리를 절약합니다. 아무 접두어도 주지 않으면 모든 브랜치를 불러옵니다. +- **인덱스 맵(index map)**: 예를 들어 여러 개의 제트(또는 전자)가 있는 이벤트에서, 선택한 객체의 인덱스를 추적하여 후속 계산에서 쉽게 참조할 수 있습니다. + +### `std::unordered_map` 초보자용 설명 +브랜치 선택을 효율적으로 관리하기 위해 코드에는 `std::unordered_map`이 사용됩니다. 간단히 말해, **특정한 이름(key)**과 **그에 대응하는 값(value)**을 빠르게 찾을 수 있게 도와주는 자료구조입니다. + +- **왜 사용할까?** 예를 들어 브랜치 이름을 키로, "이 브랜치를 읽을지 말지"라는 불리언 값을 저장해 둔다고 합시다. `unordered_map`은 원하는 브랜치 이름을 거의 즉시 찾아볼 수 있어, "이 브랜치를 켜야 할까?"를 빠르게 판단할 수 있습니다. +- **어떻게 생겼을까?** 파이썬의 `dict`와 비슷하게 키-값 쌍을 저장합니다. 단, C++에서는 각 키와 값의 자료형을 명확히 적어줘야 합니다. 이 프로젝트에서는 대략 `std::unordered_map`과 같은 형태로, 문자열 키(브랜치 이름)와 불리언 값(사용 여부)을 저장합니다. +- **사용 예시** + ```cpp + std::unordered_map branchEnabled; + branchEnabled["Muon_pt"] = true; // "Muon_pt" 브랜치를 읽겠다는 뜻 + branchEnabled["Photon_pt"] = false; // "Photon_pt" 브랜치는 건너뛰겠다는 뜻 + + if (branchEnabled["Muon_pt"]) { + // 실제로 브랜치를 ROOT 트리에 연결하는 코드 수행 + } + ``` +- **주의할 점**: `unordered_map`은 내부적으로 빠른 탐색을 위해 해시(hash)를 사용합니다. 따라서 키를 추가하거나 찾는 연산이 평균적으로 매우 빠릅니다. + +### 디버그 로그와 작업 완료 확인 +생성되는 분석기 템플릿은 `--debug` 옵션을 통해 디버그 로그를 켤 수 있습니다. 이를 활용하면 다음과 같은 정보를 쉽게 확인할 수 있습니다. + +- 현재 몇 번째 이벤트를 처리 중인지 +- 입력 파일이 몇 개인지, 각 파일의 처리가 끝났는지 +- 최종적으로 생성된 출력 파일과 저장된 히스토그램 요약 + +디버그 모드가 아니더라도, 중요한 단계마다 "어떤 파일을 읽었는지", "전체 이벤트 처리가 끝났는지" 등을 알려주는 요약 로그가 출력되도록 설계되어 있습니다. 이렇게 하면 긴 배치 작업을 돌린 뒤에도 결과가 정상적으로 끝났는지 안심하고 확인할 수 있습니다. + +### ROOT6/ROOT7 대비를 위한 현대적인 작성법 +코드는 ROOT6 이상 환경에서 권장하는 `rootcling`을 사용해 dictionary를 생성합니다. 이는 클래스 정보를 ROOT에 알려 주어, 트리가 해당 클래스 객체를 저장하거나 읽을 수 있게 하기 위해 필요합니다. 분석에 맞춰 dictionary가 더 이상 필요하지 않다면 `Makefile`에서 해당 규칙을 제거해도 되지만, 사용자 정의 클래스(예: `eventBuffer`)를 ROOT I/O와 함께 쓰고 싶다면 유지하는 것이 안전합니다. + +생성되는 C++ 템플릿은 다음과 같은 현대적인 C++ 문법을 사용합니다. + +- `auto`와 범위 기반 for문(`for (auto &entry : container)`)으로 가독성을 높임 +- `std::unique_ptr` 등 스마트 포인터 사용으로 메모리 누수를 예방함 +- 명시적인 `#include`와 네임스페이스 사용으로 ROOT6/ROOT7 환경에서도 깨끗하게 빌드될 수 있도록 정리함 + +### 더 알아보기 +- 분석기를 처음 작성한다면 `variables.txt`를 만들기 위해 `bin/mkvariables.py`를 먼저 실행해 보세요. +- 생성된 C++ 분석기는 `make`를 통해 컴파일하고, Python 분석기는 바로 실행할 수 있습니다. +- ROOT 환경 설정이 필요하므로, `setup.sh` 혹은 개인이 사용하는 ROOT 환경 설정 스크립트를 먼저 실행하는 것을 권장합니다. + +이 가이드는 초보자도 treestream의 구조를 빠르게 이해하고, 현대적인 ROOT 분석 코드를 작성하는 데 도움을 주기 위한 것입니다. diff --git a/bin/mkanalyzer.py b/bin/mkanalyzer.py index 6333964..4b998cb 100755 --- a/bin/mkanalyzer.py +++ b/bin/mkanalyzer.py @@ -197,6 +197,7 @@ def getauthor(): #include #include #include +#include #include #include "treestream.h" @@ -219,11 +220,11 @@ def getauthor(): %(selectimpl)s //-------------------------------------------------------------------------- // A read-only buffer - eventBuffer() : input(0), output(0), choose(std::map()) {} + eventBuffer() : input(nullptr), output(nullptr), branchSelection() {} eventBuffer(itreestream& stream, std::string varlist="") : input(&stream), - output(0), - choose(std::map()) + output(nullptr), + branchSelection() { if ( !input->good() ) { @@ -234,10 +235,10 @@ def getauthor(): initBuffers(); - // default is to select all branches - bool DEFAULT = varlist == ""; + // default is to select all branches + bool selectAll = varlist == ""; %(choose)s - if ( DEFAULT ) + if ( selectAll ) { std::cout << std::endl << "eventBuffer - All branches selected" @@ -254,17 +255,16 @@ def getauthor(): sin >> key; if ( sin ) { - std::map::iterator it; - for(it = choose.begin(); it != choose.end(); it++) - { - if ( it->first.length() > key.length() ) - { - if ( it->first.substr(0, key.size()) == key ) - { - choose[it->first] = true; - } - } - } + for(auto& item : branchSelection) + { + if ( item.first.length() > key.length() ) + { + if ( item.first.substr(0, key.size()) == key ) + { + item.second = true; + } + } + } } } } @@ -297,11 +297,8 @@ def getauthor(): input->read(entry); // clear indexmap - for(std::map >::iterator - item=indexmap.begin(); - item != indexmap.end(); - ++item) - item->second.clear(); + for(auto& item : indexmap) + item.second.clear(); } void select(std::string objname) @@ -346,14 +343,14 @@ def getauthor(): // --- indexmap keeps track of which objects have been flagged for selection std::map > indexmap; - // to read events - itreestream* input; +// to read events +itreestream* input; - // to write events - otreestream* output; +// to write events +otreestream* output; - // switches for choosing branches - std::map choose; +// switches for choosing branches + std::unordered_map branchSelection; }; #endif @@ -379,12 +376,16 @@ def getauthor(): // Get command line arguments commandLine cl(argc, argv); - + + if (cl.debug) + cout << "[DEBUG] Debugging enabled" << endl; + // Get names of ntuple files to be processed - vector filenames = fileNames(cl.filelist); + vector inputFiles = fileNames(cl.filelist); + cout << "Processing " << inputFiles.size() << " file(s)" << endl; // Create tree reader - itreestream stream(filenames, "%(treename)s"); + itreestream stream(inputFiles, "%(treename)s"); if ( !stream.good() ) error("can't read root input files"); // Create a buffer to receive events from the stream @@ -392,12 +393,12 @@ def getauthor(): // Use second argument to select specific branches // Example: // varlist = 'Jet_PT Jet_Eta Jet_Phi' - // ev = eventBuffer(stream, varlist) + // event = eventBuffer(stream, varlist) - eventBuffer ev(stream); - - int nevents = ev.size(); - cout << "number of events: " << nevents << endl; + eventBuffer event(stream); + + size_t numEvents = event.size(); + cout << "Number of events: " << numEvents << endl; // Create output file for histograms; see notes in header outputFile of(cl.outputfilename); @@ -411,15 +412,18 @@ def getauthor(): // Loop over events // ------------------------------------------------------------------------- - for(int entry=0; entry < nevents; entry++) + for(size_t entry = 0; entry < numEvents; ++entry) { // read an event into event buffer - ev.read(entry); - + event.read(entry); + if (cl.debug && entry % 100000 == 0) + cout << "[DEBUG] processed " << entry << " events" << endl; } - - ev.close(); + + event.close(); of.close(); + cout << "Analysis complete. Output written to " + << cl.outputfilename << endl; return 0; } ''' @@ -445,12 +449,16 @@ def getauthor(): def main(): cl = ROOT.commandLine() - + + if cl.debug: + print("[DEBUG] Debugging enabled") + # Get names of ntuple files to be processed - filenames = ROOT.fileNames(cl.filelist) + input_files = ROOT.fileNames(cl.filelist) + print(f"Processing {len(input_files)} file(s)") # Create tree reader - stream = ROOT.itreestream(filenames, "%(treename)s") + stream = ROOT.itreestream(input_files, "%(treename)s") if not stream.good(): error("can't read input files") @@ -459,12 +467,12 @@ def main(): # Use second argument to select specific branches # Example: # varlist = 'Jet_PT Jet_Eta Jet_Phi' - # ev = ROOT.eventBuffer(stream, varlist) + # event = ROOT.eventBuffer(stream, varlist) # - ev = ROOT.eventBuffer(stream) + event = ROOT.eventBuffer(stream) - nevents = ev.size() - print("number of events:", nevents) + num_events = event.size() + print("Number of events:", num_events) # Create file to store histograms of = ROOT.outputFile(cl.outputfilename) @@ -477,11 +485,14 @@ def main(): # ------------------------------------------------------------------------ # Loop over events # ------------------------------------------------------------------------ - for entry in range(nevents): - ev.read(entry) - - ev.close() + for entry in range(num_events): + event.read(entry) + if cl.debug and entry % 100000 == 0: + print(f"[DEBUG] processed {entry} events") + + event.close() of.close() + print("Analysis complete. Output written to", cl.outputfilename) # ---------------------------------------------------------------------------- try: main() @@ -554,7 +565,8 @@ def main(): CXX := g++ LINK := g++ endif -CINT := rootcint +# Use rootcling for ROOT6+ dictionary generation +ROOTCLING := rootcling #----------------------------------------------------------------------- # Define paths to be searched for C++ header files (#include ....) @@ -637,7 +649,7 @@ def main(): $(cintsrc) : $(header) $(linkdef) \t@echo "---> Generating dictionary `basename $@`" -\t$(AT)$(CINT) -f $@ -c -I. -Iinclude -I$(ROOTSYS)/include $+ +\t$(AT)$(ROOTCLING) -f $@ -c -I. -Iinclude -I$(ROOTSYS)/include $+ \t$(AT)mv $(srcdir)/*.pcm $(libdir) # Define clean up rules @@ -962,7 +974,7 @@ def main(): setb = [] addb = [] impl = [] - choose = [] + branchsel = [] # get all leaf counters counters = set() @@ -1018,8 +1030,8 @@ def main(): choosename = str.split(branchname, '/')[-1] else: choosename = branchname - choose.append(' choose["%s"]\t= DEFAULT;' % choosename) - setb.append(' if ( choose["%s"] )' % choosename) + branchsel.append(' branchSelection["%s"]\t= selectAll;' % choosename) + setb.append(' if ( branchSelection["%s"] )' % choosename) cmd = ' input->select("%s", \t%s);' % (branchname, varname) if len(cmd) < 75: setb.append(cmd) @@ -1251,7 +1263,7 @@ def main(): 'vardecl': join("", declarevec, "\n"), 'init': join("", init, "\n"), 'setb': join(" ", setb, "\n"), - 'choose': join(" ", choose, "\n"), + 'choose': join(" ", branchsel, "\n"), 'addb': join(" ", addb, "\n"), 'structdecl': join("", structdecl, "\n"), 'structimpl': join("", structimpl, "\n"), diff --git a/tnm/tnm.cc b/tnm/tnm.cc index a271e1c..da07260 100644 --- a/tnm/tnm.cc +++ b/tnm/tnm.cc @@ -177,17 +177,28 @@ commandLine::decode(int argc, char** argv) progname = nameonly(std::string(argv[0])); if ( progname == "Python" || progname == "python" ) - progname = string("analyzer"); + progname = std::string("analyzer"); - // 1st (optional) argument - if ( argc > 1 ) - filelist = std::string(argv[1]); + debug = false; + std::vector positional; + for (int i = 1; i < argc; ++i) + { + std::string arg(argv[i]); + if ( arg == "-d" || arg == "--debug" ) + { + debug = true; + continue; + } + positional.push_back(arg); + } + + if ( positional.size() > 0 ) + filelist = positional[0]; else filelist = std::string("filelist.txt"); - // 2nd (optional) command line argument - if ( argc > 2 ) - outputfilename = std::string(argv[2]); + if ( positional.size() > 1 ) + outputfilename = positional[1]; else outputfilename = progname + std::string("_histograms"); diff --git a/tnm/tnm.h b/tnm/tnm.h index c582a72..b68e82a 100644 --- a/tnm/tnm.h +++ b/tnm/tnm.h @@ -55,6 +55,7 @@ struct commandLine std::string progname; std::string filelist; std::string outputfilename; + bool debug; void decode(int argc, char** argv); }; From 609540359c79c99fb66fec9618ad5a2b63b03693 Mon Sep 17 00:00:00 2001 From: Junghyun <66112883+Avendus@users.noreply.github.com> Date: Fri, 19 Sep 2025 00:18:25 +0900 Subject: [PATCH 2/2] Document file roles and annotate analyzer templates --- README.md | 93 ++++++++++-------------------- bin/mkanalyzer.py | 144 ++++++++++++++++++++++++---------------------- tnm/tnm.cc | 25 +++----- tnm/tnm.h | 1 - 4 files changed, 110 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 9a3a0bd..aae3e82 100644 --- a/README.md +++ b/README.md @@ -40,67 +40,32 @@ and Python analyzer program for the Root tree. ## 코드 구조 쉽게 이해하기 (Korean Guide) -### 전체 흐름 한눈에 보기 -이 저장소는 CMS 실험에서 자주 사용하는 NanoAOD, miniAOD와 같은 다양한 형태의 ntuple을 빠르게 분석할 수 있도록 *뼈대 코드(skeleton)*를 생성하고, 공통으로 재사용할 수 있는 보조 함수들을 제공합니다. - -1. **`bin/mkanalyzer.py`** – 분석기(Analyzer) 뼈대 생성기 - - `variables.txt`를 읽고, C++/Python으로 된 예제 분석기를 만들어 줍니다. - - 실행 옵션을 다루는 코드, 이벤트 루프, 출력 파일 관리, 그리고 선택한 브랜치(branch)만 불러오는 기능까지 기본 구조를 모두 갖춘 템플릿을 만들어 줍니다. - - `--debug` 플래그가 켜지면 처리 속도나 주요 단계에 대한 로그를 더 자세히 출력하도록 설계되어 있습니다. - -2. **`tnm/tnm.h` & `tnm/tnm.cc`** – 분석기에서 공용으로 사용하는 유틸리티 모음 - - 출력 파일 관리(`outputFile`), 명령줄 파싱(`commandLine`), 사전적인 물리 계산 함수(`deltaR`, `setStyle` 등)를 제공합니다. - - `commandLine` 구조체는 `--debug` 옵션을 인식하여 분석기가 디버그 로그를 활성화할 수 있게 합니다. - -3. **기타 디렉터리** - - `src`, `include`: ROOT 기반 스트리밍 기능에 필요한 핵심 소스와 헤더가 들어 있습니다. - - `test`: 제공되는 테스트 프로그램을 통해 treestream이 올바르게 설치되었는지 확인할 수 있습니다. - -### `eventBuffer`와 브랜치 선택 이해하기 -분석기가 만들어 내는 C++ 코드의 중심에는 `eventBuffer` 클래스가 있습니다. 이 클래스는 다음과 같은 일을 합니다. - -- **브랜치 연결(SetBranchAddress)**: ROOT 트리에서 사용할 변수(브랜치)를 미리 연결해 두고, 이벤트를 읽어 들일 때 값이 자동으로 채워지도록 합니다. -- **선택한 브랜치만 불러오기**: 사용자가 `--branches muon`처럼 특정 접두어(prefix)를 주면, `muon`으로 시작하는 브랜치만 활성화해서 메모리를 절약합니다. 아무 접두어도 주지 않으면 모든 브랜치를 불러옵니다. -- **인덱스 맵(index map)**: 예를 들어 여러 개의 제트(또는 전자)가 있는 이벤트에서, 선택한 객체의 인덱스를 추적하여 후속 계산에서 쉽게 참조할 수 있습니다. - -### `std::unordered_map` 초보자용 설명 -브랜치 선택을 효율적으로 관리하기 위해 코드에는 `std::unordered_map`이 사용됩니다. 간단히 말해, **특정한 이름(key)**과 **그에 대응하는 값(value)**을 빠르게 찾을 수 있게 도와주는 자료구조입니다. - -- **왜 사용할까?** 예를 들어 브랜치 이름을 키로, "이 브랜치를 읽을지 말지"라는 불리언 값을 저장해 둔다고 합시다. `unordered_map`은 원하는 브랜치 이름을 거의 즉시 찾아볼 수 있어, "이 브랜치를 켜야 할까?"를 빠르게 판단할 수 있습니다. -- **어떻게 생겼을까?** 파이썬의 `dict`와 비슷하게 키-값 쌍을 저장합니다. 단, C++에서는 각 키와 값의 자료형을 명확히 적어줘야 합니다. 이 프로젝트에서는 대략 `std::unordered_map`과 같은 형태로, 문자열 키(브랜치 이름)와 불리언 값(사용 여부)을 저장합니다. -- **사용 예시** - ```cpp - std::unordered_map branchEnabled; - branchEnabled["Muon_pt"] = true; // "Muon_pt" 브랜치를 읽겠다는 뜻 - branchEnabled["Photon_pt"] = false; // "Photon_pt" 브랜치는 건너뛰겠다는 뜻 - - if (branchEnabled["Muon_pt"]) { - // 실제로 브랜치를 ROOT 트리에 연결하는 코드 수행 - } - ``` -- **주의할 점**: `unordered_map`은 내부적으로 빠른 탐색을 위해 해시(hash)를 사용합니다. 따라서 키를 추가하거나 찾는 연산이 평균적으로 매우 빠릅니다. - -### 디버그 로그와 작업 완료 확인 -생성되는 분석기 템플릿은 `--debug` 옵션을 통해 디버그 로그를 켤 수 있습니다. 이를 활용하면 다음과 같은 정보를 쉽게 확인할 수 있습니다. - -- 현재 몇 번째 이벤트를 처리 중인지 -- 입력 파일이 몇 개인지, 각 파일의 처리가 끝났는지 -- 최종적으로 생성된 출력 파일과 저장된 히스토그램 요약 - -디버그 모드가 아니더라도, 중요한 단계마다 "어떤 파일을 읽었는지", "전체 이벤트 처리가 끝났는지" 등을 알려주는 요약 로그가 출력되도록 설계되어 있습니다. 이렇게 하면 긴 배치 작업을 돌린 뒤에도 결과가 정상적으로 끝났는지 안심하고 확인할 수 있습니다. - -### ROOT6/ROOT7 대비를 위한 현대적인 작성법 -코드는 ROOT6 이상 환경에서 권장하는 `rootcling`을 사용해 dictionary를 생성합니다. 이는 클래스 정보를 ROOT에 알려 주어, 트리가 해당 클래스 객체를 저장하거나 읽을 수 있게 하기 위해 필요합니다. 분석에 맞춰 dictionary가 더 이상 필요하지 않다면 `Makefile`에서 해당 규칙을 제거해도 되지만, 사용자 정의 클래스(예: `eventBuffer`)를 ROOT I/O와 함께 쓰고 싶다면 유지하는 것이 안전합니다. - -생성되는 C++ 템플릿은 다음과 같은 현대적인 C++ 문법을 사용합니다. - -- `auto`와 범위 기반 for문(`for (auto &entry : container)`)으로 가독성을 높임 -- `std::unique_ptr` 등 스마트 포인터 사용으로 메모리 누수를 예방함 -- 명시적인 `#include`와 네임스페이스 사용으로 ROOT6/ROOT7 환경에서도 깨끗하게 빌드될 수 있도록 정리함 - -### 더 알아보기 -- 분석기를 처음 작성한다면 `variables.txt`를 만들기 위해 `bin/mkvariables.py`를 먼저 실행해 보세요. -- 생성된 C++ 분석기는 `make`를 통해 컴파일하고, Python 분석기는 바로 실행할 수 있습니다. -- ROOT 환경 설정이 필요하므로, `setup.sh` 혹은 개인이 사용하는 ROOT 환경 설정 스크립트를 먼저 실행하는 것을 권장합니다. - -이 가이드는 초보자도 treestream의 구조를 빠르게 이해하고, 현대적인 ROOT 분석 코드를 작성하는 데 도움을 주기 위한 것입니다. +### 핵심 파일별 역할 +- **`bin/mkanalyzer.py`**: `variables.txt`를 읽어 C++/Python 분석기, `eventBuffer` 헤더, 링크 정의, Makefile, README까지 한 번에 생성합니다. 템플릿 문자열 안에 실제 코드 조각을 모아두고, 사용자가 지정한 출력 디렉터리에 필요한 파일을 모두 써 줍니다. +- **`bin/mklist.py`**: 입력 루트 파일에서 사용 가능한 트리와 브랜치를 훑어 보고, 어떤 변수를 `variables.txt`에 담을지 미리 확인할 수 있는 간단한 나열 도구입니다. +- **`bin/mkvariables.py`**: 지정한 ntuple을 열어 브랜치 정보를 추출한 뒤, `mkanalyzer.py`가 읽을 `variables.txt`를 자동으로 만들어 줍니다. +- **`include/pdg.h` · `src/pdg.cc`**: PDG ID를 사람이 읽을 이름으로 바꿔 주거나, Monte Carlo 입자 계보를 출력하는 유틸리티 함수 모음입니다. 분석기에서 입자 정보를 해석할 때 바로 쓸 수 있게 독립 모듈로 제공됩니다. +- **`include/treestream.h` · `src/treestream.cc`**: `itreestream`/`otreestream` 클래스를 정의하여 ROOT 트리를 C++ 객체처럼 순회하거나 새 트리를 저장할 수 있는 핵심 I/O 계층입니다. `eventBuffer`가 실제 데이터를 불러오거나 쓰는 기반이 됩니다. +- **`tnm/tnm.h` · `tnm/tnm.cc`**: 분석기 실행에 필요한 공용 유틸리티(명령줄 파싱, 출력 파일 관리, 자주 쓰는 수학 함수 등)를 모았습니다. `mkanalyzer.py`가 만들어 주는 분석기 본문에서 바로 include 하도록 설계되어 있습니다. + +### `mkanalyzer.py`가 뼈대 코드를 엮는 방법 +1. `variables.txt`를 파싱해 각 브랜치의 타입·길이·카운터 정보를 수집합니다. +2. 위 정보를 `eventBuffer` 템플릿(`TEMPLATE_H`, `TEMPLATE_CC`)에 채워 넣어, `treestream.h`의 스트림 클래스와 연동되는 버퍼 클래스를 생성합니다. +3. 생성되는 분석기 C++ 파일은 `tnm/tnm.h`를 include 하여 `commandLine`, `outputFile`, `fileNames` 등 공용 함수를 재사용하고, 앞서 생성된 `eventBuffer.h`를 통해 트리 데이터를 읽습니다. +4. Python 템플릿 역시 ROOT 파이썬 바인딩에서 `tnm` 함수와 `eventBuffer` 클래스를 불러와 동일한 흐름을 따르도록 구성되어 있습니다. +5. Makefile 템플릿은 `treestream.cc`, `pdg.cc` 등의 라이브러리를 링크하고, 필요하면 ROOT dictionary를 만들도록 규칙을 포함합니다. 설치 후에는 `include/`와 `src/`의 코드가 그대로 재사용됩니다. + +### 기존 코드와 이전 제안의 차이 +- 원본 `mkanalyzer.py`는 브랜치 선택 토글을 `std::map`으로 구현하고, 이벤트 루프는 최소한의 로그만 제공합니다. +- 이전 제안에서는 이 부분을 `std::unordered_map`과 추가 디버그 인자로 확장하려 했으나 구조 자체는 크게 바뀌지 않았습니다. +- 현재 저장소에는 원본 흐름을 복원하고, 향후에 직접 개선할 수 있도록 **한국어 주석**으로 개선 포인트(예: `std::unordered_map`으로 교체, 디버그 로그 추가, dictionary 규칙 끄기)를 표시해 두었습니다. + +### PhysicsTools/TheNtupleMaker 의존성 정리 +- `src/treestream.cc`, `src/pdg.cc`, `tnm/tnm.h` 등은 `#include "PhysicsTools/TheNtupleMaker/interface/..."`를 먼저 시도한 뒤, 같은 파일을 로컬 경로에서 다시 include 합니다. 이는 CMSSW 안에서 사용할 때 기존 모듈과 충돌하지 않도록 하기 위한 **호환성 장치**입니다. +- 이 저장소만으로도 빌드가 가능하도록 로컬 헤더가 바로 이어서 include 되므로, CMSSW 환경이 아니라면 추가 의존성은 없습니다. +- 만약 해당 경로를 완전히 없애고 싶다면, 위 파일들의 `#ifdef PROJECT_NAME` 블록을 정리하고, Makefile/컴파일 옵션에서 `-Iinclude` 만 남겨도 됩니다. README와 `bin/mkanalyzer.py`의 주석에 dictionary 규칙을 해제하는 방법도 함께 안내되어 있습니다. + +### 향후 수정 포인트 안내 +- `bin/mkanalyzer.py`의 템플릿 안에 `// NOTE(KR): ...` 또는 `# NOTE(KR): ...` 형태의 주석을 추가했습니다. `std::map` 대신 `std::unordered_map`으로 바꾸거나, 디버그 로그/진행률 출력, dictionary 생성 비활성화 등을 적용하고 싶을 때 해당 위치를 찾아 수정하면 됩니다. +- Python 템플릿에도 같은 위치에 주석이 있으니, C++과 Python 분석기를 동시에 유지하려면 두 곳을 함께 손보면 됩니다. +- 필요한 변경을 직접 시도하면서 README를 참고하면, NanoAOD뿐 아니라 miniAOD 등 다른 ntuple 구조에도 맞게 뼈대를 확장하기 수월해집니다. diff --git a/bin/mkanalyzer.py b/bin/mkanalyzer.py index 4b998cb..9dfe2b8 100755 --- a/bin/mkanalyzer.py +++ b/bin/mkanalyzer.py @@ -197,7 +197,6 @@ def getauthor(): #include #include #include -#include #include #include "treestream.h" @@ -220,11 +219,13 @@ def getauthor(): %(selectimpl)s //-------------------------------------------------------------------------- // A read-only buffer - eventBuffer() : input(nullptr), output(nullptr), branchSelection() {} + eventBuffer() : input(0), output(0), choose(std::map()) {} + // NOTE(KR): 여기서 choose 자료구조를 std::unordered_map 등으로 + // 바꿔 메모리/탐색 효율을 개선할 수 있습니다. eventBuffer(itreestream& stream, std::string varlist="") : input(&stream), - output(nullptr), - branchSelection() + output(0), + choose(std::map()) { if ( !input->good() ) { @@ -235,10 +236,10 @@ def getauthor(): initBuffers(); - // default is to select all branches - bool selectAll = varlist == ""; + // default is to select all branches + bool DEFAULT = varlist == ""; %(choose)s - if ( selectAll ) + if ( DEFAULT ) { std::cout << std::endl << "eventBuffer - All branches selected" @@ -255,16 +256,20 @@ def getauthor(): sin >> key; if ( sin ) { - for(auto& item : branchSelection) - { - if ( item.first.length() > key.length() ) - { - if ( item.first.substr(0, key.size()) == key ) - { - item.second = true; - } - } - } + // NOTE(KR): 접두사 비교 로직을 더 직관적으로 바꾸고 싶다면 + // 여기서 range-based for 문과 std::string_view 등을 + // 도입해도 됩니다. + std::map::iterator it; + for(it = choose.begin(); it != choose.end(); it++) + { + if ( it->first.length() > key.length() ) + { + if ( it->first.substr(0, key.size()) == key ) + { + choose[it->first] = true; + } + } + } } } } @@ -297,8 +302,12 @@ def getauthor(): input->read(entry); // clear indexmap - for(auto& item : indexmap) - item.second.clear(); + // NOTE(KR): range-based for 문과 구조적 바인딩을 쓰면 더 간결하게 비울 수 있습니다. + for(std::map >::iterator + item=indexmap.begin(); + item != indexmap.end(); + ++item) + item->second.clear(); } void select(std::string objname) @@ -343,14 +352,14 @@ def getauthor(): // --- indexmap keeps track of which objects have been flagged for selection std::map > indexmap; -// to read events -itreestream* input; + // to read events + itreestream* input; -// to write events -otreestream* output; + // to write events + otreestream* output; -// switches for choosing branches - std::unordered_map branchSelection; + // switches for choosing branches + std::map choose; }; #endif @@ -376,16 +385,14 @@ def getauthor(): // Get command line arguments commandLine cl(argc, argv); - - if (cl.debug) - cout << "[DEBUG] Debugging enabled" << endl; - + // NOTE(KR): cl 구조체에 debug 플래그를 추가하고 싶다면 + // tnm/tnm.h, tnm/tnm.cc를 함께 수정해야 합니다. + // Get names of ntuple files to be processed - vector inputFiles = fileNames(cl.filelist); - cout << "Processing " << inputFiles.size() << " file(s)" << endl; + vector filenames = fileNames(cl.filelist); // Create tree reader - itreestream stream(inputFiles, "%(treename)s"); + itreestream stream(filenames, "%(treename)s"); if ( !stream.good() ) error("can't read root input files"); // Create a buffer to receive events from the stream @@ -393,12 +400,14 @@ def getauthor(): // Use second argument to select specific branches // Example: // varlist = 'Jet_PT Jet_Eta Jet_Phi' - // event = eventBuffer(stream, varlist) + // ev = eventBuffer(stream, varlist) - eventBuffer event(stream); - - size_t numEvents = event.size(); - cout << "Number of events: " << numEvents << endl; + eventBuffer ev(stream); + + int nevents = ev.size(); + cout << "number of events: " << nevents << endl; + // NOTE(KR): 디버그 모드에서 처리 상황을 보고하려면 여기에서 + // 주기적으로 로그를 출력하도록 루프를 확장하세요. // Create output file for histograms; see notes in header outputFile of(cl.outputfilename); @@ -412,18 +421,16 @@ def getauthor(): // Loop over events // ------------------------------------------------------------------------- - for(size_t entry = 0; entry < numEvents; ++entry) + for(int entry=0; entry < nevents; entry++) { // read an event into event buffer - event.read(entry); - if (cl.debug && entry % 100000 == 0) - cout << "[DEBUG] processed " << entry << " events" << endl; + ev.read(entry); + // NOTE(KR): entry 인덱스를 이용해 진행률/디버그 로그를 남기는 + // 코드를 추가하기 좋은 위치입니다. } - - event.close(); + + ev.close(); of.close(); - cout << "Analysis complete. Output written to " - << cl.outputfilename << endl; return 0; } ''' @@ -449,16 +456,14 @@ def getauthor(): def main(): cl = ROOT.commandLine() - - if cl.debug: - print("[DEBUG] Debugging enabled") - + # NOTE(KR): commandLine 클래스에 debug 속성을 추가했다면 + # 여기에서 cl.debug 값을 읽어와 로그에 활용하세요. + # Get names of ntuple files to be processed - input_files = ROOT.fileNames(cl.filelist) - print(f"Processing {len(input_files)} file(s)") + filenames = ROOT.fileNames(cl.filelist) # Create tree reader - stream = ROOT.itreestream(input_files, "%(treename)s") + stream = ROOT.itreestream(filenames, "%(treename)s") if not stream.good(): error("can't read input files") @@ -467,12 +472,12 @@ def main(): # Use second argument to select specific branches # Example: # varlist = 'Jet_PT Jet_Eta Jet_Phi' - # event = ROOT.eventBuffer(stream, varlist) + # ev = ROOT.eventBuffer(stream, varlist) # - event = ROOT.eventBuffer(stream) + ev = ROOT.eventBuffer(stream) - num_events = event.size() - print("Number of events:", num_events) + nevents = ev.size() + print("number of events:", nevents) # Create file to store histograms of = ROOT.outputFile(cl.outputfilename) @@ -485,14 +490,13 @@ def main(): # ------------------------------------------------------------------------ # Loop over events # ------------------------------------------------------------------------ - for entry in range(num_events): - event.read(entry) - if cl.debug and entry % 100000 == 0: - print(f"[DEBUG] processed {entry} events") - - event.close() + for entry in range(nevents): + ev.read(entry) + # NOTE(KR): Python 분석기에서도 진행률 바 혹은 조건부 로그를 + # 출력하려면 이 위치에서 print나 logging을 호출하면 됩니다. + + ev.close() of.close() - print("Analysis complete. Output written to", cl.outputfilename) # ---------------------------------------------------------------------------- try: main() @@ -565,8 +569,7 @@ def main(): CXX := g++ LINK := g++ endif -# Use rootcling for ROOT6+ dictionary generation -ROOTCLING := rootcling +CINT := rootcint #----------------------------------------------------------------------- # Define paths to be searched for C++ header files (#include ....) @@ -649,8 +652,9 @@ def main(): $(cintsrc) : $(header) $(linkdef) \t@echo "---> Generating dictionary `basename $@`" -\t$(AT)$(ROOTCLING) -f $@ -c -I. -Iinclude -I$(ROOTSYS)/include $+ +\t$(AT)$(CINT) -f $@ -c -I. -Iinclude -I$(ROOTSYS)/include $+ \t$(AT)mv $(srcdir)/*.pcm $(libdir) +\t# NOTE(KR): dictionary가 필요 없다면 위 규칙과 sharedlib 의존성을 주석 처리하세요. # Define clean up rules clean : @@ -974,7 +978,7 @@ def main(): setb = [] addb = [] impl = [] - branchsel = [] + choose = [] # get all leaf counters counters = set() @@ -1030,8 +1034,8 @@ def main(): choosename = str.split(branchname, '/')[-1] else: choosename = branchname - branchsel.append(' branchSelection["%s"]\t= selectAll;' % choosename) - setb.append(' if ( branchSelection["%s"] )' % choosename) + choose.append(' choose["%s"]\t= DEFAULT;' % choosename) + setb.append(' if ( choose["%s"] )' % choosename) cmd = ' input->select("%s", \t%s);' % (branchname, varname) if len(cmd) < 75: setb.append(cmd) @@ -1263,7 +1267,7 @@ def main(): 'vardecl': join("", declarevec, "\n"), 'init': join("", init, "\n"), 'setb': join(" ", setb, "\n"), - 'choose': join(" ", branchsel, "\n"), + 'choose': join(" ", choose, "\n"), 'addb': join(" ", addb, "\n"), 'structdecl': join("", structdecl, "\n"), 'structimpl': join("", structimpl, "\n"), diff --git a/tnm/tnm.cc b/tnm/tnm.cc index da07260..a271e1c 100644 --- a/tnm/tnm.cc +++ b/tnm/tnm.cc @@ -177,28 +177,17 @@ commandLine::decode(int argc, char** argv) progname = nameonly(std::string(argv[0])); if ( progname == "Python" || progname == "python" ) - progname = std::string("analyzer"); + progname = string("analyzer"); - debug = false; - std::vector positional; - for (int i = 1; i < argc; ++i) - { - std::string arg(argv[i]); - if ( arg == "-d" || arg == "--debug" ) - { - debug = true; - continue; - } - positional.push_back(arg); - } - - if ( positional.size() > 0 ) - filelist = positional[0]; + // 1st (optional) argument + if ( argc > 1 ) + filelist = std::string(argv[1]); else filelist = std::string("filelist.txt"); - if ( positional.size() > 1 ) - outputfilename = positional[1]; + // 2nd (optional) command line argument + if ( argc > 2 ) + outputfilename = std::string(argv[2]); else outputfilename = progname + std::string("_histograms"); diff --git a/tnm/tnm.h b/tnm/tnm.h index b68e82a..c582a72 100644 --- a/tnm/tnm.h +++ b/tnm/tnm.h @@ -55,7 +55,6 @@ struct commandLine std::string progname; std::string filelist; std::string outputfilename; - bool debug; void decode(int argc, char** argv); };