RooAgent 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rooagent-0.1.0/LICENSE +21 -0
- rooagent-0.1.0/PKG-INFO +52 -0
- rooagent-0.1.0/README.md +33 -0
- rooagent-0.1.0/RooAgent.egg-info/PKG-INFO +52 -0
- rooagent-0.1.0/RooAgent.egg-info/SOURCES.txt +19 -0
- rooagent-0.1.0/RooAgent.egg-info/dependency_links.txt +1 -0
- rooagent-0.1.0/RooAgent.egg-info/entry_points.txt +2 -0
- rooagent-0.1.0/RooAgent.egg-info/requires.txt +7 -0
- rooagent-0.1.0/RooAgent.egg-info/top_level.txt +1 -0
- rooagent-0.1.0/pyproject.toml +29 -0
- rooagent-0.1.0/rooagent/__init__.py +2 -0
- rooagent-0.1.0/rooagent/agent.py +153 -0
- rooagent-0.1.0/rooagent/tools/__init__.py +29 -0
- rooagent-0.1.0/rooagent/tools/converter.py +37 -0
- rooagent-0.1.0/rooagent/tools/fit_tools.py +173 -0
- rooagent-0.1.0/rooagent/tools/histogram_tools.py +38 -0
- rooagent-0.1.0/rooagent/tools/plot_tools.py +439 -0
- rooagent-0.1.0/rooagent/tools/rdataframe_tools.py +395 -0
- rooagent-0.1.0/rooagent/tools/tfile.py +178 -0
- rooagent-0.1.0/rooagent/tools/utils.py +59 -0
- rooagent-0.1.0/setup.cfg +4 -0
rooagent-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aman Desai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rooagent-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: RooAgent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent For ROOT
|
|
5
|
+
Author-email: Aman Desai <amanmukeshdesai@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/amanmdesai/RooAgent
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: langchain
|
|
12
|
+
Requires-Dist: langchain-core
|
|
13
|
+
Requires-Dist: langgraph
|
|
14
|
+
Requires-Dist: langchain-openai
|
|
15
|
+
Requires-Dist: uproot
|
|
16
|
+
Requires-Dist: pandas
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# RooAgent
|
|
21
|
+
Agent for ROOT
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Download and Install:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
git clone https://github.com/amanmdesai/RooAgent.git
|
|
28
|
+
cd RooAgent
|
|
29
|
+
pip install .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Installation:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install rooagent
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
External Dependencies
|
|
39
|
+
|
|
40
|
+
Requires a pre-installed version of ROOT that is compatible to work with Python
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
CERN ROOT: - ROOT >= 6.34 (https://root.cern/)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Install ollama's gpt-oss model:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
50
|
+
ollama pull gpt-oss
|
|
51
|
+
```
|
|
52
|
+
|
rooagent-0.1.0/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# RooAgent
|
|
2
|
+
Agent for ROOT
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Download and Install:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
git clone https://github.com/amanmdesai/RooAgent.git
|
|
9
|
+
cd RooAgent
|
|
10
|
+
pip install .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Installation:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pip install rooagent
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
External Dependencies
|
|
20
|
+
|
|
21
|
+
Requires a pre-installed version of ROOT that is compatible to work with Python
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
CERN ROOT: - ROOT >= 6.34 (https://root.cern/)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Install ollama's gpt-oss model:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
31
|
+
ollama pull gpt-oss
|
|
32
|
+
```
|
|
33
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: RooAgent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent For ROOT
|
|
5
|
+
Author-email: Aman Desai <amanmukeshdesai@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/amanmdesai/RooAgent
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: langchain
|
|
12
|
+
Requires-Dist: langchain-core
|
|
13
|
+
Requires-Dist: langgraph
|
|
14
|
+
Requires-Dist: langchain-openai
|
|
15
|
+
Requires-Dist: uproot
|
|
16
|
+
Requires-Dist: pandas
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# RooAgent
|
|
21
|
+
Agent for ROOT
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Download and Install:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
git clone https://github.com/amanmdesai/RooAgent.git
|
|
28
|
+
cd RooAgent
|
|
29
|
+
pip install .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Installation:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install rooagent
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
External Dependencies
|
|
39
|
+
|
|
40
|
+
Requires a pre-installed version of ROOT that is compatible to work with Python
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
CERN ROOT: - ROOT >= 6.34 (https://root.cern/)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Install ollama's gpt-oss model:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
50
|
+
ollama pull gpt-oss
|
|
51
|
+
```
|
|
52
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
RooAgent.egg-info/PKG-INFO
|
|
5
|
+
RooAgent.egg-info/SOURCES.txt
|
|
6
|
+
RooAgent.egg-info/dependency_links.txt
|
|
7
|
+
RooAgent.egg-info/entry_points.txt
|
|
8
|
+
RooAgent.egg-info/requires.txt
|
|
9
|
+
RooAgent.egg-info/top_level.txt
|
|
10
|
+
rooagent/__init__.py
|
|
11
|
+
rooagent/agent.py
|
|
12
|
+
rooagent/tools/__init__.py
|
|
13
|
+
rooagent/tools/converter.py
|
|
14
|
+
rooagent/tools/fit_tools.py
|
|
15
|
+
rooagent/tools/histogram_tools.py
|
|
16
|
+
rooagent/tools/plot_tools.py
|
|
17
|
+
rooagent/tools/rdataframe_tools.py
|
|
18
|
+
rooagent/tools/tfile.py
|
|
19
|
+
rooagent/tools/utils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rooagent
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "RooAgent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Agent For ROOT"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Aman Desai", email="amanmukeshdesai@gmail.com"}
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
dependencies = [
|
|
15
|
+
"langchain",
|
|
16
|
+
"langchain-core",
|
|
17
|
+
"langgraph",
|
|
18
|
+
"langchain-openai",
|
|
19
|
+
"uproot",
|
|
20
|
+
"pandas",
|
|
21
|
+
"numpy",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
requires-python = ">=3.10"
|
|
25
|
+
[project.scripts]
|
|
26
|
+
rooagent = "rooagent.agent:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/amanmdesai/RooAgent"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from langchain_openai import ChatOpenAI
|
|
2
|
+
from langgraph.graph import StateGraph, START, END
|
|
3
|
+
from langchain.messages import HumanMessage, SystemMessage, AnyMessage, ToolMessage
|
|
4
|
+
from typing_extensions import TypedDict, Annotated
|
|
5
|
+
from langgraph.prebuilt import ToolNode
|
|
6
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
7
|
+
import operator
|
|
8
|
+
import os
|
|
9
|
+
# Import all tools
|
|
10
|
+
from .tools import *
|
|
11
|
+
|
|
12
|
+
# -----------------------------
|
|
13
|
+
# SYSTEM PROMPT (Clean Version)
|
|
14
|
+
# -----------------------------
|
|
15
|
+
SYSTEM_PROMPT = """
|
|
16
|
+
ROOT-based HEP analysis assistant.
|
|
17
|
+
|
|
18
|
+
RULES:
|
|
19
|
+
1. Always use tools. Never guess.
|
|
20
|
+
2. Verify everything (files, trees, branches) before use.
|
|
21
|
+
3. Workflow: inspect file -> trees -> branches -> analyze.
|
|
22
|
+
4. Auto-use single tree; list if multiple.
|
|
23
|
+
5. Handle missing data and empty results safely.
|
|
24
|
+
6. Compare signal vs background when relevant.
|
|
25
|
+
7. Output: clear plots, labeled axes, report statistics.
|
|
26
|
+
8. No assumptions. State uncertainty if unverified.
|
|
27
|
+
|
|
28
|
+
Prioritize correctness, efficiency, reproducibility.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# -----------------------------
|
|
32
|
+
# Initialize Model (GitHub Copilot / GitHub Models API)
|
|
33
|
+
# -----------------------------
|
|
34
|
+
DEFAULT_MODEL_NAME = "gpt-4o-mini"
|
|
35
|
+
MODEL_NAME = os.getenv("MODEL", DEFAULT_MODEL_NAME)
|
|
36
|
+
|
|
37
|
+
model = ChatOpenAI(
|
|
38
|
+
model=MODEL_NAME,
|
|
39
|
+
api_key=os.getenv("GITHUB_TOKEN"),
|
|
40
|
+
base_url="https://models.inference.ai.azure.com"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Bind tools
|
|
44
|
+
tools = [
|
|
45
|
+
list_root_file_contents,
|
|
46
|
+
list_tree_branches,
|
|
47
|
+
get_histogram_stats,
|
|
48
|
+
apply_cut_and_count,
|
|
49
|
+
compute_significance,
|
|
50
|
+
plot_tree_variable,
|
|
51
|
+
compare_tree_variables,
|
|
52
|
+
root_tree_to_csv,
|
|
53
|
+
define_variable_and_plot,
|
|
54
|
+
fit_tree_variable,
|
|
55
|
+
fit_histogram,
|
|
56
|
+
draw_histograms_same_canvas,
|
|
57
|
+
draw_2d_histogram,
|
|
58
|
+
find_optimal_cut,
|
|
59
|
+
draw_1d_histogram,
|
|
60
|
+
define_variable,
|
|
61
|
+
draw_2d_histogram_from_tree,
|
|
62
|
+
generate_cutflow,
|
|
63
|
+
discover_root_data,
|
|
64
|
+
list_ttrees,
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
tools_by_name = {tool.name: tool for tool in tools}
|
|
68
|
+
model_with_tools = model.bind_tools(tools)
|
|
69
|
+
|
|
70
|
+
# -----------------------------
|
|
71
|
+
# State Definition
|
|
72
|
+
# -----------------------------
|
|
73
|
+
class MessagesState(TypedDict):
|
|
74
|
+
messages: Annotated[list[AnyMessage], operator.add]
|
|
75
|
+
llm_calls: int
|
|
76
|
+
|
|
77
|
+
# -----------------------------
|
|
78
|
+
# Tool Node
|
|
79
|
+
# -----------------------------
|
|
80
|
+
def tool_node(state: MessagesState):
|
|
81
|
+
results = []
|
|
82
|
+
|
|
83
|
+
for tool_call in state["messages"][-1].tool_calls:
|
|
84
|
+
|
|
85
|
+
tool = tools_by_name[tool_call["name"]]
|
|
86
|
+
observation = tool.invoke(tool_call["args"])
|
|
87
|
+
|
|
88
|
+
results.append(
|
|
89
|
+
ToolMessage(
|
|
90
|
+
content=str(observation),
|
|
91
|
+
tool_call_id=tool_call["id"]
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return {"messages": results}
|
|
96
|
+
|
|
97
|
+
# -----------------------------
|
|
98
|
+
# LLM Node
|
|
99
|
+
# -----------------------------
|
|
100
|
+
def llm_call(state: MessagesState):
|
|
101
|
+
response = model_with_tools.invoke(
|
|
102
|
+
[SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
"messages": [response],
|
|
107
|
+
"llm_calls": state.get("llm_calls", 0) + 1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# -----------------------------
|
|
111
|
+
# Routing
|
|
112
|
+
# -----------------------------
|
|
113
|
+
def should_continue(state: MessagesState):
|
|
114
|
+
last_message = state["messages"][-1]
|
|
115
|
+
if getattr(last_message, "tool_calls", None):
|
|
116
|
+
return "tool_node"
|
|
117
|
+
return END
|
|
118
|
+
|
|
119
|
+
# -----------------------------
|
|
120
|
+
# Build Graph
|
|
121
|
+
# -----------------------------
|
|
122
|
+
builder = StateGraph(MessagesState)
|
|
123
|
+
|
|
124
|
+
builder.add_node("llm_call", llm_call)
|
|
125
|
+
builder.add_node("tool_node",ToolNode(tools,handle_tool_errors=True))
|
|
126
|
+
# tool_node)
|
|
127
|
+
|
|
128
|
+
builder.add_edge(START, "llm_call")
|
|
129
|
+
builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
|
|
130
|
+
builder.add_edge("tool_node", "llm_call")
|
|
131
|
+
|
|
132
|
+
agent = builder.compile()
|
|
133
|
+
|
|
134
|
+
# -----------------------------
|
|
135
|
+
# CLI
|
|
136
|
+
# -----------------------------
|
|
137
|
+
def main():
|
|
138
|
+
print(f"\nROOT Physics Analysis Agent using ({MODEL_NAME})")
|
|
139
|
+
print("Type 'exit' to quit\n")
|
|
140
|
+
|
|
141
|
+
state = {"messages": [], "llm_calls": 0}
|
|
142
|
+
|
|
143
|
+
while True:
|
|
144
|
+
user_input = input("User: ")
|
|
145
|
+
|
|
146
|
+
if user_input.lower() in ["exit", "quit"]:
|
|
147
|
+
print("Thanks for using RooAgent!")
|
|
148
|
+
break
|
|
149
|
+
|
|
150
|
+
# FIXED BUG HERE
|
|
151
|
+
state["messages"].append(HumanMessage(content=user_input))
|
|
152
|
+
|
|
153
|
+
state = agent.invoke(state)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .fit_tools import *
|
|
2
|
+
from .histogram_tools import *
|
|
3
|
+
from .converter import *
|
|
4
|
+
from .plot_tools import *
|
|
5
|
+
from .rdataframe_tools import *
|
|
6
|
+
from .tfile import *
|
|
7
|
+
from .utils import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ================= ROOT STYLE =================
|
|
11
|
+
ROOT.gStyle.SetOptStat(0)
|
|
12
|
+
|
|
13
|
+
ROOT.gStyle.SetTitleFont(42, "XYZ")
|
|
14
|
+
ROOT.gStyle.SetLabelFont(42, "XYZ")
|
|
15
|
+
|
|
16
|
+
ROOT.gStyle.SetTitleSize(0.05, "XYZ")
|
|
17
|
+
ROOT.gStyle.SetLabelSize(0.04, "XYZ")
|
|
18
|
+
|
|
19
|
+
ROOT.gStyle.SetPadLeftMargin(0.12)
|
|
20
|
+
ROOT.gStyle.SetPadBottomMargin(0.12)
|
|
21
|
+
|
|
22
|
+
ROOT.gStyle.SetLegendBorderSize(0)
|
|
23
|
+
ROOT.gStyle.SetLegendFillColor(0)
|
|
24
|
+
|
|
25
|
+
ROOT.gStyle.SetFrameLineWidth(2)
|
|
26
|
+
ROOT.gStyle.SetLineWidth(2)
|
|
27
|
+
|
|
28
|
+
ROOT.gStyle.SetPadGridX(False)
|
|
29
|
+
ROOT.gStyle.SetPadGridY(False)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import ROOT
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from langchain_core.tools import tool
|
|
5
|
+
|
|
6
|
+
@tool
|
|
7
|
+
def root_tree_to_csv(file_path: str, tree_name: str, branches: List[str], output_csv: str, max_vector_size: int = 5) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Convert ROOT TTree to CSV, flattening vector branches.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
file_path : str
|
|
14
|
+
Path to ROOT file.
|
|
15
|
+
tree_name : str
|
|
16
|
+
Name of TTree.
|
|
17
|
+
branches : List[str]
|
|
18
|
+
Branches to extract (scalar or vector).
|
|
19
|
+
output_csv : str
|
|
20
|
+
Output CSV file path.
|
|
21
|
+
max_vector_size : int
|
|
22
|
+
Max number of elements for vector branches (columns will be suffixed _0, _1, ...).
|
|
23
|
+
"""
|
|
24
|
+
df = ROOT.RDataFrame(tree_name, file_path)
|
|
25
|
+
data = df.AsNumpy(branches)
|
|
26
|
+
|
|
27
|
+
flat_dict = {}
|
|
28
|
+
for branch, array in data.items():
|
|
29
|
+
if array.ndim == 1: # scalar
|
|
30
|
+
flat_dict[branch] = array
|
|
31
|
+
else: # vector branch
|
|
32
|
+
for i in range(max_vector_size):
|
|
33
|
+
flat_dict[f"{branch}_{i}"] = [a[i] if i < len(a) else None for a in array]
|
|
34
|
+
|
|
35
|
+
pandas_df = pd.DataFrame(flat_dict)
|
|
36
|
+
pandas_df.to_csv(output_csv, index=False)
|
|
37
|
+
return f"Flattened CSV saved to {output_csv}"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import ROOT
|
|
2
|
+
from langchain_core.tools import tool
|
|
3
|
+
|
|
4
|
+
@tool
|
|
5
|
+
def fit_tree_variable(
|
|
6
|
+
file_path: str,
|
|
7
|
+
tree_name: str,
|
|
8
|
+
variable: str,
|
|
9
|
+
bins: int,
|
|
10
|
+
xmin: float,
|
|
11
|
+
xmax: float,
|
|
12
|
+
fit_function: str,
|
|
13
|
+
output_plot: str
|
|
14
|
+
) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Create a histogram from a ROOT TTree variable, perform a statistical fit,
|
|
17
|
+
and save the resulting plot with legend.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
file_path : str
|
|
22
|
+
Path to the ROOT file containing the TTree.
|
|
23
|
+
tree_name : str
|
|
24
|
+
Name of the TTree to read.
|
|
25
|
+
variable : str
|
|
26
|
+
Name of the variable to histogram and fit.
|
|
27
|
+
bins : int
|
|
28
|
+
Number of bins in the histogram.
|
|
29
|
+
xmin : float
|
|
30
|
+
Minimum x-axis value.
|
|
31
|
+
xmax : float
|
|
32
|
+
Maximum x-axis value.
|
|
33
|
+
fit_function : str
|
|
34
|
+
ROOT fit function (examples: 'gaus', 'expo', 'pol1', 'pol2').
|
|
35
|
+
output_plot : str
|
|
36
|
+
File name where the canvas with histogram and fit will be saved.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
str
|
|
41
|
+
Summary of fit parameters and chi-square/NDF.
|
|
42
|
+
"""
|
|
43
|
+
df = ROOT.RDataFrame(tree_name, file_path)
|
|
44
|
+
hist_ptr = df.Histo1D((variable, variable, bins, xmin, xmax), variable)
|
|
45
|
+
h = hist_ptr.GetValue()
|
|
46
|
+
ROOT.SetOwnership(h, False)
|
|
47
|
+
|
|
48
|
+
canvas = ROOT.TCanvas("c_fit", "", 900, 700)
|
|
49
|
+
#canvas.SetGrid()
|
|
50
|
+
|
|
51
|
+
# Perform fit
|
|
52
|
+
h.Fit(fit_function, "S")
|
|
53
|
+
|
|
54
|
+
# Style histogram
|
|
55
|
+
h.SetLineWidth(3)
|
|
56
|
+
h.SetLineColor(ROOT.kBlue + 1)
|
|
57
|
+
h.SetMarkerStyle(20)
|
|
58
|
+
h.SetMarkerSize(1.2)
|
|
59
|
+
h.GetXaxis().SetTitle(variable)
|
|
60
|
+
h.GetYaxis().SetTitle("Events")
|
|
61
|
+
h.Draw("E")
|
|
62
|
+
|
|
63
|
+
# Style fit function
|
|
64
|
+
func = h.GetFunction(fit_function)
|
|
65
|
+
if func:
|
|
66
|
+
func.SetLineColor(ROOT.kRed)
|
|
67
|
+
func.SetLineWidth(2)
|
|
68
|
+
|
|
69
|
+
# Add legend
|
|
70
|
+
legend = ROOT.TLegend(0.65, 0.7, 0.88, 0.88)
|
|
71
|
+
legend.SetBorderSize(0)
|
|
72
|
+
legend.SetFillStyle(0)
|
|
73
|
+
legend.AddEntry(h, "Histogram", "lep")
|
|
74
|
+
if func:
|
|
75
|
+
legend.AddEntry(func, f"Fit: {fit_function}", "l")
|
|
76
|
+
legend.Draw()
|
|
77
|
+
|
|
78
|
+
canvas.Update()
|
|
79
|
+
canvas.SaveAs(output_plot)
|
|
80
|
+
|
|
81
|
+
# Extract fit parameters
|
|
82
|
+
params = [f"p{i} = {func.GetParameter(i):.4f}" for i in range(func.GetNpar())]
|
|
83
|
+
chi2 = func.GetChisquare()
|
|
84
|
+
ndf = func.GetNDF()
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
f"Fit function: {fit_function}\n"
|
|
88
|
+
f"Parameters: {', '.join(params)}\n"
|
|
89
|
+
f"Chi2/NDF: {chi2:.3f}/{ndf}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@tool
|
|
94
|
+
def fit_histogram(
|
|
95
|
+
file_path: str,
|
|
96
|
+
hist_name: str,
|
|
97
|
+
fit_function: str,
|
|
98
|
+
output_plot: str
|
|
99
|
+
) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Fit an existing histogram in a ROOT file, overlay the fit, and save with legend.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
file_path : str
|
|
106
|
+
Path to the ROOT file containing the histogram.
|
|
107
|
+
hist_name : str
|
|
108
|
+
Name of the histogram to fit.
|
|
109
|
+
fit_function : str
|
|
110
|
+
ROOT fit function (examples: 'gaus', 'expo', 'pol1', 'pol2').
|
|
111
|
+
output_plot : str
|
|
112
|
+
File name where the canvas with histogram and fit will be saved.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
str
|
|
117
|
+
Summary of fit parameters and chi-square/NDF.
|
|
118
|
+
"""
|
|
119
|
+
f = ROOT.TFile.Open(file_path)
|
|
120
|
+
if not f or f.IsZombie():
|
|
121
|
+
return "Error: could not open ROOT file."
|
|
122
|
+
|
|
123
|
+
h = f.Get(hist_name)
|
|
124
|
+
if not h:
|
|
125
|
+
f.Close()
|
|
126
|
+
return f"Histogram '{hist_name}' not found."
|
|
127
|
+
|
|
128
|
+
ROOT.SetOwnership(h, False)
|
|
129
|
+
|
|
130
|
+
canvas = ROOT.TCanvas("c_fit_hist", "", 900, 700)
|
|
131
|
+
#canvas.SetGrid()
|
|
132
|
+
|
|
133
|
+
# Perform fit
|
|
134
|
+
h.Fit(fit_function, "S")
|
|
135
|
+
|
|
136
|
+
# Style histogram
|
|
137
|
+
h.SetLineWidth(3)
|
|
138
|
+
h.SetLineColor(ROOT.kBlue + 1)
|
|
139
|
+
h.SetMarkerStyle(20)
|
|
140
|
+
h.SetMarkerSize(1.2)
|
|
141
|
+
h.Draw("E")
|
|
142
|
+
|
|
143
|
+
# Style fit function
|
|
144
|
+
func = h.GetFunction(fit_function)
|
|
145
|
+
if func:
|
|
146
|
+
func.SetLineColor(ROOT.kRed)
|
|
147
|
+
func.SetLineWidth(2)
|
|
148
|
+
|
|
149
|
+
# Add legend
|
|
150
|
+
legend = ROOT.TLegend(0.65, 0.7, 0.88, 0.88)
|
|
151
|
+
legend.SetBorderSize(0)
|
|
152
|
+
legend.SetFillStyle(0)
|
|
153
|
+
legend.AddEntry(h, "Histogram", "lep")
|
|
154
|
+
if func:
|
|
155
|
+
legend.AddEntry(func, f"Fit: {fit_function}", "l")
|
|
156
|
+
legend.Draw()
|
|
157
|
+
|
|
158
|
+
canvas.Update()
|
|
159
|
+
canvas.SaveAs(output_plot)
|
|
160
|
+
|
|
161
|
+
# Extract fit parameters
|
|
162
|
+
params = [f"p{i}={func.GetParameter(i):.4f}" for i in range(func.GetNpar())]
|
|
163
|
+
chi2 = func.GetChisquare()
|
|
164
|
+
ndf = func.GetNDF()
|
|
165
|
+
|
|
166
|
+
f.Close()
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
f"Histogram fit completed.\n"
|
|
170
|
+
f"Function: {fit_function}\n"
|
|
171
|
+
f"Parameters: {', '.join(params)}\n"
|
|
172
|
+
f"Chi2/NDF: {chi2:.3f}/{ndf}"
|
|
173
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
import ROOT
|
|
3
|
+
from langchain_core.tools import tool
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@tool
|
|
7
|
+
def get_histogram_stats(file_path: str, hist_name: str) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Retrieve basic statistical information (mean, RMS, entries) of a histogram
|
|
10
|
+
stored in a ROOT file.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
file_path : str
|
|
15
|
+
Path to the ROOT file containing the histogram.
|
|
16
|
+
hist_name : str
|
|
17
|
+
Name of the histogram within the ROOT file.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
str
|
|
22
|
+
A formatted string reporting the histogram's mean, RMS, and total entries.
|
|
23
|
+
Example:
|
|
24
|
+
"myHist -> Mean: 0.123, RMS: 1.234, Entries: 1000"
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
f = ROOT.TFile.Open(file_path)
|
|
28
|
+
if not f or f.IsZombie():
|
|
29
|
+
return f"Error: could not open file {file_path}."
|
|
30
|
+
h = f.Get(hist_name)
|
|
31
|
+
if not h:
|
|
32
|
+
f.Close()
|
|
33
|
+
return f"Error: histogram '{hist_name}' not found in file {file_path}."
|
|
34
|
+
mean = h.GetMean()
|
|
35
|
+
rms = h.GetRMS()
|
|
36
|
+
entries = int(h.GetEntries())
|
|
37
|
+
f.Close()
|
|
38
|
+
return f"{hist_name} -> Mean: {mean:.3f}, RMS: {rms:.3f}, Entries: {entries}"
|