jusflpyroot 0.1.12
Useful tools for root in python
Requires Python: >=3.12

# JusflPyRoot

a library to ease the life with powerfull python numpy and powerfull
ROOT classes.

*in progress, very early stage*

## Installation

Use `uv`{.verbatim} from astral to handle the environment.

``` bash
 uv tool install jusflpyroot
# OR classically
 pip install jusflpyroot
```

## Use examples

### Basic histo creation, save, load

Self-explanatory, create TH1F, induce NumpyTH1, save, destoy, repeat,
etc...

``` python
if __name__ == "__main__":

    NumpyTH1.list_file("bobes.root") #  list the file content if exists
    #   create one ROOT  histogram
    h = ROOT.TH1F("namea", "histogram that goes to file", 100, 0, 100)

    print("i... filling-in with a binary pattern to distinguish under/ovrflow and the content")
    h.Fill(- 1 )            # underflow
    h.Fill(0, 2)            # 2x inside
    h.Fill(100 - 0.0001, 4) # 4x inside
    h.Fill(100 , 8)         # 8x overflow

    #   create THE OBJECT
    nh = NumpyTH1.from_th1(h)
    nh.save("bobes.root", save_format="root")
    nh.force_del()  # brutally remove the object from instances

    # once more, but empty, I dont care about 'h'
    h = ROOT.TH1F("nameb", "histogram that also goes to file", 100, 0, 100)
    nh = NumpyTH1.from_th1(h)
    nh.save("bobes.root", save_format="root")
    nh.force_del()

    # last time, but dont delete this time
    h = ROOT.TH1F("namec", "histogram just here", 100, 0, 100)
    nh = NumpyTH1.from_th1(h)
    nh2 = NumpyTH1.load("bobes.root", "namea", load_format="root")

    print(" ... _______ I expect to see 'namec' (still in memory)   and 'namea' from disk")
    NumpyTH1.list()
    print(" ... _______ on disk:")
    NumpyTH1.list_file("bobes.root")

    # get three vectors for the model Fit
    x, y, dy = nh.get_xy()
```

The output should look like this:

``` example
i... filling-in with a binary pattern to distinguish under/ovrflow and the content
i...  saving   histo 'namea'  into   'bobes.root'
D...  deleting histo 'namea'  #instances   1 =>   0
i...  saving   histo 'nameb'  into   'bobes.root'
D...  deleting histo 'nameb'  #instances   1 =>   0
i...  loading        'namea'    from bobes.root
i... there is 2 histograms total in the file
 ... _______ I expect to see 'namec' (still in memory)   and 'namea' from disk
 0. namec      'histogram just here                '  2025-07-09 14:21:17.876   100   <0.0 - 100.0)   [ 0.0 / 0.0 / 0.0 ]
 1. namea      'histogram that goes to file        '  2025-07-09 14:21:17.876   100   <0.0 - 100.0)   [ 1.0 / 6.0 / 8.0 ]
 ... _______ on disk:
f...   ...   namea     (TH1 in bobes.root)
f...   ...   nameb     (TH1 in bobes.root)
```

### Minuit fit

``` python
h = ROOT.TH1F("namec", "histogram just here", 10, 0, 10)
for i in range(10): # for range(11) .... 10 will already go to overflows
    h.Fill(i, i)
for i in range(10): # make some mess
    h.Fill(2)
    h.Fill(3)
    h.Fill(4)
    h.Fill(5)

NumpyTH1.by_name("namec").Draw("numpy") # Draw using matplotlib

print("... ========================= fitting ==========================")
x, y, dy = nh.get_xy()  # Get data for fit (from histogram)
print(x)
print(y)
print(dy)

Fitter = PrepareLSQFit(x, y, dy ) # provide data to FITTER
Fitter.set_model("p2")            # select mode name and function
Fitter.FIT( a= -0.1, b=1, c=1)    # initial values + constant names; par names must match
Fitter.conclude()  # prints and plots using matplotlib
# NumpyTH1.wait_loop() # not needed with matplotlib plt
```

### Delete all

Delete all instances, reset colors. Can be useful in emacs codeblocks.

``` python
print("i... LIST")
NumpyTH1.list()
print("X...  DELETING ALL")
NumpyTH1.reset_all()
print("X...  DELETED")
print("i... LIST EMPTY - START")
NumpyTH1.list()
print("i... LIST EMPTY - END")
```

## Thoughts on development

### [TODO]{.todo .TODO} External models {#external-models}

-   model~name~ ... whatever name is ok
-   and MODEL ... now it is like

``` python
def P0(self, a):
    model_points = np.zeros_like(self.cx) + a
    if self.switch_xi2_output:
        return self.XI2(model_points)
    return model_points
```

-   But an external model needs more than just
    `switch_xi2_output`{.verbatim} (to be able to conclude - and the
    summary is very important).
-   My ODE needed:
    -   constants defined for the actual situation
    -   parameter `xs1` that is varied but had to be global for
        different steps
    -   parameter `norm` that was simply just by `Xi2*norm`
    -   X0 field that keeps per partes calculation data

1.  Class for an external model

    It seems that a dedicated class would be a solution for an external
    mode.

    -   init and/or reset all steps
    -   set the array of `x`{.verbatim} points where the model is
        evaluated `set_x_points(x)`{.verbatim}
    -   evaluate Xi2 given the parameters:
        `evaluate_Xi2(a=1,b=2)`{.verbatim}
    -   provide the calculated points:
        `calculate_points(a=1,b=2)`{.verbatim}

    ``` python
    class MyModel:
        "The most basic model we can have"
        cx=np.array([])
        params={} # container for parameters
        model_name="mymodel"

        def __init__(self):
            # initial parameters
            self.params['a'] = 1
            self.params['b'] = 1
            return

        def set_x_points(self,x):
            "Set X pointes, where the model calculates y values"
            self.cx=np.array(x)
            return

        def MODEL(self,xx):
            "Model here... whatever calculation, parameters are set globaly-in-the-class"
            y=xx*self.params['a'] + self.params['b']
            return y

        def calculate_points(self,a=1,b=1):
            "MAIN CALLed FUNCTION... set the parameters and call MODEL on xx"
            if len(cx)==0:return None
            self.params['a']=a
            self.params['b']=b  # Save The parameters to the instance
            y=self.MODEL(self.cx)
            return y

    # ---------- other ideas ----------------
        def guess_init_parameters_from_y(self,y):
            if len(cx)==0:return None
            self.params['a']=1
            self.params['a']=1
            return par_dict
    ```

    How to call it?

    ``` python
    A=MyModel()
    A.set_x_points(x)
    #
    Fitter = PrepareLSQFit(x, y, dy ) # provide data to FITTER
    Fitter.set_model_name("mm", set_model=False)
    Fitter.set_ext_model( A.calculate_points ) # but I need only the function y=f(x)
    Fitter.FIT( a= -0.1, b=1 )    #  paramater names must match
    Fitter.conclude()
    ```